Add annotations and fix some.

This commit is contained in:
vorotamoroz
2026-06-18 11:57:33 +01:00
parent 866a49204c
commit 5cb76bba72
15 changed files with 48 additions and 33 deletions
+4 -4
View File
@@ -1,17 +1,17 @@
# Self-hosted LiveSync CLI
Command-line version of Self-hosted LiveSync plugin for syncing vaults without Obsidian.
Command-line version of Self-hosted LiveSync plug-in for syncing vaults without Obsidian.
## Features
- ✅ Sync Obsidian vaults using CouchDB without running Obsidian
- ✅ Compatible with Self-hosted LiveSync plugin settings
- ✅ Compatible with Self-hosted LiveSync plug-in settings
- ✅ Supports all core sync features (encryption, conflict resolution, etc.)
- ✅ Lightweight and headless operation
- ✅ Cross-platform (Windows, macOS, Linux)
## Architecture
This CLI version is built using the same core as the Obsidian plugin:
This CLI version is built using the same core as the Obsidian plug-in:
```
CLI Main
@@ -290,7 +290,7 @@ livesync-cli /path/to/your-local-database --settings /path/to/settings.json unlo
### Configuration
The CLI uses the same settings format as the Obsidian plugin. Create a `.livesync/settings.json` file in your vault directory:
The CLI uses the same settings format as the Obsidian plug-in. Create a `.livesync/settings.json` file in your vault directory:
```json
{
@@ -60,6 +60,7 @@ export class NodeStorageAdapter implements IStorageAdapter<NodeStat> {
async readBinary(p: string): Promise<ArrayBuffer> {
const buffer = await fs.readFile(this.resolvePath(p));
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- required in environments where Buffer.buffer is ArrayBufferLike
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) as ArrayBuffer;
}
@@ -31,6 +31,7 @@ export class NodeVaultAdapter implements IVaultAdapter<NodeFile> {
const buffer = await fs.readFile(this.resolvePath(file.path));
// Same correction as read() — ensure stat.size matches actual byte length.
file.stat.size = buffer.length;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- required in environments where Buffer.buffer is ArrayBufferLike
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) as ArrayBuffer;
}
+1
View File
@@ -1,6 +1,7 @@
import { path, readline } from "../node-compat";
export function toArrayBuffer(data: Buffer): ArrayBuffer {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- required in environments where Buffer.buffer is ArrayBufferLike
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer;
}
+5 -5
View File
@@ -303,7 +303,7 @@ export async function main() {
console.error(`Error: ${databasePath} is not a directory`);
process.exit(1);
}
} catch (error) {
} catch {
console.error(`Error: Database directory ${databasePath} does not exist`);
process.exit(1);
}
@@ -324,7 +324,7 @@ export async function main() {
? path.resolve(options.commandArgs[0])
: options.vaultPath
? path.resolve(options.vaultPath)
: databasePath!;
: databasePath;
// Check if vault directory exists
try {
@@ -333,7 +333,7 @@ export async function main() {
console.error(`Error: Vault path ${vaultPath} is not a directory`);
process.exit(1);
}
} catch (error) {
} catch {
console.error(`Error: Vault directory ${vaultPath} does not exist`);
process.exit(1);
}
@@ -415,7 +415,7 @@ export async function main() {
// Force disable IndexedDB adapter in CLI environment
data.useIndexedDBAdapter = false;
return data;
} catch (error) {
} catch {
if (options.verbose) {
console.error(`[Settings] File not found, using defaults`);
}
@@ -434,7 +434,7 @@ export async function main() {
() => [], // No add-ons
(core) => {
// Register P2P replicator feature.
const _replicator = useP2PReplicatorFeature(core);
useP2PReplicatorFeature(core);
// Add target filter to prevent internal files are handled
core.services.vault.isTargetFile.addHandler(async (target) => {
const targetPath = stripAllPrefixes(getPathFromUXFileInfo(target));
+5 -1
View File
@@ -1,7 +1,11 @@
/* eslint-disable obsidianmd/no-nodejs-builtins */
// eslint-disable-next-line obsidianmd/no-nodejs-builtins -- This file is used to provide Node.js built-in modules in the CLI environment, which is not running in a browser context.
import * as nodeFs from "node:fs";
// eslint-disable-next-line obsidianmd/no-nodejs-builtins -- This file is used to provide Node.js built-in modules in the CLI environment, which is not running in a browser context.
import * as nodeFsPromises from "node:fs/promises";
// eslint-disable-next-line obsidianmd/no-nodejs-builtins -- This file is used to provide Node.js built-in modules in the CLI environment, which is not running in a browser context.
import * as nodePath from "node:path";
// eslint-disable-next-line obsidianmd/no-nodejs-builtins -- This file is used to provide Node.js built-in modules in the CLI environment, which is not running in a browser context.
import * as nodeReadlinePromises from "node:readline/promises";
// eslint-disable-next-line obsidianmd/no-nodejs-builtins -- This file is used to provide Node.js built-in modules in the CLI environment, which is not running in a browser context.
import type { Stats } from "node:fs";
export { nodeFs as fs, nodeFsPromises as fsPromises, nodePath as path, nodeReadlinePromises as readline, type Stats };
+5 -5
View File
@@ -6,7 +6,7 @@ Note: (I vrtmrz have not tested this so much yet).
- 🌐 Runs entirely in the browser
- 📁 Uses FileSystem API to access your local vault
- 🔄 Syncs with CouchDB, Object Storage server (compatible with Self-hosted LiveSync plugin)
- 🔄 Syncs with CouchDB, Object Storage server (compatible with Self-hosted LiveSync plug-in)
- 🚫 No server-side code required!!
- 💾 Settings stored in `.livesync/settings.json` within your vault
- 👁️ Real-time file watching (Chrome 124+ with FileSystemObserver)
@@ -127,7 +127,7 @@ webapp/
1. **Adapters**: Implement `IFileSystemAdapter` interface using FileSystem API
2. **Managers**: Handle storage events and file watching
3. **Service Modules**: Integrate with LiveSyncBaseCore
4. **Main**: Application initialization and lifecycle management
4. **Main**: Application initialisation and lifecycle management
### Service Hub
@@ -154,11 +154,11 @@ Uses `BrowserServiceHub` which provides:
- Settings stored in `.livesync/settings.json` in vault
- Real-time file watching only with FileSystemObserver (Chrome 124+)
## Differences from Obsidian Plugin
## Differences from Obsidian Plug-in
- No Obsidian-specific modules (UI, settings dialog, etc.)
- No Obsidian-specific modules (UI, settings dialogue, etc.)
- Simplified configuration
- No plugin/theme sync features
- No plug-in/theme sync features
- No internal file handling (`.obsidian` folder)
## Development Notes
-2
View File
@@ -23,7 +23,6 @@ import { compatGlobal, _activeDocument } from "@lib/common/coreEnvFunctions.ts";
const SETTINGS_DIR = ".livesync";
const SETTINGS_FILE = "settings.json";
const DB_NAME = "livesync-webapp";
/**
* Default settings for the webapp
@@ -65,7 +64,6 @@ class LiveSyncWebApp {
console.log(`Vault directory: ${this.rootHandle.name}`);
// Create service context and hub
const context = new ServiceContext();
this.serviceHub = new BrowserServiceHub<ServiceContext>();
// Setup API service
@@ -161,7 +161,6 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
this.observer = new FileSystemObserver(async (records: any[]) => {
for (const record of records) {
const handle = record.root;
const changedHandle = record.changedHandle;
const relativePathComponents = record.relativePathComponents;
const type = record.type; // "appeared", "disappeared", "modified", "moved", "unknown", "errored"
+1 -2
View File
@@ -11,7 +11,6 @@ import { eventHub } from "@lib/hub/hub";
import type { Confirm } from "@lib/interfaces/Confirm";
import { LOG_LEVEL_NOTICE, Logger } from "@lib/common/logger";
import { storeP2PStatusLine } from "./CommandsShim";
import {
EVENT_P2P_PEER_SHOW_EXTRA_MENU,
type PeerStatus,
@@ -87,7 +86,7 @@ export class P2PReplicatorShim implements P2PReplicatorBase {
this._liveSyncReplicator = replicator;
this.p2pLogCollector = p2pLogCollector;
p2pLogCollector.p2pReplicationLine.onChanged((line) => {
storeP2PStatusLine.set(line.value);
p2pStatusLine.value = line.value;
});
}
@@ -738,7 +738,7 @@ Success: ${successCount}, Errored: ${errored}`;
});
// Probably no need to wait, but just in case.
let timeout = 2 * 60 * 1000; // 2 minutes
do {
for (;;) {
const status = await remote.db.info();
if ("compact_running" in status && status?.compact_running) {
this._notice("Compaction in progress on remote database...", "gc-compact");
@@ -751,7 +751,7 @@ Success: ${successCount}, Errored: ${errored}`;
} else {
break;
}
} while (true);
}
if (compactResult && "ok" in compactResult) {
this._notice("Compaction on remote database completed successfully.", "gc-compact");
} else {
+1 -1
Submodule src/lib updated: 0278343fc6...9aeab513b0
@@ -99,9 +99,11 @@ export class DocumentHistoryModal extends Modal {
if (!file && id) {
this.file = this.services.path.id2path(id);
}
// eslint-disable-next-line obsidianmd/no-unsupported-api -- loadLocalStorage is supported in Obsidian 1.7.2+
if (this.app.loadLocalStorage("ols-history-highlightdiff") == "1") {
this.showDiff = true;
}
// eslint-disable-next-line obsidianmd/no-unsupported-api -- loadLocalStorage is supported in Obsidian 1.7.2+
if (this.app.loadLocalStorage("ols-history-diffonly") == "1") {
this.diffOnly = true;
}
@@ -552,6 +554,7 @@ export class DocumentHistoryModal extends Modal {
}
checkbox.addEventListener("input", (evt: Event) => {
this.showDiff = checkbox.checked;
// eslint-disable-next-line obsidianmd/no-unsupported-api -- saveLocalStorage is supported in Obsidian 1.7.2+
this.app.saveLocalStorage("ols-history-highlightdiff", this.showDiff == true ? "1" : null);
this.updateDiffNavVisibility();
void scheduleOnceIfDuplicated("loadRevs", () => this.loadRevs());
@@ -567,6 +570,7 @@ export class DocumentHistoryModal extends Modal {
}
checkbox.addEventListener("input", (evt: Event) => {
this.diffOnly = checkbox.checked;
// eslint-disable-next-line obsidianmd/no-unsupported-api -- saveLocalStorage is supported in Obsidian 1.7.2+
this.app.saveLocalStorage("ols-history-diffonly", this.diffOnly == true ? "1" : null);
void scheduleOnceIfDuplicated("loadRevs", () => this.loadRevs());
});
@@ -38,20 +38,20 @@ export class ObsidianVaultAdapter implements IVaultAdapter<TFile, TFolder> {
}
async delete(file: TFile | TFolder, force = false): Promise<void> {
// if ("trashFile" in this.app.fileManager) {
// // eslint-disable-next-line obsidianmd/no-unsupported-api
// return await this.app.fileManager.trashFile(file);
// }
//TODO: need fix
if ("trashFile" in this.app.fileManager) {
// eslint-disable-next-line obsidianmd/no-unsupported-api
return await this.app.fileManager.trashFile(file);
}
// eslint-disable-next-line obsidianmd/prefer-file-manager-trash-file -- Fallback for older versions of Obsidian without trashFile support
return await this.app.vault.delete(file, force);
}
async trash(file: TFile | TFolder, force = false): Promise<void> {
// if ("trashFile" in this.app.fileManager) {
// // eslint-disable-next-line obsidianmd/no-unsupported-api
// return await this.app.fileManager.trashFile(file);
// }
//TODO: need fix
if ("trashFile" in this.app.fileManager) {
// eslint-disable-next-line obsidianmd/no-unsupported-api
return await this.app.fileManager.trashFile(file);
}
// eslint-disable-next-line obsidianmd/prefer-file-manager-trash-file -- Fallback for older versions of Obsidian without trashFile support
return await this.app.vault.trash(file, force);
}