@geml/logseq-sync 2.0.2 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,7 +21,7 @@ Two settings, and only the first one usually needs touching:
21
21
  database dump, in a folder you chose
22
22
  - 🔁 **Continuous, not one-shot** — edit in Logseq, and seconds later the file
23
23
  on disk has caught up
24
- - â†Šī¸ **A way back** — `geml-sync restore` imports the vault into a graph,
24
+ - â†Šī¸ **A way back** — `logseq-sync restore` imports the vault into a graph,
25
25
  merging by block uuid. Files you can read are worth more when they are also
26
26
  files you can return
27
27
  - đŸŒŋ **Git if you want it** — point the vault at a repository and every sync is
@@ -68,7 +68,7 @@ no git, no shell (verified against the 2.0.1 app bundle). So the in-app plugin
68
68
  dirty-marker file through the plugin storage API;
69
69
  - **show** the last sync result in the toolbar (`⇄`) and command palette.
70
70
 
71
- Everything with side effects lives in the **watcher** (`watcher/bin/geml-sync.mjs`),
71
+ Everything with side effects lives in the **watcher** (`watcher/bin/logseq-sync.mjs`),
72
72
  built on Logseq's own EDN export. It reacts to the marker file
73
73
  immediately (interval polling stays on as a fallback), writes only the files
74
74
  that actually changed — so `git diff` is never noise — commits with a pathspec
@@ -82,18 +82,19 @@ Real output, real DB graph (exported with the official CLI, validated by
82
82
  `logseq validate`):
83
83
 
84
84
  ```text
85
- $ node watcher/bin/geml-sync.mjs geml-spike ~/vault-demo --git-commit --signal <storage>/geml-sync-dirty.json
86
- Starting GEML Sync: Graph "geml-spike" ➔ ~/vault-demo
87
- Git auto-commit: enabled (scoped to target paths)
88
- [19:29:14] Synced: 8 written, 0 unchanged.
89
- Git: [master (root-commit) 9cc348c] logseq-geml: sync graph "geml-spike"
85
+ $ logseq-sync geml-spike ~/vault-demo --once --git-commit --no-signal
86
+ Sync Vault with GEML: graph "geml-spike" ➔ ~/vault-demo
87
+ export via @logseq/cli, opening the graph file directly — close the graph in Logseq first
88
+ git auto-commit on, scoped to the vault
89
+ [10:15:28] Synced: 8 written, 0 unchanged.
90
+ Git: [master (root-commit) 2854063] logseq-geml: sync graph "geml-spike"
90
91
  9 files changed, 142 insertions(+)
91
92
  create mode 100644 graph.geml
92
93
  create mode 100644 pages/contents.geml
93
94
  ...
94
95
 
95
- $ node watcher/bin/geml-sync.mjs geml-spike ~/vault-demo --git-commit --signal ... # run again
96
- [19:29:55] Graph is up-to-date (0 written, 8 unchanged).
96
+ $ logseq-sync geml-spike ~/vault-demo --once --git-commit --no-signal # run again
97
+ [10:15:31] Graph is up-to-date (0 written, 8 unchanged).
97
98
  ```
98
99
 
99
100
  ## Setup
@@ -108,7 +109,7 @@ GEML* → **Vault folder**. Any folder you like — `~/logseq-vault`, a director
108
109
  inside a repository you already keep, one your backup tool already watches. It
109
110
  is created if it does not exist, `~` means your home directory, and `restore`
110
111
  reads the vault back from the same place. There is deliberately **no default**:
111
- left empty, `geml-sync` asks you for a folder rather than picking one for you.
112
+ left empty, `logseq-sync` asks you for a folder rather than picking one for you.
112
113
 
113
114
  That is the folder the files are written **into**; the graph they come **from**
114
115
  is detected, and you do not name it.
@@ -143,8 +144,8 @@ found and what is missing, and exits non-zero when the setup cannot sync:
143
144
 
