Просмотр исходного кода

Merge branch 'master' into enhance/mobile-silk

charlie 4 месяцев назад
Родитель
Сommit
aecff1e18f
37 измененных файлов с 1335 добавлено и 122 удалено
  1. 2 0
      .clj-kondo/config.edn
  2. 171 0
      .github/workflows/cli.yml
  3. 1 0
      deps.edn
  4. 6 0
      deps/cli/.carve/ignore
  5. 23 0
      deps/cli/.clj-kondo/config.edn
  6. 2 0
      deps/cli/.gitignore
  7. 6 0
      deps/cli/CHANGELOG.md
  8. 149 0
      deps/cli/README.md
  9. 44 0
      deps/cli/bb.edn
  10. 15 0
      deps/cli/cli.mjs
  11. 7 0
      deps/cli/deps.edn
  12. 6 0
      deps/cli/nbb.edn
  13. 36 0
      deps/cli/package.json
  14. 112 0
      deps/cli/src/logseq/cli.cljs
  15. 24 0
      deps/cli/src/logseq/cli/commands/export_edn.cljs
  16. 53 0
      deps/cli/src/logseq/cli/commands/graph.cljs
  17. 92 0
      deps/cli/src/logseq/cli/commands/query.cljs
  18. 43 0
      deps/cli/src/logseq/cli/commands/search.cljs
  19. 32 0
      deps/cli/src/logseq/cli/common/graph.cljs
  20. 42 0
      deps/cli/src/logseq/cli/spec.cljs
  21. 23 0
      deps/cli/src/logseq/cli/text_util.cljs
  22. 51 0
      deps/cli/src/logseq/cli/util.cljs
  23. 35 0
      deps/cli/test/logseq/cli/text_util_test.cljs
  24. 23 0
      deps/cli/test/logseq/cli_test.cljs
  25. 296 0
      deps/cli/yarn.lock
  26. 4 0
      deps/common/src/logseq/common/config.cljs
  27. 4 5
      deps/db/src/logseq/db/common/sqlite.cljs
  28. 1 0
      deps/db/src/logseq/db/common/sqlite_cli.cljs
  29. 1 1
      deps/db/src/logseq/db/sqlite/util.cljs
  30. 1 1
      scripts/src/logseq/tasks/dev/lint.clj
  31. 9 15
      src/electron/electron/db.cljs
  32. 11 35
      src/electron/electron/handler.cljs
  33. 3 8
      src/electron/electron/utils.cljs
  34. 1 1
      src/main/frontend/config.cljs
  35. 3 20
      src/main/frontend/util/text.cljs
  36. 2 2
      src/main/logseq/api.cljs
  37. 1 34
      src/test/frontend/util/text_test.cljs

+ 2 - 0
.clj-kondo/config.edn

@@ -165,6 +165,8 @@
              frontend.worker.state worker-state
              frontend.worker.util worker-util
              lambdaisland.glogi log
+             logseq.cli.common.graph cli-common-graph
+             logseq.cli.text-util cli-text-util
              logseq.common.config common-config
              logseq.common.date-time-util date-time-util
              logseq.common.graph common-graph

+ 171 - 0
.github/workflows/cli.yml

@@ -0,0 +1,171 @@
+name: logseq/cli CI
+
+on:
+  # Path filters ensure jobs only kick off if a change is made to cli or
+  # its local dependencies
+  push:
+    branches: [master]
+    paths:
+      - 'deps/cli/**'
+      - '.github/workflows/cli.yml'
+      - '!deps/cli/**.md'
+      # Deps that logseq/cli depends on should trigger this workflow
+      - 'deps/outliner/**'
+      - 'deps/graph-parser/**'
+      - 'deps/db/**'
+      - 'deps/common/**'
+  pull_request:
+    branches: [master]
+    paths:
+      - 'deps/cli/**'
+      - '.github/workflows/cli.yml'
+      - '!deps/cli/**.md'
+      # Deps that logseq/cli depends on should trigger this workflow
+      - 'deps/outliner/**'
+      - 'deps/graph-parser/**'
+      - 'deps/db/**'
+      - 'deps/common/**'
+  workflow_dispatch:
+    inputs:
+      git-ref:
+        description: "Release Git Ref (Which branch to build?)"
+        required: true
+        default: "master"
+      release-tag:
+        type: choice
+        description: "Npm Release Tag (Use 'latest' for a stable release)"
+        required: true
+        options:
+          - alpha
+          - latest
+        default: "latest"
+
+defaults:
+  run:
+    working-directory: deps/cli
+
+env:
+  CLOJURE_VERSION: '1.11.1.1413'
+  # This is the same as 1.8.
+  JAVA_VERSION: '11'
+  # This is the latest node version we can run.
+  NODE_VERSION: '22'
+  BABASHKA_VERSION: '1.0.168'
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Set up Node
+        uses: actions/setup-node@v3
+        with:
+          node-version: ${{ env.NODE_VERSION }}
+          cache: 'yarn'
+          cache-dependency-path: deps/cli/yarn.lock
+
+      - name: Set up Java
+        uses: actions/setup-java@v3
+        with:
+          distribution: 'zulu'
+          java-version: ${{ env.JAVA_VERSION }}
+
+      # Clojure needed for bb step
+      - name: Set up Clojure
+        uses: DeLaGuardo/[email protected]
+        with:
+          cli: ${{ env.CLOJURE_VERSION }}
+          bb: ${{ env.BABASHKA_VERSION }}
+
+      - name: Fetch yarn deps
+        run: yarn install --frozen-lockfile
+
+      - name: Run nbb-logseq tests
+        run: yarn test
+
+      # In this job because it depends on an npm package
+      - name: Load namespaces into nbb-logseq
+        run: bb test:load-all-namespaces-with-nbb .
+
+  lint:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Set up Java
+        uses: actions/setup-java@v3
+        with:
+          distribution: 'zulu'
+          java-version: ${{ env.JAVA_VERSION }}
+
+      - name: Set up Clojure
+        uses: DeLaGuardo/[email protected]
+        with:
+          cli: ${{ env.CLOJURE_VERSION }}
+          bb: ${{ env.BABASHKA_VERSION }}
+
+      - name: Run clj-kondo lint
+        run: clojure -M:clj-kondo --lint src test
+
+      - name: Carve lint for unused vars
+        run: bb lint:carve
+
+      - name: Lint for vars that are too large
+        run: bb lint:large-vars
+
+      - name: Lint for namespaces that aren't documented
+        run: bb lint:ns-docstrings
+
+      - name: Lint for public vars that are private based on usage
+        run: bb lint:minimize-public-vars
+
+  release:
+    if: ${{ github.event_name == 'workflow_dispatch' }}
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check out Git repository
+        uses: actions/checkout@v4
+        with:
+          ref: ${{ github.event.inputs.git-ref }}
+
+      - name: Set up Node
+        uses: actions/setup-node@v3
+        with:
+          node-version: ${{ env.NODE_VERSION }}
+          cache: 'yarn'
+          cache-dependency-path: deps/cli/yarn.lock
+
+      - name: Set up Java
+        uses: actions/setup-java@v3
+        with:
+          distribution: 'zulu'
+          java-version: ${{ env.JAVA_VERSION }}
+
+      # Clojure needed for bb step
+      - name: Set up Clojure
+        uses: DeLaGuardo/[email protected]
+        with:
+          cli: ${{ env.CLOJURE_VERSION }}
+          bb: ${{ env.BABASHKA_VERSION }}
+
+      - name: Fetch yarn deps
+        run: yarn install --frozen-lockfile
+
+      - name: Bundle vendor deps
+        run: bb build:vendor-nbb-deps
+
+      # - name: Debug package
+      #   run: yarn pack && tar -tf logseq-cli-*.tgz
+
+      - name: Authenticate with registry
+        env:
+          NPM_TOKEN: ${{ secrets.NPM_CLI_TOKEN }}
+        run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
+
+      - name: Publish package
+        run: npm publish --tag "${{ github.event.inputs.release-tag || 'alpha' }}" --access public

+ 1 - 0
deps.edn

@@ -37,6 +37,7 @@
   logseq/graph-parser                   {:local/root "deps/graph-parser"}
   logseq/outliner                       {:local/root "deps/outliner"}
   logseq/publishing                     {:local/root "deps/publishing"}
