Browse Source

enhance(cli): Add import-edn command

Works for both current and local graphs. Addresses CLI and API for
https://discord.com/channels/725182569297215569/1365819617079066744/1365819617079066744
Gabriel Horner 4 months ago
parent
commit
0bc0892b8a

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

@@ -7,3 +7,4 @@ logseq.cli.commands.search/search
 logseq.cli.commands.export/export
 logseq.cli.commands.append/append
 logseq.cli.commands.mcp-server/start
+logseq.cli.commands.import-edn/import-edn

+ 7 - 1
deps/cli/README.md

@@ -31,6 +31,8 @@ query [options]      Query DB graph(s)
 export [options]     Export DB graph as Markdown
 export-edn [options] Export DB graph as EDN
 append [options]     Appends text to current page
+mcp-server [options] Run a MCP server
+import-edn [options] Import into DB graph with EDN
 help                 Print a command's help
 
 $ logseq list
@@ -120,7 +122,11 @@ Exported 41 pages to yep_markdown_1756128259.zip
 
 # Export DB graph as EDN
 $ logseq export-edn woot -f woot.edn
-Exported 16 properties, 16 classes and 36 pages
+Exported 16 properties, 1 classes and 36 pages to woot.edn
+
+# Import into current graph with EDN
+$ logseq import-edn -f woot-ontology.edn
+Imported 16 properties, 1 classes and 0 pages!
 
 # Append text to current page
 $ logseq append add this text -a my-token

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

@@ -103,6 +103,10 @@
     :description "Run a MCP server against a local graph if --graph is given or against the current in-app graph. By default the MCP server runs as a HTTP Streamable server. Use --stdio to run it as a stdio server."
     :fn (lazy-load-fn 'logseq.cli.commands.mcp-server/start)
     :spec cli-spec/mcp-server}
+   {:cmds ["import-edn"] :desc "Import into DB graph with EDN"
+    :description "Import with EDN into a local graph or the current in-app graph if --api-server-token is given. See https://github.com/logseq/docs/blob/master/db-version.md#edn-data-export for more about this import type."
+    :fn (lazy-load-fn 'logseq.cli.commands.import-edn/import-edn)
+    :spec cli-spec/import-edn}
    {:cmds ["help"] :fn help-command :desc "Print a command's help"
     :args->opts [:command] :require [:command]}
    {:cmds []

+ 38 - 0
deps/cli/src/logseq/cli/commands/import_edn.cljs

@@ -0,0 +1,38 @@
+(ns logseq.cli.commands.import-edn
+  "Import edn command"
+  (:require ["fs" :as fs]
+            [clojure.edn :as edn]
+            [logseq.cli.util :as cli-util]
+            [logseq.db :as ldb]
+            [logseq.db.common.sqlite-cli :as sqlite-cli]
+            [logseq.db.sqlite.export :as sqlite-export]
+            [logseq.db.sqlite.util :as sqlite-util]
+            [promesa.core :as p]))
+
+(defn- print-success [import-map]
+  (println "Imported" (count (:properties import-map)) "properties,"
+           (count (:classes import-map)) "classes and"
+           (count (:pages-and-blocks import-map)) "pages!"))
+
+(defn- api-import [api-server-token import-map]
+  (-> (p/let [resp (cli-util/api-fetch api-server-token "logseq.cli.import_edn" [(sqlite-util/transit-write import-map)])]
+        (if (= 200 (.-status resp))
+          (print-success import-map)
+          (cli-util/api-handle-error-response resp)))
+      (p/catch cli-util/command-catch-handler)))
+
+(defn- local-import [{:keys [graph]} import-map]
+  (if (and graph (fs/existsSync (cli-util/get-graph-path graph)))
+    (let [conn (apply sqlite-cli/open-db! (cli-util/->open-db-args graph))
+          {:keys [init-tx block-props-tx misc-tx]}
+          (sqlite-export/build-import import-map @conn {})
+          txs (vec (concat init-tx block-props-tx misc-tx))]
+      (ldb/transact! conn txs)
+      (print-success import-map))
+    (cli-util/error "Graph" (pr-str graph) "does not exist")))
+
+(defn import-edn [{{:keys [api-server-token file] :as opts} :opts}]
+  (let [edn (edn/read-string (str (fs/readFileSync file)))]
+    (if api-server-token
+      (api-import api-server-token edn)
+      (local-import opts edn))))

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

@@ -25,6 +25,15 @@
                  :desc "Export type"
                  :default :graph}})
 
+(def import-edn
+  {:api-server-token {:alias :a
+                      :desc "API server token to query current graph"}
+   :graph {:alias :g
+           :desc "Local graph to import into"}
+   :file {:alias :f
+          :require true
+          :desc "EDN File to import"}})
+
 (def query
   {:graphs {:alias :g
             :coerce []

+ 2 - 1
src/electron/electron/server.cljs

@@ -12,7 +12,8 @@
             [electron.utils :as utils]
             [electron.window :as window]
             [logseq.cli.common.mcp.server :as cli-common-mcp-server]
-            [promesa.core :as p]))
+            [promesa.core :as p]
+            [logseq.db.sqlite.util :as sqlite-util]))
 
 (defonce ^:private *win (atom nil))
 (defonce ^:private *server (atom nil))

+ 1 - 0
src/main/logseq/api.cljs

@@ -212,6 +212,7 @@
 (def ^:export list_pages cli-based-api/list-pages)
 (def ^:export get_page_data cli-based-api/get-page-data)
 (def ^:export upsert_nodes cli-based-api/upsert-nodes)
+(def ^:export import_edn cli-based-api/import-edn)
 
 ;; file based graph APIs
 (def ^:export get_current_graph_templates file-based-api/get_current_graph_templates)

+ 13 - 2
src/main/logseq/api/db_based/cli.cljs

@@ -5,7 +5,8 @@
             [frontend.modules.outliner.ui :as ui-outliner-tx]
             [frontend.state :as state]
             [logseq.cli.common.mcp.tools :as cli-common-mcp-tools]
-            [promesa.core :as p]))
+            [promesa.core :as p]
+            [logseq.db.sqlite.util :as sqlite-util]))
 
 (defn list-tags
   [options]
@@ -48,4 +49,14 @@
                              (outliner-op/batch-import-edn! edn-data {})))]
     (when error (throw (ex-info error {})))
     (ui-handler/re-render-root!)
-    (cli-common-mcp-tools/summarize-upsert-operations ops options)))
+    (cli-common-mcp-tools/summarize-upsert-operations ops options)))
+
+(defn import-edn
+  "Given EDN data as a transitized string, converts to EDN and imports it."
+  [edn-data*]
+  (p/let [edn-data (sqlite-util/transit-read edn-data*)
+          {:keys [error]} (ui-outliner-tx/transact!
+                           {:outliner-op :batch-import-edn}
+                           (outliner-op/batch-import-edn! edn-data {}))]
+    (when error (throw (ex-info error {})))
+    (ui-handler/re-render-root!)))