diff --git a/deps/cli/.carve/ignore b/deps/cli/.carve/ignore index d8fb91dc85..68eb86ac6b 100644 --- a/deps/cli/.carve/ignore +++ b/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 diff --git a/deps/cli/README.md b/deps/cli/README.md index 2170b69458..dc4f57200a 100644 --- a/deps/cli/README.md +++ b/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 diff --git a/deps/cli/src/logseq/cli.cljs b/deps/cli/src/logseq/cli.cljs index 218dac2f0d..0b6686761b 100644 --- a/deps/cli/src/logseq/cli.cljs +++ b/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 [] diff --git a/deps/cli/src/logseq/cli/commands/import_edn.cljs b/deps/cli/src/logseq/cli/commands/import_edn.cljs new file mode 100644 index 0000000000..e256f1cd04 --- /dev/null +++ b/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)))) \ No newline at end of file diff --git a/deps/cli/src/logseq/cli/spec.cljs b/deps/cli/src/logseq/cli/spec.cljs index fdb5717e96..65aae77315 100644 --- a/deps/cli/src/logseq/cli/spec.cljs +++ b/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 [] diff --git a/src/electron/electron/server.cljs b/src/electron/electron/server.cljs index 1e5c89bc29..8d67a205a2 100644 --- a/src/electron/electron/server.cljs +++ b/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)) diff --git a/src/main/logseq/api.cljs b/src/main/logseq/api.cljs index 67ac3c6979..6610dd28f9 100644 --- a/src/main/logseq/api.cljs +++ b/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) diff --git a/src/main/logseq/api/db_based/cli.cljs b/src/main/logseq/api/db_based/cli.cljs index 9e3b00c20d..a368af07c4 100644 --- a/src/main/logseq/api/db_based/cli.cljs +++ b/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))) \ No newline at end of file + (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!))) \ No newline at end of file