+  logseq/cli                            {:local/root "deps/cli"}
   logseq/shui                           {:local/root "deps/shui"}
   metosin/malli                         {:mvn/version "0.16.1"}
   com.cognitect/transit-cljs            {:mvn/version "0.8.280"}

+ 6 - 0
deps/cli/.carve/ignore

@@ -0,0 +1,6 @@
+;; Lazy loaded
+logseq.cli.commands.export-edn/export
+logseq.cli.commands.graph/show-graph
+logseq.cli.commands.graph/list-graphs
+logseq.cli.commands.query/query
+logseq.cli.commands.search/search

+ 23 - 0
deps/cli/.clj-kondo/config.edn

@@ -0,0 +1,23 @@
+{:linters
+ {:aliased-namespace-symbol {:level :warning}
+  :namespace-name-mismatch {:level :warning}
+  :used-underscored-binding {:level :warning}
+  :shadowed-var {:level :warning}
+
+  :consistent-alias
+  {:aliases {"fs" fs
+             "node-path" path
+             clojure.pprint pprint
+             clojure.string string
+             datascript.core d
+             logseq.cli.commands.graph cli-graph
+             logseq.cli.common.graph cli-common-graph
+             logseq.cli.util cli-util
+             logseq.cli.text-util cli-text-util
+             logseq.common.config common-config
+             logseq.common.graph common-graph
+             logseq.common.util.date-time date-time-util
+             logseq.db.common.sqlite common-sqlite}}}
+ :lint-as {promesa.core/let clojure.core/let}
+ :skip-comments true
+ :output {:progress true}}

+ 2 - 0
deps/cli/.gitignore

@@ -0,0 +1,2 @@
+.clj-kondo/.cache
+/vendor

+ 6 - 0
deps/cli/CHANGELOG.md

@@ -0,0 +1,6 @@
+## 0.1.0
+
+* Initial release!
+* Provides commands: list, show, search, query, export-edn and help
+* All commands except search work offline. search and query have options for calling HTTP Server of
+  open desktop Logseq app

+ 149 - 0
deps/cli/README.md