144
145
  | | |
145
146
  |---|---|
146
- | `geml-sync <vault-dir>` | vault here instead of in the plugin settings |
147
- | `geml-sync <graph> <vault-dir>` | both explicitly |
147
+ | `logseq-sync <vault-dir>` | vault here instead of in the plugin settings |
148
+ | `logseq-sync <graph> <vault-dir>` | both explicitly |
148
149
  | `--graph <name>` | pick the graph — needed when several are open |
149
150
  | `--once` | sync once and exit, instead of watching |
150
151
  | `--git-commit` | commit, creating the vault repository if there is none |
@@ -155,11 +156,11 @@ found and what is missing, and exits non-zero when the setup cannot sync:
155
156
  | `--app-cli <path>` | a Logseq CLI the search did not find |
156
157
  | `--signal <file>` / `--no-signal` | the plugin bridge, or none |
157
158
 
158
- ### Going back: `geml-sync restore`
159
+ ### Going back: `logseq-sync restore`
159
160
 
160
161
  ```sh
161
- geml-sync restore # rehearse: says what it would import, writes nothing
162
- geml-sync restore --yes # take a Logseq backup, then import the vault
162
+ logseq-sync restore # rehearse: says what it would import, writes nothing
163
+ logseq-sync restore --yes # take a Logseq backup, then import the vault
163
164
  ```
164
165
 
165
166
  The vault imports into the graph by block uuid, so an edit lands in place
@@ -185,7 +186,7 @@ graph the app does **not** have open, and on Node 24 it needs a
185
186
  mkdir logseq-cli && cd logseq-cli && npm init -y
186
187
  npm pkg set overrides.better-sqlite3=12.11.1
187
188
  npm i @logseq/cli
188
- # then: LOGSEQ_CLI_DIR=$PWD geml-sync --no-app-cli â€Ļ
189
+ # then: LOGSEQ_CLI_DIR=$PWD logseq-sync --no-app-cli â€Ļ
189
190
  ```
190
191
 
191
192
  `--api-server-token` (or `LOGSEQ_API_SERVER_TOKEN`) routes that fallback
@@ -265,7 +266,7 @@ threads are
265
266
 
266
267
  ```
267
268
  core/ converter (mapping.mjs), sync engine, bridge.mjs (the signal/status file contract)
268
- watcher/ the geml-sync CLI and its end-to-end tests — published to npm as @geml/logseq-sync
269
+ watcher/ the logseq-sync CLI and its end-to-end tests — published to npm as @geml/logseq-sync
269
270
  plugin/ the in-app half (this package.json is the Logseq plugin manifest)
