From 48c1f5374eba3700eb62a9e68f227e482e48944f Mon Sep 17 00:00:00 2001 From: rcmerci Date: Thu, 26 Feb 2026 21:37:45 +0800 Subject: [PATCH] 041-logseq-cli-add-block-json-identifiers.md --- ...1-logseq-cli-add-block-json-identifiers.md | 171 ++++++++++++++++++ docs/cli/logseq-cli.md | 13 ++ src/main/logseq/cli/command/add.cljs | 148 ++++++++++++--- src/main/logseq/cli/format.cljs | 12 +- src/test/logseq/cli/command/add_test.cljs | 45 +++++ src/test/logseq/cli/format_test.cljs | 15 +- src/test/logseq/cli/integration_test.cljs | 160 ++++++++++++++++ 7 files changed, 531 insertions(+), 33 deletions(-) create mode 100644 docs/agent-guide/041-logseq-cli-add-block-json-identifiers.md create mode 100644 src/test/logseq/cli/command/add_test.cljs diff --git a/docs/agent-guide/041-logseq-cli-add-block-json-identifiers.md b/docs/agent-guide/041-logseq-cli-add-block-json-identifiers.md new file mode 100644 index 0000000000..c3b0e28b86 --- /dev/null +++ b/docs/agent-guide/041-logseq-cli-add-block-json-identifiers.md @@ -0,0 +1,171 @@ +# Logseq CLI Add Command Entity Id Output Implementation Plan + +Goal: Make `add page` and `add block` return newly created entity `db/id` in `--output human`, `--output json`, and `--output edn`. + +Architecture: Keep existing add command write paths and add a post-write identifier resolution step in the CLI command layer. +Architecture: Return a unified structured payload for both add commands where `:result` is a vector of created entity `db/id`. +Architecture: Update human formatter for add commands to include created entity ids while preserving existing success semantics. + +Tech Stack: ClojureScript, Logseq CLI command layer, db-worker-node thread API, Logseq CLI formatter. + +Related: Builds on docs/agent-guide/027-logseq-cli-update-command.md and docs/agent-guide/005-logseq-cli-output-and-db-worker-node-log.md. + +Document naming follows @planning-documents using the next available sequence number `041`. + +## Problem statement + +Current behavior for `add block --output json` is `{"status":"ok","data":{"result":null}}` because `/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/add.cljs` sets `:result nil` in `execute-add-block`. + +Current add command outputs are not consistent for machine and human flows when users need the new entity id immediately. + +Users now require `db/id` in all output formats for both `add page` and `add block`. + +Without this output, automation must run extra commands to locate newly created entities before `update` or `remove`. + +## Testing Plan + +I will follow @test-driven-development and complete all RED tests before implementation. + +I will add integration tests in `/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/integration_test.cljs` for `add page --output json` and `add block --output json` asserting returned `db/id`. + +I will add integration tests in `/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/integration_test.cljs` for `add page --output edn` and `add block --output edn` asserting returned `:db/id`. + +I will add formatter tests in `/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/format_test.cljs` asserting human output lines for add page and add block include new ids. + +I will add one integration chain test in `/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/integration_test.cljs` that uses returned add ids directly in `update` and `remove` commands. + +I will add a focused unit test namespace at `/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/command/add_test.cljs` for helper logic that builds the created-entity result payload. + +NOTE: I will write *all* tests before I add any implementation behavior. + +## Architecture sketch + +``` +add page/add block + -> /src/main/logseq/cli/command/add.cljs execute-add-* + -> thread-api write call + -> resolve created entity ids in CLI command layer + -> result payload {:result [id1 id2 ...]} + -> /src/main/logseq/cli/format.cljs human renderer includes id information +``` + +## Output contract + +JSON output for add page returns created page id vector in `data.result`. + +JSON output for add block returns created block ids in `data.result`. + +EDN output mirrors the same data shape using keyword keys. + +Human output includes created ids for both commands. + +Example human output for add page is: + +```text +Added page: +[123] +``` + +Example human output for add block is: + +```text +Added blocks: +[101 102] +``` + +## Plan + +1. Read `/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/add.cljs` and confirm current add page and add block result payload shapes. +2. Read `/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/format.cljs` and confirm current human formatter paths for add commands. +3. Write RED integration test A in `/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/integration_test.cljs` for add page JSON result containing created page `db/id`. +4. Write RED integration test B in `/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/integration_test.cljs` for add block JSON result containing created block `db/id` list. +5. Write RED integration test C in `/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/integration_test.cljs` for add page and add block EDN output containing `:db/id`. +6. Write RED formatter tests in `/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/format_test.cljs` asserting human add output includes ids. +7. Write RED unit tests in `/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/command/add_test.cljs` for id vector normalization and deterministic ordering. +8. Run focused tests and verify failures are caused by missing behavior and not incorrect test setup. +9. Implement add block result enrichment in `/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/add.cljs` so it returns all created block ids including nested children. +10. Implement add page result enrichment in `/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/add.cljs` so it returns created page id as a one-element vector. +11. Keep result field naming stable with a shape `:data {:result [101 102]}`. +12. Update `/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/format.cljs` add page and add block human renderers to include id data from command result. +13. Ensure JSON and EDN formatter paths continue to serialize the enriched result without special-case regressions. +14. Update `/Users/rcmerci/gh-repos/logseq/docs/cli/logseq-cli.md` with add page and add block structured output examples that include `db/id`. +15. Run focused tests again and verify GREEN behavior for all newly added tests. +16. Refactor helper naming and duplicate mapping code in add command execution if needed without behavior change. +17. Re-run focused tests after refactor to verify tests stay green. +18. Run `bb dev:lint-and-test` for regression verification. + +## Edge cases + +Add block from `--content` must still return generated block id when UUID was not supplied in input. + +Add block from `--blocks` with multiple nested blocks must return all created ids and use deterministic ordering in returned payload. + +Add page should return the created page id even when tags and properties are added in the same command. + +Add block human output should remain readable when returning many ids including nested children. + +When id resolution fails unexpectedly after a successful write, command should return an error rather than a misleading success payload without ids. + +Error output format and exit codes must remain unchanged for invalid input and missing target cases. + +## Testing commands and expected output + +Run focused RED tests. + +```bash +bb dev:test -v 'logseq.cli.integration-test/test-cli-add-page-json-output-returns-id' +bb dev:test -v 'logseq.cli.integration-test/test-cli-add-block-json-output-returns-ids' +bb dev:test -v 'logseq.cli.integration-test/test-cli-add-page-block-edn-output-returns-id' +bb dev:test -v 'logseq.cli.format-test/test-human-output-add-remove' +``` + +Expected RED output includes assertion failures indicating missing id fields in add outputs. + +Run focused GREEN tests after implementation. + +```bash +bb dev:test -v 'logseq.cli.integration-test/test-cli-add-page-json-output-returns-id' +bb dev:test -v 'logseq.cli.integration-test/test-cli-add-block-json-output-returns-ids' +bb dev:test -v 'logseq.cli.integration-test/test-cli-add-page-block-edn-output-returns-id' +bb dev:test -v 'logseq.cli.integration-test/test-cli-add-identifiers-chain-update-remove' +bb dev:test -v 'logseq.cli.command.add-test' +bb dev:test -v 'logseq.cli.format-test/test-human-output-add-remove' +``` + +Expected GREEN output includes zero failures and zero errors for the focused namespaces. + +Run full verification. + +```bash +bb dev:lint-and-test +``` + +Expected output includes successful lint and tests with exit code `0`. + +## Testing Details + +Integration tests will assert actual CLI command output payloads and real persisted graph behavior for add page and add block. + +Formatter tests will assert exact human output strings for add page and add block including id fragments. + +Unit tests will validate helper behavior for id list assembly and ordering. + +## Implementation Details + +- Enrich `execute-add-block` result so `:result` is a vector of all created block ids including nested children. +- Enrich `execute-add-page` result so `:result` is a one-element vector containing created page id. +- Keep result payload stable across JSON and EDN output paths by using Clojure maps with keyword keys. +- Update human formatters for add page and add block to include id information: + - add page rendered as `Added page:` on one line and `[123]` on the next line. + - add block rendered as `Added blocks:` on one line and `[101 102]` on the next line. +- Preserve existing command success and error semantics besides the new id output fields. +- Add integration coverage for add outputs plus id-based `update` and `remove` chaining. +- Update CLI docs to show the new add page and add block result examples with ids. + +## Decisions + +Human output for add block must display all created ids including nested children. + +For add block and add page, `:result` must be an id vector such as `[101 102]`. + +--- diff --git a/docs/cli/logseq-cli.md b/docs/cli/logseq-cli.md index 07b0a2e5c6..41cfe0ba11 100644 --- a/docs/cli/logseq-cli.md +++ b/docs/cli/logseq-cli.md @@ -138,6 +138,19 @@ Output formats: - Global `--output ` applies to all commands - For `graph export`, `--output` refers to the destination file path. Output formatting is controlled via `:output-format` in config or `LOGSEQ_CLI_OUTPUT`. - Human output is plain text. List/search commands render tables with a final `Count: N` line. For list and search subcommands, the ID column uses `:db/id` (not UUID). If `:db/ident` exists, an `IDENT` column is included. Search table columns are `ID` and `TITLE`. Block titles can include multiple lines; multi-line rows align additional lines under the `TITLE` column. Times such as list `UPDATED-AT`/`CREATED-AT` and `graph info` `Created at` are shown in human-friendly relative form. Errors include error codes and may include a `Hint:` line. Use `--output json|edn` for structured output. +- `add page` and `add block` return created entity ids in `data.result` for JSON/EDN output, and include ids in human output. + - Human example: + ```text + Added page: + [123] + ``` + - Human example: + ```text + Added blocks: + [201 202] + ``` + - JSON example: `{"status":"ok","data":{"result":[123]}}` + - EDN example: `{:status :ok, :data {:result [123]}}` - `doctor` output includes overall status (`ok`, `warning`, `error`) and per-check rows for `db-worker-script`, `data-dir`, and `running-servers`. For scripting, `--output json|edn` keeps the structured check payload. - Common doctor failures: - `doctor-script-missing`: `db-worker-node.js` runtime target is missing (default target: `dist/db-worker-node.js`; use `doctor --dev-script` to check `static/db-worker-node.js`). diff --git a/src/main/logseq/cli/command/add.cljs b/src/main/logseq/cli/command/add.cljs index 6e89244c2f..90c858923b 100644 --- a/src/main/logseq/cli/command/add.cljs +++ b/src/main/logseq/cli/command/add.cljs @@ -99,19 +99,119 @@ (defn- ensure-block-uuids [blocks] - (mapv (fn [block] - (let [current (:block/uuid block)] - (cond - (some? current) - (update block :block/uuid (fn [value] - (if (and (string? value) (common-util/uuid-string? value)) - (uuid value) - value))) + (mapv (fn ensure-block-uuid [block] + (let [current (:block/uuid block) + block (cond + (some? current) + (update block :block/uuid (fn [value] + (if (and (string? value) (common-util/uuid-string? value)) + (uuid value) + value))) - :else - (assoc block :block/uuid (common-uuid/gen-uuid))))) + :else + (assoc block :block/uuid (common-uuid/gen-uuid)))] + (if (seq (:block/children block)) + (update block :block/children ensure-block-uuids) + block))) blocks)) +(defn- normalize-created-ids + [ids] + (->> ids + (remove nil?) + distinct + vec)) + +(defn- normalized-uuid + [value] + (cond + (uuid? value) value + (and (string? value) (common-util/uuid-string? (string/trim value))) + (uuid (string/trim value)) + :else nil)) + +(defn- collect-created-block-uuids + [blocks] + (letfn [(walk [acc block] + (let [block-uuid (normalized-uuid (:block/uuid block)) + acc (if block-uuid + (conj acc block-uuid) + acc) + children (:block/children block)] + (if (seq children) + (reduce walk acc children) + acc)))] + (->> (reduce walk [] blocks) + distinct + vec))) + +(defn- flatten-block-tree + [blocks] + (letfn [(walk [parent-uuid block] + (let [children (:block/children block) + block (cond-> (dissoc block :block/children) + parent-uuid + (assoc :block/parent [:block/uuid parent-uuid])) + block-uuid (normalized-uuid (:block/uuid block))] + (into [block] + (mapcat #(walk block-uuid %) children))))] + (vec (mapcat #(walk nil %) blocks)))) + +(defn- created-ids-in-order + [ordered-uuids entities entity-kind] + (let [id-by-uuid (reduce (fn [acc {:keys [db/id block/uuid]}] + (if-let [entity-uuid (normalized-uuid uuid)] + (if (some? id) + (assoc acc entity-uuid id) + acc) + acc)) + {} + entities) + missing-uuids (->> ordered-uuids + (remove #(contains? id-by-uuid %)) + vec)] + (when (seq missing-uuids) + (throw (ex-info "unable to resolve created ids" + {:code :add-id-resolution-failed + :entity-kind entity-kind + :missing-uuids missing-uuids}))) + (normalize-created-ids (map #(get id-by-uuid %) ordered-uuids)))) + +(defn- resolve-created-block-ids + [config repo blocks insert-result] + (let [ordered-uuids (or (seq (collect-created-block-uuids (:tx-data insert-result))) + (seq (collect-created-block-uuids blocks)) + (seq (collect-created-block-uuids (:blocks insert-result))))] + (if-not (seq ordered-uuids) + (p/rejected (ex-info "unable to resolve created block ids" + {:code :add-id-resolution-failed + :entity-kind :block + :reason :missing-created-uuids})) + (p/let [entities (p/all + (map (fn [block-uuid] + (transport/invoke config :thread-api/pull false + [repo [:db/id :block/uuid] [:block/uuid block-uuid]])) + ordered-uuids))] + (created-ids-in-order ordered-uuids entities :block))))) + +(defn- resolve-created-page-ids + [config repo page create-result] + (let [page-uuid (some-> create-result second normalized-uuid)] + (if page-uuid + (p/let [page-entity (transport/invoke config :thread-api/pull false + [repo [:db/id :block/uuid] [:block/uuid page-uuid]])] + (created-ids-in-order [page-uuid] [page-entity] :page)) + (p/let [page-entity (transport/invoke config :thread-api/pull false + [repo [:db/id :block/uuid] + [:block/name (common-util/page-name-sanity-lc page)]]) + page-id (:db/id page-entity)] + (if (some? page-id) + [page-id] + (throw (ex-info "unable to resolve created page id" + {:code :add-id-resolution-failed + :entity-kind :page + :page page}))))))) + (defn- extract-page-refs [title] (when (string? title) @@ -845,8 +945,7 @@ tags-result (parse-tags-option (:tags options)) properties-result (parse-properties-option (:properties options)) tags (:value tags-result) - properties (:value properties-result) - ensure-uuids? (or status (seq tags) (seq properties))] + properties (:value properties-result)] (cond (and (seq status-text) (nil? status)) {:ok? false @@ -865,9 +964,7 @@ (let [vector-result (ensure-blocks (:value blocks-result))] (if-not (:ok? vector-result) vector-result - (let [blocks (cond-> (:value vector-result) - ensure-uuids? - ensure-block-uuids)] + (let [blocks (ensure-block-uuids (:value vector-result))] {:ok? true :action {:type :add-block :repo repo @@ -921,11 +1018,12 @@ blocks (if (seq refs) (normalize-block-title-refs (:blocks action) refs) (:blocks action)) + blocks-for-insert (flatten-block-tree blocks) status (:status action) tags (resolve-tags cfg (:repo action) (:tags action)) properties (resolve-properties cfg (:repo action) (:properties action)) pos (:pos action) - keep-uuid? (or status (seq tags) (seq properties)) + keep-uuid? true opts (case pos "last-child" {:sibling? false :bottom? true} "sibling" {:sibling? true} @@ -933,11 +1031,11 @@ opts (cond-> opts keep-uuid? (assoc :keep-uuid? true)) - ops [[:insert-blocks [blocks + ops [[:insert-blocks [blocks-for-insert target-id (assoc opts :outliner-op :insert-blocks)]]] - _ (transport/invoke cfg :thread-api/apply-outliner-ops false [(:repo action) ops {}]) - block-ids (->> blocks + insert-result (transport/invoke cfg :thread-api/apply-outliner-ops false [(:repo action) ops {}]) + block-ids (->> blocks-for-insert (map :block/uuid) (remove nil?) vec) @@ -963,9 +1061,10 @@ [(:repo action) [[:batch-set-property [block-ids k v {}]]] {}])) - properties)))] + properties))) + created-ids (resolve-created-block-ids cfg (:repo action) blocks-for-insert insert-result)] {:status :ok - :data {:result nil}}))) + :data {:result created-ids}}))) (defn execute-add-page [action config] @@ -977,7 +1076,7 @@ options (cond-> {} (seq properties) (assoc :properties properties)) ops [[:create-page [(:page action) options]]] - result (transport/invoke cfg :thread-api/apply-outliner-ops false [(:repo action) ops {}]) + create-result (transport/invoke cfg :thread-api/apply-outliner-ops false [(:repo action) ops {}]) _ (when (seq tag-ids) (p/let [page-name (common-util/page-name-sanity-lc (:page action)) page (pull-entity cfg (:repo action) [:db/id :block/uuid] [:block/name page-name]) @@ -990,6 +1089,7 @@ [(:repo action) [[:batch-set-property [[page-uuid] :block/tags tag-id {}]]] {}])) - tag-ids))))] + tag-ids)))) + created-ids (resolve-created-page-ids cfg (:repo action) (:page action) create-result)] {:status :ok - :data {:result result}}))) + :data {:result created-ids}}))) diff --git a/src/main/logseq/cli/format.cljs b/src/main/logseq/cli/format.cljs index ed5cfacb5f..f70756b0a0 100644 --- a/src/main/logseq/cli/format.cljs +++ b/src/main/logseq/cli/format.cljs @@ -236,12 +236,12 @@ (and host port) (conj (str "Host: " host " Port: " port)))))) (defn- format-add-block - [{:keys [repo blocks]}] - (str "Added blocks: " (count blocks) " (repo: " repo ")")) + [_context ids] + (str "Added blocks:\n" (pr-str (vec (or ids []))))) (defn- format-add-page - [{:keys [repo page]}] - (str "Added page: " page " (repo: " repo ")")) + [_context ids] + (str "Added page:\n" (pr-str (vec (or ids []))))) (defn- format-remove [{:keys [repo page uuid id ids]}] @@ -311,8 +311,8 @@ (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) + :add-block (format-add-block context (:result data)) + :add-page (format-add-page context (:result data)) :remove (format-remove context) :update-block (format-update-block context) :graph-export (format-graph-export context) diff --git a/src/test/logseq/cli/command/add_test.cljs b/src/test/logseq/cli/command/add_test.cljs new file mode 100644 index 0000000000..ac10df43e7 --- /dev/null +++ b/src/test/logseq/cli/command/add_test.cljs @@ -0,0 +1,45 @@ +(ns logseq.cli.command.add-test + (:require [cljs.test :refer [deftest is testing]] + [logseq.cli.command.add :as add-command])) + +(deftest test-collect-created-block-uuids + (testing "collects uuids depth-first and removes duplicates" + (let [root-uuid (random-uuid) + child-uuid (random-uuid) + grandchild-uuid (random-uuid) + sibling-uuid (random-uuid) + blocks [{:block/uuid root-uuid + :block/children [{:block/uuid child-uuid} + {:block/title "without uuid" + :block/children [{:block/uuid grandchild-uuid}]}]} + {:block/uuid sibling-uuid} + {:block/uuid child-uuid}]] + (is (= [root-uuid child-uuid grandchild-uuid sibling-uuid] + (#'add-command/collect-created-block-uuids blocks)))))) + +(deftest test-created-ids-in-order + (testing "normalizes created ids in deterministic uuid order" + (let [uuid-a (random-uuid) + uuid-b (random-uuid) + uuid-c (random-uuid) + ordered-uuids [uuid-c uuid-a uuid-b] + entities [{:block/uuid uuid-a :db/id 101} + {:block/uuid uuid-b :db/id 202} + {:block/uuid uuid-c :db/id 303}]] + (is (= [303 101 202] + (#'add-command/created-ids-in-order ordered-uuids entities :block)))))) + +(deftest test-created-ids-in-order-errors-on-missing-entity + (testing "throws when any created uuid cannot be resolved to db/id" + (let [uuid-a (random-uuid) + uuid-b (random-uuid) + error (try + (#'add-command/created-ids-in-order + [uuid-a uuid-b] + [{:block/uuid uuid-a :db/id 11}] + :block) + nil + (catch :default e e))] + (is (some? error)) + (is (= :add-id-resolution-failed (-> error ex-data :code))) + (is (= [uuid-b] (-> error ex-data :missing-uuids)))))) diff --git a/src/test/logseq/cli/format_test.cljs b/src/test/logseq/cli/format_test.cljs index 8f8d3239f3..ba581c4a9f 100644 --- a/src/test/logseq/cli/format_test.cljs +++ b/src/test/logseq/cli/format_test.cljs @@ -91,14 +91,23 @@ result))))) (deftest test-human-output-add-remove - (testing "add block renders a succinct success line" + (testing "add block renders ids in two lines" (let [result (format/format-result {:status :ok :command :add-block :context {:repo "demo-repo" :blocks ["a" "b"]} - :data {:result {:ok true}}} + :data {:result [201 202]}} {:output-format nil})] - (is (= "Added blocks: 2 (repo: demo-repo)" result)))) + (is (= "Added blocks:\n[201 202]" result)))) + + (testing "add page renders ids in two lines" + (let [result (format/format-result {:status :ok + :command :add-page + :context {:repo "demo-repo" + :page "Home"} + :data {:result [123]}} + {:output-format nil})] + (is (= "Added page:\n[123]" result)))) (testing "remove page renders a succinct success line" (let [result (format/format-result {:status :ok diff --git a/src/test/logseq/cli/integration_test.cljs b/src/test/logseq/cli/integration_test.cljs index dfb2eeb2fc..b8f2a1b8ba 100644 --- a/src/test/logseq/cli/integration_test.cljs +++ b/src/test/logseq/cli/integration_test.cljs @@ -184,6 +184,10 @@ (when (= title (item-title item)) item))) item-id)) +(defn- first-result-id + [payload] + (first (get-in payload [:data :result]))) + (deftest ^:long test-cli-graph-list (async done (let [data-dir (node-helper/create-tmp-dir "db-worker")] @@ -302,6 +306,162 @@ (is false (str "unexpected error: " e)) (done))))))) +(deftest ^:long test-cli-add-page-json-output-returns-id + (async done + (let [data-dir (node-helper/create-tmp-dir "db-worker-add-page-json-id") + repo "add-page-json-id-graph"] + (-> (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" repo] data-dir cfg-path) + add-page-result (run-cli ["--repo" repo "add" "page" "--page" "Home"] data-dir cfg-path) + add-page-payload (parse-json-output add-page-result) + page-ids (get-in add-page-payload [:data :result]) + page-id (first page-ids) + query-payload (run-query data-dir cfg-path repo + "[:find ?id . :in $ ?page-name :where [?id :block/name ?page-name]]" + (pr-str [(common-util/page-name-sanity-lc "Home")])) + queried-page-id (get-in query-payload [:data :result]) + stop-result (run-cli ["server" "stop" "--repo" repo] data-dir cfg-path) + stop-payload (parse-json-output stop-result)] + (is (= 0 (:exit-code add-page-result))) + (is (= "ok" (:status add-page-payload))) + (is (vector? page-ids)) + (is (= 1 (count page-ids))) + (is (number? page-id)) + (is (= page-id queried-page-id)) + (is (= "ok" (:status stop-payload))) + (done)) + (p/catch (fn [e] + (is false (str "unexpected error: " e)) + (done))))))) + +(deftest ^:long test-cli-add-block-json-output-returns-ids + (async done + (let [data-dir (node-helper/create-tmp-dir "db-worker-add-block-json-ids") + repo "add-block-json-ids-graph"] + (-> (p/let [cfg-path (node-path/join (node-helper/create-tmp-dir "cli") "cli.edn") + blocks-edn (pr-str [{:block/title "Parent" + :block/children [{:block/title "Child"}]} + {:block/title "Sibling"}]) + _ (fs/writeFileSync cfg-path "{:output-format :json}") + _ (run-cli ["graph" "create" "--repo" repo] data-dir cfg-path) + _ (run-cli ["--repo" repo "add" "page" "--page" "Home"] data-dir cfg-path) + add-block-result (run-cli ["--repo" repo + "add" "block" + "--target-page-name" "Home" + "--blocks" blocks-edn] + data-dir cfg-path) + add-block-payload (parse-json-output add-block-result) + block-ids (get-in add-block-payload [:data :result]) + title-query-payload (run-query data-dir cfg-path repo + "[:find ?title :in $ [?id ...] :where [?id :block/title ?title]]" + (pr-str [block-ids])) + block-titles (->> (get-in title-query-payload [:data :result]) + (map first) + set) + stop-result (run-cli ["server" "stop" "--repo" repo] data-dir cfg-path) + stop-payload (parse-json-output stop-result)] + (is (= 0 (:exit-code add-block-result))) + (is (= "ok" (:status add-block-payload))) + (is (vector? block-ids)) + (is (= 3 (count block-ids))) + (is (= 3 (count (distinct block-ids)))) + (is (every? number? block-ids)) + (is (= #{"Parent" "Child" "Sibling"} block-titles)) + (is (= "ok" (:status stop-payload))) + (done)) + (p/catch (fn [e] + (is false (str "unexpected error: " e)) + (done))))))) + +(deftest ^:long test-cli-add-page-block-edn-output-returns-id + (async done + (let [data-dir (node-helper/create-tmp-dir "db-worker-add-edn-id") + repo "add-edn-id-graph"] + (-> (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" repo] data-dir cfg-path) + add-page-result (run-cli ["--repo" repo + "--output" "edn" + "add" "page" + "--page" "Home"] + data-dir cfg-path) + add-page-payload (parse-edn-output add-page-result) + page-ids (get-in add-page-payload [:data :result]) + add-block-result (run-cli ["--repo" repo + "--output" "edn" + "add" "block" + "--target-page-name" "Home" + "--content" "EDN block"] + data-dir cfg-path) + add-block-payload (parse-edn-output add-block-result) + block-ids (get-in add-block-payload [:data :result]) + stop-result (run-cli ["server" "stop" "--repo" repo] data-dir cfg-path) + stop-payload (parse-json-output stop-result)] + (is (= 0 (:exit-code add-page-result))) + (is (= :ok (:status add-page-payload))) + (is (vector? page-ids)) + (is (= 1 (count page-ids))) + (is (number? (first page-ids))) + (is (= 0 (:exit-code add-block-result))) + (is (= :ok (:status add-block-payload))) + (is (vector? block-ids)) + (is (= 1 (count block-ids))) + (is (number? (first block-ids))) + (is (= "ok" (:status stop-payload))) + (done)) + (p/catch (fn [e] + (is false (str "unexpected error: " e)) + (done))))))) + +(deftest ^:long test-cli-add-identifiers-chain-update-remove + (async done + (let [data-dir (node-helper/create-tmp-dir "db-worker-add-id-chain") + repo "add-id-chain-graph"] + (-> (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" repo] data-dir cfg-path) + add-page-result (run-cli ["--repo" repo "add" "page" "--page" "ChainPage"] data-dir cfg-path) + add-page-payload (parse-json-output add-page-result) + page-id (first-result-id add-page-payload) + add-block-result (run-cli ["--repo" repo + "add" "block" + "--target-id" (str page-id) + "--content" "Chain block"] + data-dir cfg-path) + add-block-payload (parse-json-output add-block-result) + block-id (first-result-id add-block-payload) + update-result (run-cli ["--repo" repo + "update" + "--id" (str block-id) + "--update-properties" "{:logseq.property/publishing-public? true}"] + data-dir cfg-path) + update-payload (parse-json-output update-result) + remove-result (run-cli ["--repo" repo "remove" "--id" (str block-id)] data-dir cfg-path) + remove-payload (parse-json-output remove-result) + query-after-remove (run-query data-dir cfg-path repo + "[:find ?e . :in $ ?title :where [?e :block/title ?title]]" + (pr-str ["Chain block"])) + removed-id (get-in query-after-remove [:data :result]) + stop-result (run-cli ["server" "stop" "--repo" repo] data-dir cfg-path) + stop-payload (parse-json-output stop-result)] + (is (= 0 (:exit-code add-page-result))) + (is (= "ok" (:status add-page-payload))) + (is (number? page-id)) + (is (= 0 (:exit-code add-block-result))) + (is (= "ok" (:status add-block-payload))) + (is (number? block-id)) + (is (= 0 (:exit-code update-result))) + (is (= "ok" (:status update-payload))) + (is (= 0 (:exit-code remove-result))) + (is (= "ok" (:status remove-payload))) + (is (nil? removed-id)) + (is (= "ok" (:status stop-payload))) + (done)) + (p/catch (fn [e] + (is false (str "unexpected error: " e)) + (done))))))) + (deftest ^:long test-cli-add-block-rewrites-page-ref (async done (let [data-dir (node-helper/create-tmp-dir "db-worker-ref-rewrite")]