@@ -0,0 +1,149 @@
+## Description
+
+This library provides a `logseq` CLI for DB graphs. The CLI currently only applies to desktop DB graphs and requires the [database-version](/README.md#-database-version) desktop app to be installed. The CLI works offline by default which means it can also be used on CI/CD platforms like Github Actions. Some CLI commands can also interact with the current DB graph if the [HTTP Server](https://docs.logseq.com/#/page/local%20http%20server) is turned on in the Desktop app.
+
+## Install
+
+Install the `logseq` CLI with `npm install -g @logseq/cli`.
+
+## Usage
+
+This section assumes you have installed the CLI from npm or via the [dev
+setup](#setup). If you haven't, substitute `node cli.mjs` for `logseq` e.g.
+`node.cli.mjs -h`.
+
+All commands excepts for `search` can be used offline or on CI. The `search` command and any command that has an api-query-token option require the [HTTP Server](https://docs.logseq.com/#/page/local%20http%20server) to be turned on.
+
+Now let's use it!
+
+```
+$ logseq -h
+Usage: logseq [command] [options]
+
+Options:
+  -v, --version Print version
+
+Commands:
+list                 List graphs
+show                 Show DB graph(s) info
+search [options]     Search current DB graph
+query [options]      Query DB graph(s)
+export-edn [options] Export DB graph as EDN
+help                 Print a command's help
+
+
+$ logseq list
+DB Graphs:
+db-test
+docs
+woot
+...
+
+File Graphs:
+docs
+...
+
+$ logseq show db-test
+
+|                         Name |                                              Value |
+|------------------------------+----------------------------------------------------|
+|              Graph directory |                    /Users/me/logseq/graphs/db-test |
+|             Graph created at |                                     Jul 12th, 2025 |
+|         Graph schema version |                              {:major 65, :minor 7} |
+| Graph initial schema version |                              {:major 65, :minor 7} |
+|      Graph created by commit | https://github.com/logseq/logseq/commit/3c93fd2637 |
+|            Graph imported by |                                  :cli/create-graph |
+
+# Search your current graph and print results one per line like grep
+$ logseq search woot -a my-token
+Search found 100 results:
+dev:db-export woot woot.edn && dev:db-create woot2 woot.edn
+dev:db-diff woot woot2
+...
+
+# Query a graph locally using `d/entity` id(s) like an integer or a :db/ident
+# Can also specify a uuid string to fetch an entity
+$ logseq query woot 10 :logseq.class/Tag
+({:db/id 10,
+  :db/ident :logseq.kv/graph-git-sha,
+  :kv/value "f736895b1b-dirty"}
+ {:block/uuid #uuid "00000002-5389-0208-3000-000000000000",
+  :block/updated-at 1751990934670,
+  :logseq.property.class/extends #{{:db/id 1}},
+  :block/created-at 1751990934670,
+  :logseq.property/built-in? true,
+  :block/tags #{{:db/id 2}},
+  :block/title "Tag",
+  :db/id 2,
+  :db/ident :logseq.class/Tag,
+  :block/name "tag"})
+
+# Query a graph using a datalog query
+$ logseq query woot '[:find (pull ?b [*]) :where [?b :kv/value]]'
+[{:db/id 5, :db/ident :logseq.kv/db-type, :kv/value "db"}
+ {:db/id 6,
+  :db/ident :logseq.kv/schema-version,
+  :kv/value {:major 65, :minor 7}}
+
+# Query the current graph using the api server
+# An api query can be a datalog query or a simple query
+$ logseq query '(task DOING)' -a my-token
+ [{:journalDay 20250717,
+   :name "jul 17th, 2025",
+   :title "Jul 17th, 2025",
+   :type "journal",
+   :uuid "00000001-2025-0717-0000-000000000000",
+   :id 36418,
+   :content "Jul 17th, 2025"},
+  :title
+  "DOING Logseq CLI\nid:: 68795144-e5f6-48e8-849d-79cd6473b952\n:LOGBOOK:\nCLOCK: [2025-07-17 Thu 12:37:09]\n:END:",
+  :propertiesOrder ["id"],
+  :id 37013,
+  :order "aF",
+  :uuid "68795144-e5f6-48e8-849d-79cd6473b952"}
+  ...
+
+# Export your DB graph as EDN
+$ logseq export-edn woot -f woot.edn
+Exported 16 properties, 16 classes and 36 pages
+```
+
+## API
+
+This library is under the parent namespace `logseq.cli`.
+
+## Dev
+
+Most of this library is also compatible with ClojureScript for use on the
+frontend. This library follows the practices that [the Logseq frontend
+follows](/docs/dev-practices.md). Most of the same linters are used, with
+configurations that are specific to this library. See [this library's CI
+file](/.github/workflows/cli.yml) for linting examples.
+
+### Setup
+
+First install the following dependencies:
+* Install node.js >= 22 and yarn.
+* Run `yarn install` to install npm dependencies.
+* Install [babashka](https://github.com/babashka/babashka).
+
+To install the CLI locally, `yarn link`.
+
+### Testing
+
+Testing is done with nbb-logseq and
+[nbb-test-runner](https://github.com/nextjournal/nbb-test-runner). Some basic
+usage:
+
+```
+# Run all tests
+$ yarn test
+# List available options
+$ yarn test -H
+# Run tests with :focus metadata flag
+$ yarn test -i focus
+```
+
+### Managing dependencies
+
+See [standard nbb/cljs library advice in graph-parser](/deps/graph-parser/README.md#managing-dependencies).

+ 44 - 0
deps/cli/bb.edn

@@ -0,0 +1,44 @@
+{:min-bb-version "1.0.168"
+ :deps
+ {logseq/bb-tasks
+  #_{:local/root "../../../bb-tasks"}
+  {:git/url "https://github.com/logseq/bb-tasks"
+   :git/sha "70d3edeb287f5cec7192e642549a401f7d6d4263"}}
+
+ :pods
+ {clj-kondo/clj-kondo {:version "2024.09.27"}}
+
+ :tasks
+ {build:vendor-nbb-deps
+  {:doc "Copy over latest nbb deps to vendor/ and make CLI independent of nbb.edn"
+   :requires ([babashka.fs :as fs])
+   :task (do
+           (shell "yarn nbb-logseq -e :load-deps")
+           (let [nbb-cache-dir (or (first (fs/list-dir ".nbb/.cache"))
+                                   (throw (ex-info "No nbb cache directory found" {})))]
+             (fs/delete-tree "vendor/src")
+             (fs/copy-tree (fs/path nbb-cache-dir "nbb-deps/logseq") "vendor/src/logseq")
+             (fs/copy-tree (fs/path nbb-cache-dir "nbb-deps/malli") "vendor/src/malli")
+             (fs/copy-tree (fs/path nbb-cache-dir "nbb-deps/borkdude") "vendor/src/borkdude")
+             (fs/copy-tree (fs/path nbb-cache-dir "nbb-deps/medley") "vendor/src/medley"))
+           (fs/delete-if-exists "nbb.edn")
+           (println "Done!"))}
+  test:load-all-namespaces-with-nbb
+  logseq.bb-tasks.nbb.test/load-all-namespaces
+
+  lint:large-vars
+  logseq.bb-tasks.lint.large-vars/-main
+
+  lint:carve
+  logseq.bb-tasks.lint.carve/-main
+
+  lint:ns-docstrings
+  logseq.bb-tasks.lint.ns-docstrings/-main
+
+  lint:minimize-public-vars
+  logseq.bb-tasks.lint.minimize-public-vars/-main}
+
+ :tasks/config
+ {:large-vars
+  {:max-lines-count 30
+   :metadata-exceptions #{:large-vars/cleanup-todo}}}}

+ 15 - 0
deps/cli/cli.mjs

@@ -0,0 +1,15 @@
+#!/usr/bin/env node
+
+import { loadFile, addClassPath } from '@logseq/nbb-logseq'
+import { fileURLToPath } from 'url';
+import { dirname, resolve } from 'path';
+
+const __dirname = fileURLToPath(dirname(import.meta.url));
+global.__dirname = __dirname
+addClassPath(resolve(__dirname, 'src'));
+addClassPath(resolve(__dirname, 'vendor/src'));
+const { main } = await loadFile(resolve(__dirname, 'src/logseq/cli.cljs'));
+
+// Expects to be called as node X.js ...
+const args = process.argv.slice(2)
+main.apply(null, args);

+ 7 - 0
deps/cli/deps.edn

@@ -0,0 +1,7 @@
+{:paths ["src"]
+ :deps
+ {logseq/outliner {:local/root "../outliner"}}
+
+ :aliases
+ {:clj-kondo {:replace-deps {clj-kondo/clj-kondo {:mvn/version "2024.09.27"}}
+              :main-opts    ["-m" "clj-kondo.main"]}}}

+ 6 - 0
deps/cli/nbb.edn

@@ -0,0 +1,6 @@
+{:paths ["src"]
+ :deps
+ {logseq/outliner
+  {:local/root "../outliner"}
+  io.github.nextjournal/nbb-test-runner
+  {:git/sha "b379325cfa5a3306180649da5de3bf5166414e71"}}}

+ 36 - 0
deps/cli/package.json

@@ -0,0 +1,36 @@
+{
+  "name": "@logseq/cli",
+  "version": "0.1.0-alpha.1",
+  "description": "Logseq CLI",
+  "bin": {
+    "logseq": "cli.mjs"
+  },
+  "engines": {
+    "node": ">=22.17.0"
+  },
+  "license": "MIT",
+  "dependencies": {
+    "@logseq/nbb-logseq": "github:logseq/nbb-logseq#feat-db-v26",
+    "better-sqlite3": "~11.10.0",
+    "fs-extra": "^11.3.0"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/logseq/logseq.git",
+    "directory": "deps/cli"
+  },
+  "keywords": [
+    "logseq",
+    "knowledge graph",
+    "note taking",
+    "clojurescript",
+    "nbb"
+  ],
+  "files": [
+    "src",
+    "vendor"
+  ],
+  "scripts": {
+    "test": "yarn nbb-logseq -cp test -m nextjournal.test-runner"
+  }
+}

+ 112 - 0
deps/cli/src/logseq/cli.cljs

@@ -0,0 +1,112 @@
+(ns logseq.cli
+  "Main ns for Logseq CLI"
+  (:require ["fs" :as fs]
+            ["path" :as node-path]
+            [babashka.cli :as cli]
+            [clojure.string :as string]
+            [logseq.cli.common.graph :as cli-common-graph]
+            [logseq.cli.spec :as cli-spec]
+            [nbb.error]
+            [promesa.core :as p]))
+
+(defn- format-commands [{:keys [table]}]
+  (let [table (mapv (fn [{:keys [cmds desc spec]}]
+                      (cond-> [(str (string/join " " cmds)
+                                    (when spec " [options]"))]
+                        desc (conj desc)))
+                    (filter (comp seq :cmds) table))]
+    (cli/format-table {:rows table})))
+
+(def ^:private default-spec
+  {:version {:coerce :boolean
+             :alias :v
+             :desc "Print version"}})
+
+(declare table)
+(defn- help [_m]
+  (println (str "Usage: logseq [command] [options]\n\nOptions:\n"
+                (cli/format-opts {:spec default-spec})))
+  (println (str "\nCommands:\n" (format-commands {:table table}))))
+
+(defn- default-command
+  [{{:keys [version]} :opts :as m}]
+  (if version
+    (let [package-json (node-path/join js/__dirname "package.json")]
+      (when (fs/existsSync package-json)
+        (println (-> (fs/readFileSync package-json)
+                     js/JSON.parse
+                     (aget "version")))))
+    (help m)))
+
+(defn- command-help [{{:keys [command]} :opts}]
+  (if-let [cmd-map (and command (some #(when (= command (first (:cmds %))) %) table))]
+    (println (str "Usage: logseq " command
+                  (when (:args->opts cmd-map)
+                    (str " " (string/join " "
+                                          (map #(str "[" (name %) "]") (:args->opts cmd-map)))))
+                  (when (:spec cmd-map)
+                    (str " [options]\n\nOptions:\n"
+                         (cli/format-opts {:spec (:spec cmd-map)})))))
+    (println "Command" (pr-str command) "does not exist")))
+
+(defn- lazy-load-fn
+  "Lazy load fn to speed up start time. After nbb requires ~30 namespaces, start time gets close to 1s"
+  [fn-sym]
+  (fn [& args]
+    (-> (p/let [_ (require (symbol (namespace fn-sym)))]
+          (apply (resolve fn-sym) args))
+        (p/catch (fn [err]
+                   (if (= :sci/error (:type (ex-data err)))
+                     (nbb.error/print-error-report err)
+                     (js/console.error "Error:" err))
+                   (js/process.exit 1))))))
+
+(def ^:private table
+  [{:cmds ["list"] :desc "List graphs"
+    :fn (lazy-load-fn 'logseq.cli.commands.graph/list-graphs)}
+   {:cmds ["show"] :desc "Show DB graph(s) info"
+    :fn (lazy-load-fn 'logseq.cli.commands.graph/show-graph)
+    :args->opts [:graphs] :coerce {:graphs []} :require [:graphs]}
+   {:cmds ["search"]
+    :fn (lazy-load-fn 'logseq.cli.commands.search/search)
+    :desc "Search current DB graph"
+    :args->opts [:search-terms] :coerce {:search-terms []} :require [:search-terms]
+    :spec cli-spec/search}
+   {:cmds ["query"] :desc "Query DB graph(s)"
+    :fn (lazy-load-fn 'logseq.cli.commands.query/query)
+    :args->opts [:graph :args] :coerce {:args []} :no-keyword-opts true :require [:graph :args]
+    :spec cli-spec/query}
+   {:cmds ["export-edn"] :desc "Export DB graph as EDN"
+    :fn (lazy-load-fn 'logseq.cli.commands.export-edn/export)
+    :args->opts [:graph] :require [:graph]
+    :spec cli-spec/export-edn}
+   {:cmds ["help"] :fn command-help :desc "Print a command's help"
+    :args->opts [:command] :require [:command]}
+   {:cmds []
+    :spec default-spec
+    :fn default-command}])
+
+(defn- error-if-db-version-not-installed
+  []
+  (when-not (fs/existsSync (cli-common-graph/get-db-graphs-dir))
+    (println "Error: The database version's desktop app is not installed. Please install per https://github.com/logseq/logseq/#-database-version.")
+    (js/process.exit 1)))
+
+(defn ^:api -main [& args]
+  (when-not (contains? #{nil "-h" "--help"} (first args))
+    (error-if-db-version-not-installed))
+  (try
+    (cli/dispatch table
+                  args
+                  {:error-fn (fn [{:keys [cause msg option] type' :type :as data}]
+                               (if (and (= :org.babashka/cli type')
+                                        (= :require cause))
+                                 (println "Error: Command missing required"
+                                          (if (get-in data [:spec option]) "option" "argument")
+                                          option)
+                                 (throw (ex-info msg data)))
+                               (js/process.exit 1))})
+    (catch ^:sci/error js/Error e
+      (nbb.error/print-error-report e))))
+
+#js {:main -main}

+ 24 - 0
deps/cli/src/logseq/cli/commands/export_edn.cljs

@@ -0,0 +1,24 @@
+(ns logseq.cli.commands.export-edn
+  "Export edn command"
+  (:require ["fs" :as fs]
+            [clojure.pprint :as pprint]
+            [logseq.db.common.sqlite-cli :as sqlite-cli]
+            [logseq.db.sqlite.export :as sqlite-export]
+            [logseq.cli.util :as cli-util]))
+
+(defn export [{{:keys [graph] :as options} :opts}]
+  (if (fs/existsSync (cli-util/get-graph-dir graph))
+   (let [conn (apply sqlite-cli/open-db! (cli-util/->open-db-args graph))
+         export-map (sqlite-export/build-export @conn
+                                                (cond-> {:export-type (:export-type options)}
+                                                  (= :graph (:export-type options))
+                                                  (assoc :graph-options (dissoc options :file :export-type :graph))))]
+     (if (:file options)
+       (do
+         (println "Exported" (count (:properties export-map)) "properties,"
+                  (count (:properties export-map)) "classes and"
+                  (count (:pages-and-blocks export-map)) "pages")
+         (fs/writeFileSync (:file options)
+                           (with-out-str (pprint/pprint export-map))))
+       (pprint/pprint export-map)))
+    (cli-util/error "Graph" (pr-str graph) "does not exist")))

+ 53 - 0
deps/cli/src/logseq/cli/commands/graph.cljs

@@ -0,0 +1,53 @@
+(ns logseq.cli.commands.graph
+  "Graph related commands"
+  (:require ["fs" :as fs]
+            ["path" :as node-path]
+            [cljs-time.coerce :as tc]
+            [clojure.pprint :as pprint]
+            [clojure.string :as string]
+            [datascript.core :as d]
+            [logseq.cli.common.graph :as cli-common-graph]
+            [logseq.cli.util :as cli-util]
+            [logseq.common.config :as common-config]
+            [logseq.common.util.date-time :as date-time-util]
+            [logseq.db.common.sqlite-cli :as sqlite-cli]))
+
+(defn- ms->journal-title
+  [ms]
+  (date-time-util/format (tc/from-long ms) "MMM do, yyyy"))
+
+(defn show-graph
+  [{{:keys [graphs]} :opts}]
+  (doseq [graph graphs]
+    (let [graph-dir (cli-util/get-graph-dir graph)]
+      (if (fs/existsSync graph-dir)
+        (let [conn (apply sqlite-cli/open-db! (cli-util/->open-db-args graph))
+              kv-value #(:kv/value (d/entity @conn %))]
+          (pprint/print-table
+           (map #(array-map "Name" (first %) "Value" (second %))
+                (cond-> [["Graph directory" graph-dir]
+                         ["Graph created at" (some-> (kv-value :logseq.kv/graph-created-at) ms->journal-title)]
+                         ["Graph schema version" (kv-value :logseq.kv/schema-version)]
+                         ["Graph initial schema version" (kv-value :logseq.kv/graph-initial-schema-version)]]
+                  (d/entity @conn :logseq.kv/graph-git-sha)
+                  (conj ["Graph created by commit"
+                         (str "https://github.com/logseq/logseq/commit/" (kv-value :logseq.kv/graph-git-sha))])
+                  (d/entity @conn :logseq.kv/import-type)
+                  (conj ["Graph imported by" (kv-value :logseq.kv/import-type)])))))
+        (cli-util/error "Graph" (pr-str graph) "does not exist")))))
+
+(defn list-graphs
+  []
+  (let [[db-graphs* file-graphs*] ((juxt filter remove) #(string/starts-with? % common-config/db-version-prefix)
+                                                        (cli-common-graph/get-db-based-graphs))
+        db-graphs (->> db-graphs*
+                       (map #(string/replace-first % common-config/db-version-prefix ""))
+                       sort)
+        file-graphs (->> file-graphs*
+                         (map #(string/replace-first % common-config/file-version-prefix ""))
+                         (map node-path/basename)
+                         sort)]
+    (println "DB Graphs:")
+    (println (string/join "\n" db-graphs))
+    (println "\nFile Graphs:")
+    (println (string/join "\n" file-graphs))))

+ 92 - 0
deps/cli/src/logseq/cli/commands/query.cljs

@@ -0,0 +1,92 @@
+(ns logseq.cli.commands.query
+  "Query command"
+  (:require ["fs" :as fs]
+            [clojure.edn :as edn]
+            [clojure.pprint :as pprint]
+            [clojure.string :as string]
+            [datascript.core :as d]
+            [datascript.impl.entity :as de]
+            [logseq.cli.util :as cli-util]
+            [logseq.common.util :as common-util]
+            [logseq.db.common.sqlite-cli :as sqlite-cli]
+            [logseq.db.frontend.property :as db-property]
+            [logseq.db.frontend.rules :as rules]
+            [promesa.core :as p]))
+
+(defn- api-query
+  [query token]
+  (let [datalog-query? (string/starts-with? query "[")
+        method (if datalog-query?  "logseq.db.datascript_query" "logseq.db.q")]
+    (-> (p/let [resp (cli-util/api-fetch token method [query])]
+          (if (= 200 (.-status resp))
+            (p/let [body (.json resp)]
+              (let [res (js->clj body :keywordize-keys true)
+                    results (if datalog-query?
+                              ;; Remove nesting for most queries which just have one :find binding
+                              (if (= 1 (count (first res))) (mapv first res) res)
+                              res)]
+                (pprint/pprint results)))
+            (cli-util/api-handle-error-response resp)))
+        (p/catch cli-util/command-catch-handler))))
+
+(defn- readable-properties
+  "Expands an entity's properties and tags to be readable. Similar to
+   db-test/readable-properties but to be customized for CLI use"
+  [ent]
+  (->> (db-property/properties ent)
+       (mapv (fn [[k v]]
+               [k
+                (cond
+                  (#{:block/tags :logseq.property.class/extends} k)
+                  (mapv :db/ident v)
+                  (and (set? v) (every? de/entity? v))
+                  (set (map db-property/property-value-content v))
+                  (de/entity? v)
+                  (or (:db/ident v) (db-property/property-value-content v))
+                  :else
+                  v)]))
+       (into {})))
+
+(defn- local-entities-query
+  "Queries by calling d/entity"
+  [db properties-expand args]
+  (map #(when-let [ent (d/entity db
+                                 (cond
+                                   (and (string? %) (common-util/uuid-string? %))
+                                   [:block/uuid (uuid %)]
+                                   (string? %)
+                                   (edn/read-string %)
+                                   :else
+                                   %))]
+          (let [m (into {:db/id (:db/id ent)} ent)]
+            (if properties-expand
+              (merge m (readable-properties m))
+              m)))
+       args))
+
+(defn- local-query
+  [{{:keys [graph args graphs properties-readable]} :opts}]
+  (let [graphs' (into [graph] graphs)]
+    (doseq [graph' graphs']
+      (if (fs/existsSync (cli-util/get-graph-dir graph'))
+        (let [conn (apply sqlite-cli/open-db! (cli-util/->open-db-args graph))
+              query* (when (string? (first args)) (common-util/safe-read-string {:log-error? false} (first args)))
+              ;; If datalog query detected run it or else default to entity lookups
+              results (if (and (vector? query*) (= :find (first query*)))
+                          ;; assumes no :in are in queries
+                        (let [query' (into query* [:in '$ '%])
+                              res (d/q query' @conn (rules/extract-rules rules/db-query-dsl-rules))]
+                          ;; Remove nesting for most queries which just have one :find binding
+                          (if (= 1 (count (first res))) (mapv first res) res))
+                        (local-entities-query @conn properties-readable args))]
+          (when (> (count graphs') 1)
+            (println "Results for graph" (pr-str graph')))
+          (pprint/pprint results))
+        (cli-util/error "Graph" (pr-str graph') "does not exist")))))
+
+(defn query
+  [{{:keys [graph args api-query-token]} :opts :as m}]
+  (if api-query-token
+    ;; graph can be query since it's not used for api-query
+    (api-query (or graph (first args)) api-query-token)
+    (local-query m)))

+ 43 - 0
deps/cli/src/logseq/cli/commands/search.cljs

@@ -0,0 +1,43 @@
+(ns logseq.cli.commands.search
+  "Search command"
+  (:require [clojure.pprint :as pprint]
+            [clojure.string :as string]
+            [logseq.cli.util :as cli-util]
+            [logseq.cli.text-util :as cli-text-util]
+            [promesa.core :as p]))
+
+(defn- highlight
+  "Shows up as soft red on terminals that support ANSI 24-bit color like iTerm"
+  [text]
+  (str "\u001b[38;2;" 242 ";" 101 ";" 106 "m" text "\u001b[0m"))
+
+(defn- highlight-content-query
+  "Return string with highlighted content FTS result. CLI version of cmdk/highlight-content-query"
+  [content]
+  (when-not (string/blank? content)
+    ;; why recur? because there might be multiple matches
+    (loop [content content]
+      (let [[b-cut hl-cut e-cut] (cli-text-util/cut-by content "$pfts_2lqh>$" "$<pfts_2lqh$")
+            new-result (str b-cut (highlight hl-cut) e-cut)]
+        (if-not (string/blank? e-cut)
+          (recur new-result)
+          new-result)))))
+
+(defn search
+  [{{:keys [search-terms api-query-token raw limit]} :opts}]
+  (-> (p/let [resp (cli-util/api-fetch api-query-token
+                                       "logseq.app.search"
+                                       [(string/join " " search-terms) {:limit limit}])]
+        (if (= 200 (.-status resp))
+          (p/let [body (.json resp)]
+            (let [{:keys [blocks]} (js->clj body :keywordize-keys true)]
+              (println "Search found" (count blocks) "results:")
+              (if raw
+                (pprint/pprint blocks)
+                (println (string/join "\n"
+                                      (->> blocks
+                                           (map :block/title)
+                                           (map #(string/replace % "\n" "\\\\n"))
+                                           (map highlight-content-query)))))))
+          (cli-util/api-handle-error-response resp)))
+      (p/catch cli-util/command-catch-handler)))

+ 32 - 0
deps/cli/src/logseq/cli/common/graph.cljs

@@ -0,0 +1,32 @@
+(ns ^:node-only logseq.cli.common.graph
+  "Graph related fns shared between CLI and electron"
+  (:require ["fs-extra" :as fs-extra]
+            ["os" :as os]
+            ["path" :as node-path]
+            [clojure.string :as string]
+            [logseq.common.config :as common-config]
+            [logseq.common.graph :as common-graph]))
+
+(defn- graph-name->path
+  [graph-name]
+  (when graph-name
+    (-> graph-name
+        (string/replace "+3A+" ":")
+        (string/replace "++" "/"))))
+
+(defn get-db-graphs-dir
+  "Directory where DB graphs are stored"
+  []
+  (node-path/join (os/homedir) "logseq" "graphs"))
+
+(defn get-db-based-graphs
+  []
+  (let [dir (get-db-graphs-dir)]
+    (fs-extra/ensureDirSync dir)
+    (->> (common-graph/read-directories dir)
+         (remove (fn [s] (= s common-config/unlinked-graphs-dir)))
+         (map graph-name->path)
+         (map (fn [s]
+                (if (string/starts-with? s common-config/file-version-prefix)
+                  s
+                  (str common-config/db-version-prefix s)))))))

+ 42 - 0
deps/cli/src/logseq/cli/spec.cljs

@@ -0,0 +1,42 @@
+(ns logseq.cli.spec
+  "Babashka.cli specs for commands. Normally these would live alongside their
+  commands but are separate because command namespaces are lazy loaded")
+
+(def export-edn
+  {:include-timestamps? {:alias :T
+                         :desc "Include timestamps in export"}
+   :file {:alias :f
+          :desc "Saves edn to file"}
+   :catch-validation-errors? {:alias :c
+                              :desc "Catch validation errors for dev"}
+   :exclude-namespaces {:alias :e
+                        :coerce #{}
+                        :desc "Namespaces to exclude from properties and classes"}
+   :exclude-built-in-pages? {:alias :b
+                             :desc "Exclude built-in pages"}
+   :exclude-files? {:alias :F
+                    :desc "Exclude :file/path files"}
+   :export-type {:alias :t
+                 :coerce :keyword
+                 :desc "Export type"
+                 :default :graph}})
+
+(def query
+  {:graphs {:alias :g
+            :coerce []
+            :desc "Additional graphs to query"}
+   :properties-readable {:alias :p
+                         :coerce :boolean
+                         :desc "Make properties on entity queries show property values instead of ids"}
+   :api-query-token {:alias :a
+                     :desc "Query current graph with api server token"}})
+
+(def search
+  {:api-query-token {:alias :a
+                     :require true
+                     :desc "Api server token"}
+   :raw {:alias :r
+         :desc "Print raw response"}
+   :limit {:alias :l
+           :default 100
+           :desc "Limit max number of results"}})

+ 23 - 0
deps/cli/src/logseq/cli/text_util.cljs

@@ -0,0 +1,23 @@
+(ns logseq.cli.text-util
+  "Text utils"
+  (:require [clojure.string :as string]))
+
+(defn cut-by
+  "Cut string by specified wrapping symbols, only match the first occurrence.
+     value - string to cut
+     before - cutting symbol (before)
+     end - cutting symbol (end)"
+  [value before end]
+  (let [b-pos (string/index-of value before)
+        b-len (count before)]
+    (if b-pos
+      (let [b-cut (subs value 0 b-pos)
+            m-cut (subs value (+ b-pos b-len))
+            e-len (count end)
+            e-pos (string/index-of m-cut end)]
+        (if e-pos
+          (let [e-cut (subs m-cut (+ e-pos e-len))
+                m-cut (subs m-cut 0 e-pos)]
+            [b-cut m-cut e-cut])
+          [b-cut m-cut nil]))
+      [value nil nil])))

+ 51 - 0
deps/cli/src/logseq/cli/util.cljs

@@ -0,0 +1,51 @@
+(ns ^:node-only logseq.cli.util
+  "Util fns"
+  (:require ["path" :as node-path]
+            [clojure.string :as string]
+            [logseq.cli.common.graph :as cli-common-graph]
+            [logseq.db.common.sqlite :as common-sqlite]
+            [nbb.error]))
+
+(defn get-graph-dir
+  [graph]
+  (node-path/join (cli-common-graph/get-db-graphs-dir) (common-sqlite/sanitize-db-name graph)))
+
+(defn ->open-db-args
+  "Creates args for sqlite-cli/open-db! given a graph. Similar to sqlite-cli/->open-db-args"
+  [graph]
+  [(cli-common-graph/get-db-graphs-dir) (common-sqlite/sanitize-db-name graph)])
+
+(defn api-fetch [token method args]
+  (js/fetch "http://127.0.0.1:12315/api"
+            (clj->js {:method "POST"
+                      :headers {"Authorization" (str "Bearer " token)
+                                "Content-Type" "application/json"}
+                      :body (js/JSON.stringify
+                             (clj->js {:method method
+                                       :args args}))})))
+
+(defn api-handle-error-response
+  "Handles a non 200 response"
+  [resp]
+  (js/console.error "Error: API Server responded with status" (.-status resp)
+                    (when (.-statusText resp) (str "and body " (pr-str (.-statusText resp)))))
+  (js/process.exit 1))
+
+(defn command-catch-handler
+  "Default p/catch handler for commands which handles sci errors and HTTP API Server connections gracefully"
+  [err]
+  (cond
+    (= :sci/error (:type (ex-data err)))
+    (nbb.error/print-error-report err)
+    (string/includes? (some->> err .-cause .-message str) "ECONNREFUSED")
+    (do (js/console.error "Error: Failed to connect to HTTP API Server with error" (pr-str (.-message err)))
+        (js/console.log "Make sure the HTTP API Server is turned on."))
+    :else
+    (js/console.error "Error:" err))
+  (js/process.exit 1))
+
+(defn error
+  "Prints error and then exits"
+  [& strings]
+  (apply println "Error:" strings)
+  (js/process.exit 1))

+ 35 - 0
deps/cli/test/logseq/cli/text_util_test.cljs

@@ -0,0 +1,35 @@
+(ns logseq.cli.text-util-test
+  (:require [cljs.test :refer [are deftest]]
+            [logseq.cli.text-util :as cli-text-util]))
+
+(deftest test-cut-by
+  (are [x y] (= x y)
+    ["" "" ""]
+    (cli-text-util/cut-by "[[]]" "[[" "]]")
+
+    ["" "abc" ""]
+    (cli-text-util/cut-by "[[abc]]" "[[" "]]")
+
+    ["012 " "6" " [[2]]"]
+    (cli-text-util/cut-by "012 [[6]] [[2]]" "[[" "]]")
+
+    ["" "prop" "value"]
+    (cli-text-util/cut-by "prop::value" "" "::")
+
+    ["prop" "" "value"]
+    (cli-text-util/cut-by "prop::value" "::" "")
+
+    ["some " "content" " here"]
+    (cli-text-util/cut-by "some $pfts>$content$pfts<$ here" "$pfts>$" "$pfts<$")
+
+    ["some " "content$pft" nil]
+    (cli-text-util/cut-by "some $pfts>$content$pft" "$pfts>$" "$pfts<$")
+
+    ["some $pf" nil nil]
+    (cli-text-util/cut-by "some $pf" "$pfts>$" "$pfts<$")
+
+    ["" "content" ""]
+    (cli-text-util/cut-by "$pfts>$content$pfts<$" "$pfts>$" "$pfts<$")
+
+    ["" "content$p" nil]
+    (cli-text-util/cut-by "$pfts>$content$p" "$pfts>$" "$pfts<$")))

+ 23 - 0
deps/cli/test/logseq/cli_test.cljs

@@ -0,0 +1,23 @@
+(ns logseq.cli-test
+  (:require ["child_process" :as child-process]
+            [cljs.test :refer [is deftest]]
+            [clojure.string :as string]))
+
+(defn- sh
+  "Run shell cmd synchronously and silently. Stdout/stderr can be inspected as needed"
+  [cmd]
+  (child-process/spawnSync (first cmd)
+                           (clj->js (rest cmd))
+                           #js {:stdio "pipe"}))
+
+(deftest basic-help
+  (let [start-time (cljs.core/system-time)
+        result (sh ["node" "cli.mjs" "--help"])
+        end-time (cljs.core/system-time)]
+
+    (is (string/includes? (str (.-stdout result))
+                          "Usage: logseq [command]"))
+
+    (let [max-time (-> 0.25 (* (if js/process.env.CI 2 1)))]
+      (is (< (-> end-time (- start-time) (/ 1000)) max-time)
+          (str "Printing CLI help takes less than " max-time "s")))))

+ 296 - 0
deps/cli/yarn.lock

@@ -0,0 +1,296 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@logseq/nbb-logseq@github:logseq/nbb-logseq#feat-db-v26":
+  version "1.2.173-feat-db-v25"
+  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/b26d290944234b20762ff109e5328b87ea240692"
+  dependencies:
+    import-meta-resolve "^4.1.0"
+
+base64-js@^1.3.1:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
+  integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
+
+better-sqlite3@~11.10.0:
+  version "11.10.0"
+  resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-11.10.0.tgz#2b1b14c5acd75a43fd84d12cc291ea98cef57d98"
+  integrity sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==
+  dependencies:
+    bindings "^1.5.0"
+    prebuild-install "^7.1.1"
+
+bindings@^1.5.0:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
+  integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
+  dependencies:
+    file-uri-to-path "1.0.0"
+
+bl@^4.0.3:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
+  integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
+  dependencies:
+    buffer "^5.5.0"
+    inherits "^2.0.4"
+    readable-stream "^3.4.0"
+
+buffer@^5.5.0:
+  version "5.7.1"
+  resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
+  integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
+  dependencies:
+    base64-js "^1.3.1"
+    ieee754 "^1.1.13"
+
+chownr@^1.1.1:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
+  integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
+
+decompress-response@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
+  integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==
+  dependencies:
+    mimic-response "^3.1.0"
+
+deep-extend@^0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
+  integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
+
+detect-libc@^2.0.0:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.4.tgz#f04715b8ba815e53b4d8109655b6508a6865a7e8"
+  integrity sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==
+
+end-of-stream@^1.1.0, end-of-stream@^1.4.1:
+  version "1.4.5"
+  resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c"
+  integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==
+  dependencies:
+    once "^1.4.0"
+
+expand-template@^2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
+  integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
+
[email protected]:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
+  integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
+
+fs-constants@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
+  integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
+
+fs-extra@^11.3.0:
+  version "11.3.0"
+  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d"
+  integrity sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==
+  dependencies:
+    graceful-fs "^4.2.0"
+    jsonfile "^6.0.1"
+    universalify "^2.0.0"
+
[email protected]:
+  version "0.0.0"
+  resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
+  integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==
+
+graceful-fs@^4.1.6, graceful-fs@^4.2.0:
+  version "4.2.11"
+  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
+  integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
+
+ieee754@^1.1.13:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
+  integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
+
+import-meta-resolve@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#f9db8bead9fafa61adb811db77a2bf22c5399706"
+  integrity sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==
+
+inherits@^2.0.3, inherits@^2.0.4:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+ini@~1.3.0:
+  version "1.3.8"
+  resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
+  integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
+
+jsonfile@^6.0.1:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
+  integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
+  dependencies:
+    universalify "^2.0.0"
+  optionalDependencies:
+    graceful-fs "^4.1.6"
+
+mimic-response@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
+  integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
+
+minimist@^1.2.0, minimist@^1.2.3:
+  version "1.2.8"
+  resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
+  integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
+
+mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
+  version "0.5.3"
+  resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
+  integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
+
+napi-build-utils@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e"
+  integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==
+
+node-abi@^3.3.0:
+  version "3.75.0"
+  resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.75.0.tgz#2f929a91a90a0d02b325c43731314802357ed764"
+  integrity sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==
+  dependencies:
+    semver "^7.3.5"
+
+once@^1.3.1, once@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+  integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
+  dependencies:
+    wrappy "1"
+
+prebuild-install@^7.1.1:
+  version "7.1.3"
+  resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec"
+  integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==
+  dependencies:
+    detect-libc "^2.0.0"
+    expand-template "^2.0.3"
+    github-from-package "0.0.0"
+    minimist "^1.2.3"
+    mkdirp-classic "^0.5.3"
+    napi-build-utils "^2.0.0"
+    node-abi "^3.3.0"
+    pump "^3.0.0"
+    rc "^1.2.7"
+    simple-get "^4.0.0"
+    tar-fs "^2.0.0"
+    tunnel-agent "^0.6.0"
+
+pump@^3.0.0:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.3.tgz#151d979f1a29668dc0025ec589a455b53282268d"
+  integrity sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==
+  dependencies:
+    end-of-stream "^1.1.0"
+    once "^1.3.1"
+
+rc@^1.2.7:
+  version "1.2.8"
+  resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
+  integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
+  dependencies:
+    deep-extend "^0.6.0"
+    ini "~1.3.0"
+    minimist "^1.2.0"
+    strip-json-comments "~2.0.1"
+
+readable-stream@^3.1.1, readable-stream@^3.4.0:
+  version "3.6.2"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
+  integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
+  dependencies:
+    inherits "^2.0.3"
+    string_decoder "^1.1.1"
+    util-deprecate "^1.0.1"
+
+safe-buffer@^5.0.1, safe-buffer@~5.2.0:
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+  integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
+semver@^7.3.5:
+  version "7.7.2"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58"
+  integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
+
+simple-concat@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
+  integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
+
+simple-get@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543"
+  integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==
+  dependencies:
+    decompress-response "^6.0.0"
+    once "^1.3.1"
+    simple-concat "^1.0.0"
+
+string_decoder@^1.1.1:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
+  integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
+  dependencies:
+    safe-buffer "~5.2.0"
+
+strip-json-comments@~2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+  integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==
+
+tar-fs@^2.0.0:
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.3.tgz#fb3b8843a26b6f13a08e606f7922875eb1fbbf92"
+  integrity sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==
+  dependencies:
+    chownr "^1.1.1"
+    mkdirp-classic "^0.5.2"
+    pump "^3.0.0"
+    tar-stream "^2.1.4"
+
+tar-stream@^2.1.4:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
+  integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
+  dependencies:
+    bl "^4.0.3"
+    end-of-stream "^1.4.1"
+    fs-constants "^1.0.0"
+    inherits "^2.0.3"
+    readable-stream "^3.1.1"
+
+tunnel-agent@^0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
+  integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==
+  dependencies:
+    safe-buffer "^5.0.1"
+
+universalify@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
+  integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
+
+util-deprecate@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+  integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
+
+wrappy@1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+  integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==

+ 4 - 0
deps/common/src/logseq/common/config.cljs

@@ -30,7 +30,11 @@
 
 (defonce asset-protocol "assets://")
 
+(defonce db-version-prefix "logseq_db_")
+(defonce file-version-prefix "logseq_local_")
+
 (defonce local-assets-dir "assets")
+(defonce unlinked-graphs-dir "Unlinked graphs")
 
 (defonce favorites-page-name "$$$favorites")
 (defonce views-page-name "$$$views")

+ 4 - 5
deps/db/src/logseq/db/common/sqlite.cljs

@@ -4,7 +4,8 @@
   (:require ["path" :as node-path]
             [clojure.string :as string]
             [datascript.core :as d]
-            [logseq.db.sqlite.util :as sqlite-util]))
+            [logseq.db.sqlite.util :as sqlite-util]
+            [logseq.common.config :as common-config]))
 
 (defn create-kvs-table!
   "Creates a sqlite table for use with datascript.storage if one doesn't exist"
@@ -17,16 +18,14 @@
   (or (d/restore-conn storage)
       (d/create-conn schema {:storage storage})))
 
-(defonce file-version-prefix "logseq_local_")
-
 (defn local-file-based-graph?
   [s]
   (and (string? s)
-       (string/starts-with? s file-version-prefix)))
+       (string/starts-with? s common-config/file-version-prefix)))
 
 (defn sanitize-db-name
   [db-name]
-  (if (string/starts-with? db-name file-version-prefix)
+  (if (string/starts-with? db-name common-config/file-version-prefix)
     (-> db-name
         (string/replace ":" "+3A+")
         (string/replace "/" "++"))

+ 1 - 0
deps/db/src/logseq/db/common/sqlite_cli.cljs

@@ -112,4 +112,5 @@
                              ;; $ORIGINAL_PWD used by bb tasks to correct current dir
                                (node-path/join (or js/process.env.ORIGINAL_PWD ".") %))]
         ((juxt node-path/dirname node-path/basename) (resolve-path' graph-dir-or-path)))
+      ;; TODO: Reuse with get-db-graphs-dir when there is a db ns that is usable by electron i.e. no better-sqlite3
       [(node-path/join (os/homedir) "logseq" "graphs") graph-dir-or-path])))

+ 1 - 1
deps/db/src/logseq/db/sqlite/util.cljs

@@ -13,7 +13,7 @@
             [logseq.db.frontend.property :as db-property]
             [logseq.db.frontend.property.type :as db-property-type]))
 
-(defonce db-version-prefix "logseq_db_")
+(defonce db-version-prefix common-config/db-version-prefix)
 
 (def ^:private write-handlers (cljs-bean.transit/writer-handlers))
 (def ^:private read-handlers {})

+ 1 - 1
scripts/src/logseq/tasks/dev/lint.clj

@@ -24,7 +24,7 @@
 (defn kondo-git-changes
   "Run clj-kondo across dirs and only for files that git diff detects as unstaged changes"
   []
-  (let [kondo-dirs ["src" "deps/common" "deps/db" "deps/graph-parser" "deps/outliner" "deps/publishing"]
+  (let [kondo-dirs ["src" "deps/common" "deps/db" "deps/graph-parser" "deps/outliner" "deps/publishing" "deps/cli"]
         dir-regex (re-pattern (str "^(" (string/join "|" kondo-dirs) ")"))
         dir-to-files (->> (shell {:out :string} "git diff --name-only")
                           :out

+ 9 - 15
src/electron/electron/db.cljs

@@ -1,45 +1,39 @@
 (ns electron.db
   "Provides SQLite dbs for electron and manages files of those dbs"
-  (:require ["electron" :refer [app]]
-            ["fs-extra" :as fs]
+  (:require ["fs-extra" :as fs]
             ["path" :as node-path]
+            [logseq.cli.common.graph :as cli-common-graph]
+            [logseq.common.config :as common-config]
             [logseq.db.common.sqlite :as common-sqlite]))
 
-(defn get-graphs-dir
-  []
-  (let [path (.getPath ^object app "home")]
-    (node-path/join path "logseq" "graphs")))
-
 (defn ensure-graphs-dir!
   []
-  (fs/ensureDirSync (get-graphs-dir)))
+  (fs/ensureDirSync (cli-common-graph/get-db-graphs-dir)))
 
 (defn ensure-graph-dir!
   [db-name]
   (ensure-graphs-dir!)
-  (let [graph-dir (node-path/join (get-graphs-dir) (common-sqlite/sanitize-db-name db-name))]
+  (let [graph-dir (node-path/join (cli-common-graph/get-db-graphs-dir) (common-sqlite/sanitize-db-name db-name))]
     (fs/ensureDirSync graph-dir)
     graph-dir))
 
 (defn save-db!
   [db-name data]
-  (let [[_db-name db-path] (common-sqlite/get-db-full-path (get-graphs-dir) db-name)]
+  (let [[_db-name db-path] (common-sqlite/get-db-full-path (cli-common-graph/get-db-graphs-dir) db-name)]
     (fs/writeFileSync db-path data)))
 
 (defn get-db
   [db-name]
   (let [_ (ensure-graph-dir! db-name)
-        [_db-name db-path] (common-sqlite/get-db-full-path (get-graphs-dir) db-name)]
+        [_db-name db-path] (common-sqlite/get-db-full-path (cli-common-graph/get-db-graphs-dir) db-name)]
     (when (fs/existsSync db-path)
       (fs/readFileSync db-path))))
 
-(def unlinked-graphs-dir "Unlinked graphs")
-
 (defn unlink-graph!
   [repo]
   (let [db-name (common-sqlite/sanitize-db-name repo)
-        path (node-path/join (get-graphs-dir) db-name)
-        unlinked (node-path/join (get-graphs-dir) unlinked-graphs-dir)
+        path (node-path/join (cli-common-graph/get-db-graphs-dir) db-name)
+        unlinked (node-path/join (cli-common-graph/get-db-graphs-dir) common-config/unlinked-graphs-dir)
         new-path (node-path/join unlinked db-name)
         new-path-exists? (fs/existsSync new-path)
         new-path' (if new-path-exists?

+ 11 - 35
src/electron/electron/handler.cljs

@@ -30,6 +30,7 @@
             [electron.utils :as utils]
             [electron.window :as win]
             [goog.functions :refer [debounce]]
+            [logseq.cli.common.graph :as cli-common-graph]
             [logseq.common.graph :as common-graph]
             [logseq.db :as ldb]
             [logseq.db.common.sqlite :as common-sqlite]
@@ -204,51 +205,26 @@
     (bean/->js {:path path
                 :files files})))
 
-(defn- graph-name->path
-  [graph-name]
-  (when graph-name
-    (-> graph-name
-        (string/replace "+3A+" ":")
-        (string/replace "++" "/"))))
-
-(defn- get-graphs-dir
+(defn- get-file-graphs-dir
+  "Get cache directory for file graphs"
   []
-  (let [dir (if utils/ci?
-              (.resolve node-path js/__dirname "../tmp/graphs")
-              (.join node-path (.homedir os) ".logseq" "graphs"))]
+  (let [dir (node-path/join (os/homedir) ".logseq" "graphs")]
     (fs-extra/ensureDirSync dir)
     dir))
 
-(defn- get-db-based-graphs-dir
-  []
-  (let [dir (.join node-path (.homedir os) "logseq" "graphs")]
-    (fs-extra/ensureDirSync dir)
-    dir))
-
-(defn- get-file-based-graphs
+(defn get-file-based-graphs
   "Returns all graph names in the cache directory (starting with `logseq_local_`)"
   []
-  (let [dir (get-graphs-dir)]
+  (let [dir (get-file-graphs-dir)]
     (->> (common-graph/readdir dir)
          (remove #{dir})
          (map #(node-path/basename % ".transit"))
-         (map graph-name->path))))
+         (map cli-common-graph/graph-name->path))))
 
-(defn- get-db-based-graphs
-  []
-  (let [dir (get-db-based-graphs-dir)]
-    (->> (common-graph/read-directories dir)
-         (remove (fn [s] (= s db/unlinked-graphs-dir)))
-         (map graph-name->path)
-         (map (fn [s]
-                (if (string/starts-with? s common-sqlite/file-version-prefix)
-                  s
-                  (str sqlite-util/db-version-prefix s)))))))
-
-(defn- get-graphs
+(defn get-graphs
   "Returns all graph names"
   []
-  (let [db-graphs (get-db-based-graphs)
+  (let [db-graphs (cli-common-graph/get-db-based-graphs)
         file-graphs (get-file-based-graphs)]
     (distinct (concat db-graphs file-graphs))))
 
@@ -297,7 +273,7 @@
 (defmethod handle :deleteGraph [_window [_ graph graph-name _db-based?]]
   (when graph-name
     (db/unlink-graph! graph)
-    (let [old-transit-path (node-path/join (get-graphs-dir) (str (common-sqlite/sanitize-db-name graph) ".transit"))]
+    (let [old-transit-path (node-path/join (get-file-graphs-dir) (str (common-sqlite/sanitize-db-name graph) ".transit"))]
       (when (fs/existsSync old-transit-path)
         (fs/unlinkSync old-transit-path)))))
 
@@ -314,7 +290,7 @@
 
 (defn clear-cache!
   [window]
-  (let [graphs-dir (get-graphs-dir)]
+  (let [graphs-dir (get-file-graphs-dir)]
     (fs-extra/removeSync graphs-dir))
 
   (let [path (.getPath ^object app "userData")]

+ 3 - 8
src/electron/electron/utils.cljs

@@ -6,9 +6,9 @@
             [cljs-bean.core :as bean]
             [clojure.string :as string]
             [electron.configs :as cfgs]
-            [electron.db :as db]
             [electron.logger :as logger]
             [logseq.db.sqlite.util :as sqlite-util]
+            [logseq.cli.common.graph :as cli-common-graph]
             [promesa.core :as p]))
 
 (defonce *win (atom nil)) ;; The main window
@@ -19,11 +19,6 @@
 
 (defonce prod? (= js/process.env.NODE_ENV "production"))
 
-;; Under e2e testing?
-(defonce ci? (let [v js/process.env.CI]
-               (or (true? v)
-                   (= v "true"))))
-
 (defonce dev? (not prod?))
 (defonce *fetchAgent (atom nil))
 
@@ -260,7 +255,7 @@
   "required by all internal state in the electron section"
   [graph-name]
   (cond (string/starts-with? graph-name sqlite-util/db-version-prefix)
-        (node-path/join (db/get-graphs-dir) (string/replace-first graph-name sqlite-util/db-version-prefix ""))
+        (node-path/join (cli-common-graph/get-db-graphs-dir) (string/replace-first graph-name sqlite-util/db-version-prefix ""))
         (string/includes? graph-name "logseq_local_")
         (string/replace-first graph-name "logseq_local_" "")))
 
@@ -268,7 +263,7 @@
   (defn get-graph-name
     "Reverse `get-graph-dir`"
     [graph-dir]
-    (if (= (db/get-graphs-dir) (node-path/dirname graph-dir))
+    (if (= (cli-common-graph/get-db-graphs-dir) (node-path/dirname graph-dir))
       (str sqlite-util/db-version-prefix (node-path/basename graph-dir))
       (str "logseq_local_" graph-dir))))
 

+ 1 - 1
src/main/frontend/config.cljs

@@ -343,7 +343,7 @@
 (defonce idb-db-prefix "logseq-db/")
 (defonce local-db-prefix "logseq_local_")
 (defonce local-handle "handle")
-(defonce db-version-prefix sqlite-util/db-version-prefix)
+(defonce db-version-prefix common-config/db-version-prefix)
 
 (defn db-graph-name
   [repo-with-prefix]

+ 3 - 20
src/main/frontend/util/text.cljs

@@ -5,7 +5,8 @@
             [frontend.config :as config]
             [frontend.util :as util]
             [goog.string :as gstring]
-            [logseq.common.path :as path]))
+            [logseq.common.path :as path]
+            [logseq.cli.text-util :as cli-text-util]))
 
 (defonce between-re #"\(between ([^\)]+)\)")
 
@@ -121,25 +122,7 @@
              []
              ks))))
 
-(defn cut-by
-  "Cut string by specified wrapping symbols, only match the first occurrence.
-     value - string to cut
-     before - cutting symbol (before)
-     end - cutting symbol (end)"
-  [value before end]
-  (let [b-pos (string/index-of value before)
-        b-len (count before)]
-    (if b-pos
-      (let [b-cut (subs value 0 b-pos)
-            m-cut (subs value (+ b-pos b-len))
-            e-len (count end)
-            e-pos (string/index-of m-cut end)]
-        (if e-pos
-          (let [e-cut (subs m-cut (+ e-pos e-len))
-                m-cut (subs m-cut 0 e-pos)]
-            [b-cut m-cut e-cut])
-          [b-cut m-cut nil]))
-      [value nil nil])))
+(def cut-by cli-text-util/cut-by)
 
 (defn get-graph-name-from-path
   "Get `Dir/GraphName` style name for from repo-url.

+ 2 - 2
src/main/logseq/api.cljs

@@ -1233,8 +1233,8 @@
 
 ;; search
 (defn ^:export search
-  [q']
-  (-> (search-handler/search q')
+  [q' & [opts]]
+  (-> (search-handler/search (state/get-current-repo) q' (if opts (js->clj opts :keywordize-keys true) {}))
       (p/then #(bean/->js %))))
 
 ;; helpers

+ 1 - 34
src/test/frontend/util/text_test.cljs

@@ -53,37 +53,4 @@
 
     '(false false false false false false true true true true true true)
     (map #(text-util/wrapped-by? "prop::value" % "::" "") (take 12 (range)))
-    ))
-
-
-(deftest test-cut-by
-  (are [x y] (= x y)
-    ["" "" ""]
-    (text-util/cut-by "[[]]" "[[" "]]")
-
-    ["" "abc" ""]
-    (text-util/cut-by "[[abc]]" "[[" "]]")
-
-    ["012 " "6" " [[2]]"]
-    (text-util/cut-by "012 [[6]] [[2]]" "[[" "]]")
-
-    ["" "prop" "value"]
-    (text-util/cut-by "prop::value" "" "::")
-
-    ["prop" "" "value"]
-    (text-util/cut-by "prop::value" "::" "")
-
-    ["some " "content" " here"]
-    (text-util/cut-by "some $pfts>$content$pfts<$ here" "$pfts>$" "$pfts<$")
-
-    ["some " "content$pft" nil]
-    (text-util/cut-by "some $pfts>$content$pft" "$pfts>$" "$pfts<$")
-
-    ["some $pf" nil nil]
-    (text-util/cut-by "some $pf" "$pfts>$" "$pfts<$")
-
-    ["" "content" ""]
-    (text-util/cut-by "$pfts>$content$pfts<$" "$pfts>$" "$pfts<$")
-
-    ["" "content$p" nil]
-    (text-util/cut-by "$pfts>$content$p" "$pfts>$" "$pfts<$")))
+    ))