270
271
  ```
271
272
 
@@ -25,7 +25,7 @@
25
25
 
26
26
  <!-- watcher box -->
27
27
  <rect x="488" y="36" width="248" height="130" rx="10" fill="#ffffff" stroke="#2da44e" stroke-width="1.5"/>
28
- <text x="612" y="62" text-anchor="middle" font-size="15" font-weight="700" fill="#57606a">geml-sync watcher (CLI)</text>
28
+ <text x="612" y="62" text-anchor="middle" font-size="15" font-weight="700" fill="#57606a">logseq-sync watcher (CLI)</text>
29
29
  <text x="612" y="86" text-anchor="middle" font-size="11.5" fill="#57606a">export via official @logseq/cli</text>
30
30
  <text x="612" y="103" text-anchor="middle" font-size="11.5" fill="#57606a">writes only files that changed</text>
31
31
  <text x="612" y="120" text-anchor="middle" font-size="11.5" fill="#57606a">git commit scoped to the vault</text>
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@geml/logseq-sync",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
7
  "description": "Continuously sync a Logseq DB graph to a Git-friendly folder of readable plain-text GEML files — the watcher half of the Sync Vault with GEML plugin. Built on the official @logseq/cli export; writes only files that changed, commits scoped strictly to the vault.",
8
8
  "type": "module",
9
9
  "bin": {
10
- "geml-sync": "watcher/bin/geml-sync.mjs"
10
+ "logseq-sync": "watcher/bin/logseq-sync.mjs"
11
11
  },
12
12
  "files": [
13
13
  "core/src",
@@ -44,7 +44,7 @@
44
44
  ],
45
45
  "scripts": {
46
46
  "test": "node core/test/roundtrip.test.mjs && node core/test/sync.test.mjs && node core/test/discovery.test.mjs && node watcher/test/cli-sync.test.mjs && node watcher/test/signal-sync.test.mjs && node watcher/test/zero-config.test.mjs && node plugin/test/core.test.mjs",
47
- "sync": "node watcher/bin/geml-sync.mjs",
47
+ "sync": "node watcher/bin/logseq-sync.mjs",
48
48
  "build:plugin": "node plugin/build.mjs"
49
49
  },
50
50
  "dependencies": {
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- // geml-sync — a Logseq DB graph ➔ a Git-friendly folder of readable GEML files.
3
- // The full usage is the USAGE constant below, printed by `geml-sync --help`.
2
+ // logseq-sync — a Logseq DB graph ➔ a Git-friendly folder of readable GEML files.
3
+ // The full usage is the USAGE constant below, printed by `logseq-sync --help`.
4
4
 
5
5
  import { execFileSync } from "node:child_process";
6
6
  import {
@@ -23,13 +23,13 @@ import {
23
23
 
24
24
  const PLUGIN_TITLE = "Sync Vault with GEML";
25
25
 
26
- const USAGE = `geml-sync — a Logseq DB graph ➔ a Git-friendly folder of readable GEML files.
26
+ const USAGE = `logseq-sync — a Logseq DB graph ➔ a Git-friendly folder of readable GEML files.
27
27
 
28
28
  Usage:
29
- geml-sync [vault-dir] [flags] vault-dir defaults to the plugin's setting
30
- geml-sync <graph> <vault-dir> [flags] explicit form, when you have several graphs
31
- geml-sync doctor report what was detected and what is missing
32
- geml-sync restore [vault-dir] vault ➔ graph. Rehearses; --yes performs it,
29
+ logseq-sync [vault-dir] [flags] vault-dir defaults to the plugin's setting
30
+ logseq-sync <graph> <vault-dir> [flags] explicit form, when you have several graphs
31
+ logseq-sync doctor report what was detected and what is missing
32
+ logseq-sync restore [vault-dir] vault ➔ graph. Rehearses; --yes performs it,
33
33
  taking a graph backup first (--no-backup to skip)
34
34
 
35
35
  Whatever can be worked out, is: the CLI that ships inside the desktop app,
@@ -135,8 +135,10 @@ for (let i = 0; i < args.length; i++) {
135
135
  } else if (arg === "--api-server-token") {
136
136
  needValue(i, "--api-server-token");
137
137
  flags.apiServerToken = args[++i];
138
- } else if (arg.startsWith("--")) {
139
- console.error(`Error: Unknown flag "${arg}". Run \`geml-sync --help\` for usage.`);
138
+ } else if (arg.startsWith("-")) {
139
+ // One dash included: "-graph demo" once sailed through as a graph literally
140
+ // named "-graph" and a vault named "demo" — a typo must stop, not sync.
141
+ console.error(`Error: Unknown flag "${arg}". Run \`logseq-sync --help\` for usage.`);
140
142
  process.exit(2);
141
143
  } else if (subcommand === null && positional.length === 0 && (arg === "doctor" || arg === "restore")) {
142
144
  subcommand = arg;
@@ -263,7 +265,7 @@ if (positional.length >= 2) {
263
265
  if (isGraphName) {
264
266
  console.error(
265
267
  `Error: "${only}" is the name of a graph, not a vault directory. ` +
266
- `Write the destination too — geml-sync ${only} <vault-dir> — or select it with --graph ${only}.`
268
+ `Write the destination too — logseq-sync ${only} <vault-dir> — or select it with --graph ${only}.`
267
269
  );
268
270
  process.exit(2);
269
271
  }
@@ -293,7 +295,7 @@ function resolveVaultOrExit() {
293
295
  if (vaultRaw) return resolve(expandHome(vaultRaw));
294
296
  console.error(
295
297
  `Error: no vault directory. Set it in Logseq — Settings → Plugins → ${PLUGIN_TITLE} → ` +
296
- `"Vault folder" — or pass one: geml-sync <vault-dir>.`
298
+ `"Vault folder" — or pass one: logseq-sync <vault-dir>.`
297
299
  );
298
300
  process.exit(2);
299
301
  }
@@ -379,12 +381,12 @@ function doctor() {
379
381
  const sig = resolveSignalPath();
380
382
  rows.push(`${sig ? " ok " : " note "} ${"bridge".padEnd(14)} ${sig ?? "no plugin storage dir; interval polling only"}`);
381
383
 
382
- console.log(`${PLUGIN_TITLE} — geml-sync doctor\n`);
384
+ console.log(`${PLUGIN_TITLE} — logseq-sync doctor\n`);
383
385
  console.log(rows.join("\n"));
384
386
  console.log(
385
387
  blocked
386
388
  ? "\nNot ready: fix the MISS lines above."
387
- : "\nReady. Run `geml-sync` with no arguments to start syncing."
389
+ : "\nReady. Run `logseq-sync` with no arguments to start syncing."
388
390
  );
389
391
  process.exit(blocked ? 1 : 0);
390
392
  }
@@ -395,7 +397,10 @@ if (subcommand === "doctor") doctor();
395
397
  graphName = resolveGraphOrExit();
396
398
 
397
399
  // Validate graph name to prevent command/path injection
398
- if (!/^[a-zA-Z0-9_.-]+$/.test(graphName)) {
400
+ // The first character must not be a dash or a dot: the name travels as argv
401
+ // into the exporting CLI, where a leading dash reads as a flag ("-graph"
402
+ // arrived here as a real user typo for --graph), and "." / ".." read as paths.
403
+ if (!/^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/.test(graphName)) {
399
404
  console.error(`Error: Invalid graph name "${graphName}". Only alphanumeric characters, hyphens, and underscores are allowed.`);
400
405
  process.exit(2);
401
406
  }
@@ -476,9 +481,19 @@ function runLogseqCli(...cmdArgs) {
476
481
  });
477
482
  }
478
483
 
479
- // Fallback to npx (executable npx.cmd on Windows, npx on Unix) without shell: true
480
- const npxCmd = process.platform === "win32" ? "npx.cmd" : "npx";
481
- return execFileSync(npxCmd, ["-y", "@logseq/cli", ...cmdArgs], {
484
+ // On Windows the npx fallback is an instruction, not a spawn: Node refuses
485
+ // to run a .cmd without a shell (CVE-2024-27980), and routing a user-typed
486
+ // graph name through cmd.exe to get around that is how injection happens.
487
+ // Every prior attempt died as `spawnSync npx.cmd EINVAL`, once every poll.
488
+ if (process.platform === "win32") {
489
+ throw new Error(
490
+ "@logseq/cli is not installed where I can see it. Install it once\n" +
491
+ " (mkdir logseq-cli && cd logseq-cli && npm init -y && npm i @logseq/cli)\n" +
492
+ "and point LOGSEQ_CLI_DIR at that directory — or pass --app-cli <path>\n" +
493
+ "to the desktop app's CLI."
494
+ );
495
+ }
496
+ return execFileSync("npx", ["-y", "@logseq/cli", ...cmdArgs], {
482
497
  cwd: cliCwd,
483
498
  encoding: "utf8",
484
499
  shell: false,