@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 +18 -17
- package/docs/how-it-works.svg +1 -1
- package/package.json +3 -3
- package/watcher/bin/{geml-sync.mjs â logseq-sync.mjs} +32 -17
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** â `
|
|
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/
|
|
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
|
-
$
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
$
|
|
96
|
-
[
|
|
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, `
|
|
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
|
-
| `
|
|
147
|
-
| `
|
|
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: `
|
|
159
|
+
### Going back: `logseq-sync restore`
|
|
159
160
|
|
|
160
161
|
```sh
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
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
|
|
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
|
|
package/docs/how-it-works.svg
CHANGED
|
@@ -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">
|
|
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.
|
|
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
|
-
"
|
|
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/
|
|
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
|
-
//
|
|
3
|
-
// The full usage is the USAGE constant below, printed by `
|
|
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 = `
|
|
26
|
+
const USAGE = `logseq-sync â a Logseq DB graph â a Git-friendly folder of readable GEML files.
|
|
27
27
|
|
|
28
28
|
Usage:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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 â
|
|
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:
|
|
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} â
|
|
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 `
|
|
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
|
-
|
|
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
|
-
//
|
|
480
|
-
|
|
481
|
-
|
|
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,
|