From a6be20695aad2db18baed6d0b5212d4a25b1c799 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 11 May 2026 03:49:35 +0100 Subject: [PATCH 01/73] feat: use new p2p-rpc wrapper --- src/lib | 2 +- updates.md | 7 +++++++ vitest.config.rpc-unit.ts | 30 ++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 vitest.config.rpc-unit.ts diff --git a/src/lib b/src/lib index 9753055..6a2dc67 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 97530553a63dc206ea3fb7ef60721cabda6c74cc +Subproject commit 6a2dc6777f1eb2beb7a058b8d2dde662662df9d7 diff --git a/updates.md b/updates.md index 0d65ee5..cd1dac7 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,13 @@ Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025) The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). Because 0.25 got a lot of updates, thankfully, compatibility is kept and we do not need breaking changes! In other words, when get enough stabled. The next version will be v1.0.0. Even though it my hope. +## Unreleased + +### P2P Synchronisation + +Now the foundation for P2P synchronisation has been rewritten, and the unit tests have been added. The foundation has been separated into the transport layer, signalling-and-connection layer, and, an RPC layers. And each layer has been unit-tested. As the result, the P2P synchronisation now uses the robust shim that uses RPC-ed PouchDB synchronisation in contrast to previous implementation. +This P2P synchronisation is not compatible with previous versions in terms of connectivity. All devices must be updated. + ## 0.25.60 29th April, 2026 diff --git a/vitest.config.rpc-unit.ts b/vitest.config.rpc-unit.ts new file mode 100644 index 0000000..8c24b34 --- /dev/null +++ b/vitest.config.rpc-unit.ts @@ -0,0 +1,30 @@ +import { defineConfig, mergeConfig } from "vitest/config"; +import viteConfig from "./vitest.config.common"; + +export default mergeConfig( + viteConfig, + defineConfig({ + resolve: { + alias: { + obsidian: "", + }, + }, + test: { + name: "rpc-unit-tests", + include: ["src/lib/src/rpc/**/*.unit.spec.ts"], + exclude: ["test/**"], + coverage: { + include: ["src/lib/src/rpc/**/*.ts"], + exclude: ["**/*.unit.spec.ts", "**/index.ts"], + provider: "v8", + reporter: ["text", "json", "html", ["text", { file: "coverage-rpc-text.txt" }]], + thresholds: { + lines: 90, + functions: 90, + branches: 75, + statements: 90, + }, + }, + }, + }) +); From 3f7bb047acfac2d374282032591e7437bc3588d9 Mon Sep 17 00:00:00 2001 From: Brian Spackman <283016431+brian-spackman@users.noreply.github.com> Date: Tue, 12 May 2026 13:28:03 -0600 Subject: [PATCH 02/73] fix: floor sub-millisecond CLI mtimes to prevent mobile crash On Linux, fs.Stats.mtimeMs and ctimeMs return floats with sub-millisecond precision derived from the kernel's nanosecond filesystem mtime. Stored raw, this produces document timestamps like 1778511180024.462 in CouchDB rather than integer milliseconds. Mobile clients running LiveSync 0.25.60 have been observed to crash when processing change-feed updates carrying non-integer millisecond timestamps from CLI-written documents. Desktop and mobile GUI plugins write integer milliseconds, so the crash only manifests when the headless CLI on Linux is the source. Whether the issue was introduced in 0.25.60 or had been latent in earlier versions hasn't been investigated; 0.25.60 is the version where the crash was confirmed and the fix verified. Floor the values at every stat-read site (six across three adapters and one command) so CLI-written documents carry integer-millisecond timestamps consistent with the rest of the mesh. --- src/apps/cli/adapters/NodeFileSystemAdapter.ts | 8 ++++---- src/apps/cli/adapters/NodeStorageAdapter.ts | 4 ++-- src/apps/cli/adapters/NodeVaultAdapter.ts | 8 ++++---- src/apps/cli/commands/runCommand.ts | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/apps/cli/adapters/NodeFileSystemAdapter.ts b/src/apps/cli/adapters/NodeFileSystemAdapter.ts index b90ad73..d1b6764 100644 --- a/src/apps/cli/adapters/NodeFileSystemAdapter.ts +++ b/src/apps/cli/adapters/NodeFileSystemAdapter.ts @@ -104,8 +104,8 @@ export class NodeFileSystemAdapter implements IFileSystemAdapter { const stat = await fs.stat(this.resolvePath(p)); return { size: stat.size, - mtime: stat.mtimeMs, - ctime: stat.ctimeMs, + mtime: Math.floor(stat.mtimeMs), + ctime: Math.floor(stat.ctimeMs), type: stat.isDirectory() ? "folder" : "file", }; } catch { diff --git a/src/apps/cli/adapters/NodeVaultAdapter.ts b/src/apps/cli/adapters/NodeVaultAdapter.ts index 947ad01..fc10577 100644 --- a/src/apps/cli/adapters/NodeVaultAdapter.ts +++ b/src/apps/cli/adapters/NodeVaultAdapter.ts @@ -66,8 +66,8 @@ export class NodeVaultAdapter implements IVaultAdapter { path: p as any, stat: { size: stat.size, - mtime: stat.mtimeMs, - ctime: stat.ctimeMs, + mtime: Math.floor(stat.mtimeMs), + ctime: Math.floor(stat.ctimeMs), type: "file", }, }; @@ -89,8 +89,8 @@ export class NodeVaultAdapter implements IVaultAdapter { path: p as any, stat: { size: stat.size, - mtime: stat.mtimeMs, - ctime: stat.ctimeMs, + mtime: Math.floor(stat.mtimeMs), + ctime: Math.floor(stat.ctimeMs), type: "file", }, }; diff --git a/src/apps/cli/commands/runCommand.ts b/src/apps/cli/commands/runCommand.ts index e188c23..c888855 100644 --- a/src/apps/cli/commands/runCommand.ts +++ b/src/apps/cli/commands/runCommand.ts @@ -83,8 +83,8 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext console.log(`[Command] push ${sourcePath} -> ${destinationDatabasePath}`); await core.serviceModules.storageAccess.writeFileAuto(destinationDatabasePath, toArrayBuffer(sourceData), { - mtime: sourceStat.mtimeMs, - ctime: sourceStat.ctimeMs, + mtime: Math.floor(sourceStat.mtimeMs), + ctime: Math.floor(sourceStat.ctimeMs), }); const destinationPathWithPrefix = destinationDatabasePath as FilePathWithPrefix; const stored = await core.serviceModules.fileHandler.storeFileToDB(destinationPathWithPrefix, true); From a4d5ef4620a64b53064eb9cfe146684f7254c29b Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 31 Mar 2026 21:39:47 +1100 Subject: [PATCH 03/73] =?UTF-8?q?cli:=20implement=20daemon=20startup=20seq?= =?UTF-8?q?uence=20and=20CouchDB=E2=86=92local=20sync?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add daemon command to help text and --interval/-i flag for polling mode - Capture original sync settings before suspendAllSync() clobbers them - Implement daemon startup: mirror scan → restore settings → applySettings() which triggers the full suspend/resume lifecycle and starts the _changes feed - Guard processSynchroniseResult no-op to non-daemon commands so default handler writes incoming CouchDB changes to the local filesystem - Polling mode: restore settings + clearInterval-safe try/catch error handling - Warn when both liveSync and syncOnStart are false after restore (no-op config) - Fix: only block indefinitely if daemon startup succeeded --- .../cli/adapters/NodeFileSystemAdapter.ts | 1 + .../cli/commands/daemonCommand.unit.spec.ts | 312 ++++++++++++++++++ src/apps/cli/commands/runCommand.ts | 90 +++++ src/apps/cli/commands/types.ts | 4 + src/apps/cli/main.ts | 122 +++++-- src/apps/cli/main.unit.spec.ts | 63 ++++ 6 files changed, 567 insertions(+), 25 deletions(-) create mode 100644 src/apps/cli/commands/daemonCommand.unit.spec.ts diff --git a/src/apps/cli/adapters/NodeFileSystemAdapter.ts b/src/apps/cli/adapters/NodeFileSystemAdapter.ts index b90ad73..1593cda 100644 --- a/src/apps/cli/adapters/NodeFileSystemAdapter.ts +++ b/src/apps/cli/adapters/NodeFileSystemAdapter.ts @@ -112,6 +112,7 @@ export class NodeFileSystemAdapter implements IFileSystemAdapter ({ + performFullScan: vi.fn(async () => true), +})); + +// Mock UnresolvedErrorManager to avoid event-hub side effects. +vi.mock("@lib/services/base/UnresolvedErrorManager", () => ({ + UnresolvedErrorManager: class UnresolvedErrorManager { + showError() {} + clearError() {} + clearErrors() {} + }, +})); + +import * as offlineScanner from "@lib/serviceFeatures/offlineScanner"; + +function createCoreMock() { + return { + services: { + control: { + activated: Promise.resolve(), + applySettings: vi.fn(async () => {}), + }, + setting: { + applyPartial: vi.fn(async () => {}), + currentSettings: vi.fn(() => ({ liveSync: true, syncOnStart: false })), + }, + replication: { + replicate: vi.fn(async () => true), + }, + appLifecycle: { + onUnload: { + addHandler: vi.fn(), + }, + }, + }, + serviceModules: { + fileHandler: { + dbToStorage: vi.fn(async () => true), + storeFileToDB: vi.fn(async () => true), + }, + storageAccess: { + readFileAuto: vi.fn(async () => ""), + writeFileAuto: vi.fn(async () => {}), + }, + databaseFileAccess: { + fetch: vi.fn(async () => undefined), + }, + }, + } as any; +} + +function makeDaemonOptions(interval?: number): CLIOptions { + return { + command: "daemon", + commandArgs: [], + databasePath: "/tmp/vault", + verbose: false, + force: false, + interval, + }; +} + +const baseContext = { + vaultPath: "/tmp/vault", + settingsPath: "/tmp/vault/.livesync/settings.json", + originalSyncSettings: { + liveSync: true, + syncOnStart: false, + periodicReplication: false, + syncOnSave: false, + syncOnEditorSave: false, + syncOnFileOpen: false, + syncAfterMerge: false, + }, +} as any; + +describe("daemon command", () => { + beforeEach(() => { + vi.restoreAllMocks(); + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("calls performFullScan during startup", async () => { + const core = createCoreMock(); + vi.mocked(offlineScanner.performFullScan).mockResolvedValue(true); + + await runCommand(makeDaemonOptions(), { ...baseContext, core }); + + expect(offlineScanner.performFullScan).toHaveBeenCalledTimes(1); + }); + + it("returns false when performFullScan fails", async () => { + const core = createCoreMock(); + vi.mocked(offlineScanner.performFullScan).mockResolvedValue(false); + + const result = await runCommand(makeDaemonOptions(), { ...baseContext, core }); + + expect(result).toBe(false); + }); + + it("polling mode: calls setTimeout when interval option is set", async () => { + const core = createCoreMock(); + vi.mocked(offlineScanner.performFullScan).mockResolvedValue(true); + const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout"); + + await runCommand(makeDaemonOptions(30), { ...baseContext, core }); + + expect(setTimeoutSpy).toHaveBeenCalledTimes(1); + // Interval should be in milliseconds (30s → 30000ms) + expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 30000); + }); + + it("polling mode: applies settings with suspendFileWatching=false before setting interval", async () => { + const core = createCoreMock(); + vi.mocked(offlineScanner.performFullScan).mockResolvedValue(true); + + await runCommand(makeDaemonOptions(10), { ...baseContext, core }); + + expect(core.services.setting.applyPartial).toHaveBeenCalledWith( + expect.objectContaining({ suspendFileWatching: false }), + true + ); + expect(core.services.control.applySettings).toHaveBeenCalledTimes(1); + }); + + it("liveSync mode: calls applyPartial and applySettings", async () => { + const core = createCoreMock(); + vi.mocked(offlineScanner.performFullScan).mockResolvedValue(true); + + await runCommand(makeDaemonOptions(), { ...baseContext, core }); + + expect(core.services.setting.applyPartial).toHaveBeenCalledWith( + expect.objectContaining({ + ...baseContext.originalSyncSettings, + suspendFileWatching: false, + }), + true + ); + expect(core.services.control.applySettings).toHaveBeenCalledTimes(1); + }); + + it("liveSync mode: logs warning when both liveSync and syncOnStart are false", async () => { + const core = createCoreMock(); + core.services.setting.currentSettings = vi.fn(() => ({ + liveSync: false, + syncOnStart: false, + })); + vi.mocked(offlineScanner.performFullScan).mockResolvedValue(true); + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + + const result = await runCommand(makeDaemonOptions(), { ...baseContext, core }); + + expect(result).toBe(true); + const warningCalls = consoleSpy.mock.calls.filter( + (args) => typeof args[0] === "string" && args[0].includes("liveSync and syncOnStart are both disabled") + ); + expect(warningCalls.length).toBeGreaterThan(0); + }); + + it("liveSync mode: no warning when liveSync is true", async () => { + const core = createCoreMock(); + core.services.setting.currentSettings = vi.fn(() => ({ + liveSync: true, + syncOnStart: false, + })); + vi.mocked(offlineScanner.performFullScan).mockResolvedValue(true); + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + + await runCommand(makeDaemonOptions(), { ...baseContext, core }); + + const warningCalls = consoleSpy.mock.calls.filter( + (args) => typeof args[0] === "string" && args[0].includes("liveSync and syncOnStart are both disabled") + ); + expect(warningCalls.length).toBe(0); + }); + + it("calls replicate before performFullScan", async () => { + const core = createCoreMock(); + const callOrder: string[] = []; + core.services.replication.replicate = vi.fn(async () => { + callOrder.push("replicate"); + return true; + }); + vi.mocked(offlineScanner.performFullScan).mockImplementation(async () => { + callOrder.push("performFullScan"); + return true; + }); + + await runCommand(makeDaemonOptions(), { ...baseContext, core }); + + expect(callOrder).toEqual(["replicate", "performFullScan"]); + }); + + it("returns false when initial replication fails", async () => { + const core = createCoreMock(); + core.services.replication.replicate = vi.fn(async () => false); + vi.mocked(offlineScanner.performFullScan).mockClear(); + + const result = await runCommand(makeDaemonOptions(), { ...baseContext, core }); + + expect(result).toBe(false); + // performFullScan should NOT have been called + expect(offlineScanner.performFullScan).not.toHaveBeenCalled(); + }); + + it("polling mode: registers onUnload handler that clears timeout", async () => { + const core = createCoreMock(); + vi.mocked(offlineScanner.performFullScan).mockResolvedValue(true); + + await runCommand(makeDaemonOptions(10), { ...baseContext, core }); + + // onUnload handler should have been registered + expect(core.services.appLifecycle.onUnload.addHandler).toHaveBeenCalledTimes(1); + const handler = core.services.appLifecycle.onUnload.addHandler.mock.calls[0][0]; + + // Get the timeout ID that was created + const clearTimeoutSpy = vi.spyOn(globalThis, "clearTimeout"); + await handler(); + expect(clearTimeoutSpy).toHaveBeenCalledTimes(1); + }); + + it("polling backoff: interval escalates on failure, caps at 300000ms, then halves on recovery", async () => { + const core = createCoreMock(); + vi.mocked(offlineScanner.performFullScan).mockResolvedValue(true); + vi.spyOn(console, "error").mockImplementation(() => {}); + + // startup replicate (call 1) succeeds; poll calls 2–7 fail; call 8 succeeds. + let callCount = 0; + core.services.replication.replicate = vi.fn(async () => { + callCount++; + if (callCount === 1) return true; // initial startup replicate + if (callCount <= 7) throw new Error("network failure"); + return true; // recovery + }); + + const baseMs = 30 * 1000; + const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout"); + + await runCommand(makeDaemonOptions(30), { ...baseContext, core }); + + // After runCommand returns the first setTimeout has been scheduled. + // setTimeoutSpy.mock.calls[0] is the initial schedule (baseMs). + expect(setTimeoutSpy.mock.calls[0][1]).toBe(baseMs); + + // Advance through 6 failure polls. After each failure the next setTimeout + // should be scheduled with a larger (or capped) interval. + // formula: min(base * 2^n, 300000). base=30000ms. + // failure 1: 30000*2=60000, failure 2: 30000*4=120000, + // failure 3: 30000*8=240000, failure 4: 30000*16=480000→capped, 5→cap, 6→cap + const expectedIntervals = [ + baseMs * 2, // after failure 1: 60000 + baseMs * 4, // after failure 2: 120000 + baseMs * 8, // after failure 3: 240000 + 300_000, // after failure 4 (would be 480000, capped) + 300_000, // after failure 5 (cap) + 300_000, // after failure 6 (cap) + ]; + + for (const expected of expectedIntervals) { + const prevCallCount = setTimeoutSpy.mock.calls.length; + await vi.advanceTimersByTimeAsync(setTimeoutSpy.mock.calls[prevCallCount - 1][1] as number); + const newCallCount = setTimeoutSpy.mock.calls.length; + expect(newCallCount).toBeGreaterThan(prevCallCount); + expect(setTimeoutSpy.mock.calls[newCallCount - 1][1]).toBe(expected); + } + + // Now trigger the success poll — interval should halve each time toward base. + // After failure 6, consecutiveFailures=6, currentIntervalMs=300000. + // On success: consecutiveFailures=5, currentIntervalMs=150000. + const prevCallCount = setTimeoutSpy.mock.calls.length; + await vi.advanceTimersByTimeAsync(setTimeoutSpy.mock.calls[prevCallCount - 1][1] as number); + const afterSuccessCallCount = setTimeoutSpy.mock.calls.length; + expect(afterSuccessCallCount).toBeGreaterThan(prevCallCount); + // The interval after one success should be halved (300000 / 2 = 150000). + expect(setTimeoutSpy.mock.calls[afterSuccessCallCount - 1][1]).toBe(150_000); + }); + + it("polling error handling: replicate rejection is caught and console.error is called", async () => { + const core = createCoreMock(); + vi.mocked(offlineScanner.performFullScan).mockResolvedValue(true); + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + + // Make replicate succeed on the initial call (startup), then fail on the poll. + let callCount = 0; + core.services.replication.replicate = vi.fn(async () => { + callCount++; + if (callCount === 1) return true; // startup replicate + throw new Error("network failure"); + }); + + const intervalMs = 30 * 1000; + await runCommand(makeDaemonOptions(30), { ...baseContext, core }); + + // Advance time to trigger the first poll callback and flush its async work. + await vi.advanceTimersByTimeAsync(intervalMs); + + // No unhandled rejection — the error was caught internally. + const errorCalls = consoleSpy.mock.calls.filter( + (args) => typeof args[0] === "string" && args[0].includes("Poll error") + ); + expect(errorCalls.length).toBeGreaterThan(0); + }); +}); diff --git a/src/apps/cli/commands/runCommand.ts b/src/apps/cli/commands/runCommand.ts index e188c23..5e1af90 100644 --- a/src/apps/cli/commands/runCommand.ts +++ b/src/apps/cli/commands/runCommand.ts @@ -15,6 +15,96 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext await core.services.control.activated; if (options.command === "daemon") { + const log = (msg: unknown) => console.error(`[Daemon] ${msg}`); + + // Skip the config mismatch dialog — the daemon cannot resolve it interactively + // and the default "Dismiss" action would block replication. The daemon should + // accept whatever configuration the remote has. + await core.services.setting.applyPartial({ disableCheckingConfigMismatch: true }, true); + + // 1. Replicate CouchDB → local PouchDB so the mirror scan has content to work with. + log("Replicating from CouchDB..."); + const replResult = await core.services.replication.replicate(true); + if (!replResult) { + console.error("[Daemon] Initial CouchDB replication failed, cannot continue"); + return false; + } + log("CouchDB replication complete"); + + // 2. Mirror scan to reconcile PouchDB ↔ local filesystem. + const errorManager = new UnresolvedErrorManager(core.services.appLifecycle); + log("Running mirror scan..."); + const scanOk = await performFullScan(core as any, log, errorManager, false, true); + if (!scanOk) { + console.error("[Daemon] Mirror scan failed, cannot continue"); + return false; + } + log("Mirror scan complete"); + + // 3. Re-enable sync. + const restoreSyncSettings = async () => { + await core.services.setting.applyPartial({ + ...context.originalSyncSettings, + suspendFileWatching: false, + }, true); + // applySettings fires the full lifecycle: onSuspending → onResumed. + // ModuleReplicatorCouchDB starts continuous replication on onResumed + // via fireAndForget. + await core.services.control.applySettings(); + // Lifecycle events (onSuspending) may re-enable suspension flags. + // Clear them explicitly after the lifecycle completes. applyPartial + // with true is a direct store write — it does not re-trigger lifecycle. + await core.services.setting.applyPartial({ + suspendFileWatching: false, + suspendParseReplicationResult: false, + }, true); + }; + if (options.interval) { + log(`Polling mode: syncing every ${options.interval}s`); + await restoreSyncSettings(); + const baseIntervalMs = options.interval * 1000; + let currentIntervalMs = baseIntervalMs; + let consecutiveFailures = 0; + const maxIntervalMs = 5 * 60 * 1000; // 5 minutes cap + + const poll = async () => { + try { + await core.services.replication.replicate(true); + if (consecutiveFailures > 0) { + consecutiveFailures--; + currentIntervalMs = Math.max(currentIntervalMs / 2, baseIntervalMs); + log(`Replication recovered`); + } + } catch (err) { + consecutiveFailures++; + currentIntervalMs = Math.min(baseIntervalMs * Math.pow(2, consecutiveFailures), maxIntervalMs); + console.error(`[Daemon] Poll error (${consecutiveFailures} consecutive):`, err); + if (consecutiveFailures >= 5) { + console.error(`[Daemon] Warning: ${consecutiveFailures} consecutive failures, backing off to ${Math.round(currentIntervalMs / 1000)}s`); + } + } + pollTimer = setTimeout(poll, currentIntervalMs); + }; + let pollTimer: ReturnType = setTimeout(poll, currentIntervalMs); + core.services.appLifecycle.onUnload.addHandler(async () => { + clearTimeout(pollTimer); + return true; + }); + } else { + log("LiveSync mode: restoring sync settings and starting _changes feed"); + await restoreSyncSettings(); + // The applySettings() lifecycle fires onResumed → ModuleReplicatorCouchDB which + // starts continuous replication via fireAndForget(openReplication). Don't call + // openReplication directly — it races with the handler and causes dedup/termination. + log("LiveSync active"); + const currentSettings = core.services.setting.currentSettings(); + if (!currentSettings.liveSync && !currentSettings.syncOnStart) { + console.error("[Daemon] Warning: liveSync and syncOnStart are both disabled in settings. " + + "No sync will occur. Set liveSync=true in your settings file for continuous sync, " + + "or use --interval for polling mode."); + } + } + return true; } diff --git a/src/apps/cli/commands/types.ts b/src/apps/cli/commands/types.ts index f63f751..ca01152 100644 --- a/src/apps/cli/commands/types.ts +++ b/src/apps/cli/commands/types.ts @@ -1,5 +1,6 @@ import { LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; import { ServiceContext } from "@lib/services/base/ServiceBase"; +import type { ObsidianLiveSyncSettings } from "@lib/common/types"; export type CLICommand = | "daemon" @@ -29,15 +30,18 @@ export interface CLIOptions { force?: boolean; command: CLICommand; commandArgs: string[]; + interval?: number; } export interface CLICommandContext { databasePath: string; core: LiveSyncBaseCore; settingsPath: string; + originalSyncSettings: Pick; } export const VALID_COMMANDS = new Set([ + "daemon", "sync", "p2p-peers", "p2p-sync", diff --git a/src/apps/cli/main.ts b/src/apps/cli/main.ts index 97483d5..07ef657 100644 --- a/src/apps/cli/main.ts +++ b/src/apps/cli/main.ts @@ -43,7 +43,8 @@ Arguments: database-path Path to the local database directory Commands: - sync Run one replication cycle and exit + daemon (default) Run mirror scan then continuously sync CouchDB <-> local filesystem + sync Run one replication cycle and exit p2p-peers Show discovered peers as [peer]\t\t p2p-sync Sync with the specified peer-id or peer-name @@ -60,24 +61,30 @@ Commands: rm Mark a file as deleted in local database resolve Resolve conflicts by keeping and deleting others mirror [vault-path] Mirror database contents to the local file system (vault-path defaults to database-path) + +Options: + --interval , -i (daemon only) Poll CouchDB every N seconds instead of using the _changes feed + Examples: - livesync-cli ./my-database sync - livesync-cli ./my-database p2p-peers 5 - livesync-cli ./my-database p2p-sync my-peer-name 15 - livesync-cli ./my-database p2p-host - livesync-cli ./my-database --settings ./custom-settings.json push ./note.md folder/note.md - livesync-cli ./my-database pull folder/note.md ./exports/note.md - livesync-cli ./my-database pull-rev folder/note.md ./exports/note.old.md 3-abcdef - livesync-cli ./my-database setup "obsidian://setuplivesync?settings=..." - echo "Hello" | livesync-cli ./my-database put notes/hello.md - livesync-cli ./my-database cat notes/hello.md - livesync-cli ./my-database cat-rev notes/hello.md 3-abcdef - livesync-cli ./my-database ls notes/ - livesync-cli ./my-database info notes/hello.md - livesync-cli ./my-database rm notes/hello.md - livesync-cli ./my-database resolve notes/hello.md 3-abcdef - livesync-cli init-settings ./data.json - livesync-cli ./my-database --verbose + livesync-cli ./my-database Run daemon (LiveSync mode) + livesync-cli ./my-database --interval 30 Run daemon (polling every 30s) + livesync-cli ./my-database sync + livesync-cli ./my-database p2p-peers 5 + livesync-cli ./my-database p2p-sync my-peer-name 15 + livesync-cli ./my-database p2p-host + livesync-cli ./my-database --settings ./custom-settings.json push ./note.md folder/note.md + livesync-cli ./my-database pull folder/note.md ./exports/note.md + livesync-cli ./my-database pull-rev folder/note.md ./exports/note.old.md 3-abcdef + livesync-cli ./my-database setup "obsidian://setuplivesync?settings=..." + echo "Hello" | livesync-cli ./my-database put notes/hello.md + livesync-cli ./my-database cat notes/hello.md + livesync-cli ./my-database cat-rev notes/hello.md 3-abcdef + livesync-cli ./my-database ls notes/ + livesync-cli ./my-database info notes/hello.md + livesync-cli ./my-database rm notes/hello.md + livesync-cli ./my-database resolve notes/hello.md 3-abcdef + livesync-cli init-settings ./data.json + livesync-cli ./my-database --verbose `); } @@ -94,6 +101,7 @@ export function parseArgs(): CLIOptions { let verbose = false; let debug = false; let force = false; + let interval: number | undefined; let command: CLICommand = "daemon"; const commandArgs: string[] = []; @@ -110,6 +118,21 @@ export function parseArgs(): CLIOptions { settingsPath = args[i]; break; } + case "--interval": + case "-i": { + i++; + if (!args[i]) { + console.error(`Error: Missing value for ${token}`); + process.exit(1); + } + const n = parseInt(args[i], 10); + if (!Number.isInteger(n) || n <= 0) { + console.error(`Error: --interval requires a positive integer, got '${args[i]}'`); + process.exit(1); + } + interval = n; + break; + } case "--debug": case "-d": // debugging automatically enables verbose logging, as it is intended for debugging issues. @@ -164,6 +187,7 @@ export function parseArgs(): CLIOptions { force, command, commandArgs, + interval, }; } @@ -248,6 +272,20 @@ export async function main() { infoLog(`Settings: ${settingsPath}`); infoLog(""); + // For daemon and mirror mode, load ignore rules before the core is constructed so that + // chokidar's ignored option is populated when beginWatch() fires during onLoad(). + const watchEnabled = options.command === "daemon"; + const vaultPathForIgnoreRules = + options.command === "mirror" && options.commandArgs[0] + ? path.resolve(options.commandArgs[0]) + : databasePath; + let ignoreRules: IgnoreRules | undefined; + if (options.command === "daemon" || options.command === "mirror") { + ignoreRules = new IgnoreRules(vaultPathForIgnoreRules); + await ignoreRules.load(); + } + + // Create service context and hub const context = new NodeServiceContext(databasePath); const serviceHubInstance = new NodeServiceHub(databasePath, context); @@ -278,11 +316,14 @@ export async function main() { } console.error(`${prefix} ${message}`); }); - // Prevent replication result to be processed automatically. - serviceHubInstance.replication.processSynchroniseResult.addHandler(async () => { - console.error(`[Info] Replication result received, but not processed automatically in CLI mode.`); - return await Promise.resolve(true); - }, -100); + // Prevent replication result from being processed automatically in non-daemon commands. + // In daemon mode the default handler must run so changes are applied to the filesystem. + if (options.command !== "daemon") { + serviceHubInstance.replication.processSynchroniseResult.addHandler(async () => { + console.error(`[Info] Replication result received, but not processed automatically in CLI mode.`); + return await Promise.resolve(true); + }, -100); + } // Setup settings handlers const settingService = serviceHubInstance.setting; @@ -366,6 +407,25 @@ export async function main() { process.on("SIGINT", () => shutdown("SIGINT")); process.on("SIGTERM", () => shutdown("SIGTERM")); + // Save the settings file before any lifecycle events can mutate and persist them. + // suspendAllSync and other lifecycle hooks clobber sync settings in memory, and + // various code paths persist the clobbered state to disk. We restore on shutdown. + const settingsBackup = await fs.readFile(settingsPath, "utf-8").catch(() => null); + + // Restore settings file on any exit to undo lifecycle mutations. + // Write to a temp path first so a crash mid-write doesn't leave a truncated file. + process.on("exit", () => { + if (settingsBackup) { + const tmpPath = settingsPath + ".tmp"; + try { + require("fs").writeFileSync(tmpPath, settingsBackup, "utf-8"); + require("fs").renameSync(tmpPath, settingsPath); + } catch (err) { + console.error("[Settings] Failed to restore settings on exit:", err); + } + } + }); + // Start the core try { infoLog(`[Starting] Initializing LiveSync...`); @@ -375,6 +435,18 @@ export async function main() { console.error(`[Error] Failed to initialize LiveSync`); process.exit(1); } + // Capture sync settings before suspendAllSync() clobbers them. + // Used by daemon mode to restore the correct sync behaviour after the mirror scan. + const settingsBeforeSuspend = core.services.setting.currentSettings(); + const originalSyncSettings = { + liveSync: settingsBeforeSuspend.liveSync, + syncOnStart: settingsBeforeSuspend.syncOnStart, + periodicReplication: settingsBeforeSuspend.periodicReplication, + syncOnSave: settingsBeforeSuspend.syncOnSave, + syncOnEditorSave: settingsBeforeSuspend.syncOnEditorSave, + syncOnFileOpen: settingsBeforeSuspend.syncOnFileOpen, + syncAfterMerge: settingsBeforeSuspend.syncAfterMerge, + }; await core.services.setting.suspendAllSync(); await core.services.control.onReady(); @@ -400,7 +472,7 @@ export async function main() { infoLog(""); } - const result = await runCommand(options, { databasePath, core, settingsPath }); + const result = await runCommand(options, { databasePath, core, settingsPath, originalSyncSettings }); if (!result) { console.error(`[Error] Command '${options.command}' failed`); process.exitCode = 1; @@ -408,7 +480,7 @@ export async function main() { infoLog(`[Done] Command '${options.command}' completed`); } - if (options.command === "daemon") { + if (options.command === "daemon" && result) { // Keep the process running await new Promise(() => {}); } else { diff --git a/src/apps/cli/main.unit.spec.ts b/src/apps/cli/main.unit.spec.ts index 4c35ae9..2b70a44 100644 --- a/src/apps/cli/main.unit.spec.ts +++ b/src/apps/cli/main.unit.spec.ts @@ -85,4 +85,67 @@ describe("CLI parseArgs", () => { expect(parsed.command).toBe("p2p-host"); expect(parsed.commandArgs).toEqual([]); }); + + it("parses --interval flag with valid integer", () => { + process.argv = ["node", "livesync-cli", "./vault", "--interval", "30"]; + const parsed = parseArgs(); + expect(parsed.command).toBe("daemon"); + expect(parsed.interval).toBe(30); + }); + + it("parses -i shorthand for --interval", () => { + process.argv = ["node", "livesync-cli", "./vault", "-i", "10"]; + const parsed = parseArgs(); + expect(parsed.interval).toBe(10); + }); + + it("exits 1 when --interval has no value", () => { + process.argv = ["node", "livesync-cli", "./vault", "--interval"]; + const exitMock = mockProcessExit(); + vi.spyOn(console, "error").mockImplementation(() => {}); + expect(() => parseArgs()).toThrowError("__EXIT__:1"); + expect(exitMock).toHaveBeenCalledWith(1); + }); + + it("exits 1 when --interval is not a positive integer", () => { + process.argv = ["node", "livesync-cli", "./vault", "--interval", "0"]; + const exitMock = mockProcessExit(); + vi.spyOn(console, "error").mockImplementation(() => {}); + expect(() => parseArgs()).toThrowError("__EXIT__:1"); + expect(exitMock).toHaveBeenCalledWith(1); + }); + + it("exits 1 when --interval is negative", () => { + process.argv = ["node", "livesync-cli", "./vault", "--interval", "-5"]; + const exitMock = mockProcessExit(); + vi.spyOn(console, "error").mockImplementation(() => {}); + expect(() => parseArgs()).toThrowError("__EXIT__:1"); + }); + + it("exits 1 when --interval is not numeric", () => { + process.argv = ["node", "livesync-cli", "./vault", "--interval", "abc"]; + const exitMock = mockProcessExit(); + vi.spyOn(console, "error").mockImplementation(() => {}); + expect(() => parseArgs()).toThrowError("__EXIT__:1"); + }); + + it("parses explicit daemon command", () => { + process.argv = ["node", "livesync-cli", "./vault", "daemon"]; + const parsed = parseArgs(); + expect(parsed.command).toBe("daemon"); + expect(parsed.databasePath).toBe("./vault"); + }); + + it("defaults to daemon when no command specified", () => { + process.argv = ["node", "livesync-cli", "./vault"]; + const parsed = parseArgs(); + expect(parsed.command).toBe("daemon"); + }); + + it("parses explicit daemon command with --interval", () => { + process.argv = ["node", "livesync-cli", "./vault", "daemon", "--interval", "30"]; + const parsed = parseArgs(); + expect(parsed.command).toBe("daemon"); + expect(parsed.interval).toBe(30); + }); }); From e6ae516493063153106180f2d19d9b947564b060 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 31 Mar 2026 21:48:42 +1100 Subject: [PATCH 04/73] =?UTF-8?q?cli:=20implement=20local=E2=86=92CouchDB?= =?UTF-8?q?=20file=20watching=20via=20chokidar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add chokidar ^4.0.0 as dependency (root package.json, runtime-package.json) - Mark chokidar as external in vite.config.ts (not bundled, loaded at runtime) - Implement CLIWatchAdapter.beginWatch() with chokidar: - ignoreInitial: true (startup files handled by mirror scan) - awaitWriteFinish to prevent partial-write events - Excludes dotfiles and .livesync/ directory at watcher level - Maps add/change/unlink/addDir/unlinkDir to IStorageEventWatchHandlers - Fatal error handler: logs clearly and releases watcher resources - Add close() to CLIWatchAdapter, StorageEventManagerCLI for clean shutdown - Register onUnload hook in CLIServiceModules to close watcher on shutdown --- package-lock.json | 36 ++--- package.json | 1 + .../cli/adapters/NodeFileSystemAdapter.ts | 6 - .../managers/CLIStorageEventManagerAdapter.ts | 122 ++++++++++++++--- ...CLIStorageEventManagerAdapter.unit.spec.ts | 126 ++++++++++++++++++ .../cli/managers/StorageEventManagerCLI.ts | 12 +- src/apps/cli/runtime-package.json | 1 + .../cli/serviceModules/CLIServiceModules.ts | 9 +- src/apps/cli/vite.config.ts | 41 +++++- 9 files changed, 296 insertions(+), 58 deletions(-) create mode 100644 src/apps/cli/managers/CLIStorageEventManagerAdapter.unit.spec.ts diff --git a/package-lock.json b/package-lock.json index 0f1f682..56a150c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@smithy/protocol-http": "^5.3.9", "@smithy/querystring-builder": "^4.2.9", "@trystero-p2p/nostr": "^0.23.0", + "chokidar": "^4.0.0", "commander": "^14.0.3", "diff-match-patch": "^1.0.5", "fflate": "^0.8.2", @@ -984,7 +985,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -2378,7 +2378,8 @@ "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@minhducsun2002/leb128": { "version": "1.0.0", @@ -4224,7 +4225,6 @@ "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "deepmerge": "^4.3.1", @@ -4738,7 +4738,6 @@ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", @@ -4943,7 +4942,6 @@ "integrity": "sha512-gjjrFC4+kPVK/fN9URDJWrssU5Gqh8Az8pKG/NSfQ2V+ky8b/y1BgBg0Ug13+hOGp5pzInonmGRPn7vOgSLgzA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@blazediff/core": "1.9.1", "@vitest/mocker": "4.1.1", @@ -4967,7 +4965,6 @@ "integrity": "sha512-dtVSBZZha2k/7P7EAXXrEAoxuIKl8Yv9f2Dk4GN/DGfmhf4DQvkvu+57okR2wq/gan1xppKjL/aBxK/kbYrbGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/browser": "4.1.1", "@vitest/mocker": "4.1.1", @@ -5409,7 +5406,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6152,7 +6148,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -6385,7 +6380,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, "license": "MIT", "dependencies": { "readdirp": "^4.0.1" @@ -6648,7 +6642,8 @@ "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -7441,7 +7436,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -7555,7 +7549,6 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -9695,7 +9688,6 @@ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -11203,7 +11195,6 @@ "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "playwright-core": "1.58.2" }, @@ -11270,7 +11261,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -11296,7 +11286,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "lilconfig": "^3.1.1" }, @@ -11943,7 +11932,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 14.18.0" @@ -12956,7 +12944,8 @@ "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/sublevel-pouchdb": { "version": "9.0.0", @@ -13025,7 +13014,6 @@ "integrity": "sha512-0a/huwc8e2es+7KFi70esqsReRfRbrT8h1cJSY/+z1lF0yKM6TT+//HYu28Yxstr50H7ifaqZRDGd0KuKDxP7w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -13336,7 +13324,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -13455,7 +13442,6 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -14086,7 +14072,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14236,7 +14221,6 @@ "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -14873,7 +14857,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -14907,7 +14890,6 @@ "integrity": "sha512-yF+o4POL41rpAzj5KVILUxm1GCjKnELvaqmU9TLLUbMfDzuN0UpUR9uaDs+mCtjPe+uYPksXDRLQGGPvj1cTmA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.1.1", "@vitest/mocker": "4.1.1", @@ -15015,7 +14997,8 @@ "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/wait-port": { "version": "1.1.0", @@ -15667,7 +15650,6 @@ "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "dev": true, "license": "ISC", - "peer": true, "bin": { "yaml": "bin.mjs" }, diff --git a/package.json b/package.json index 479780a..92eff77 100644 --- a/package.json +++ b/package.json @@ -127,6 +127,7 @@ }, "dependencies": { "@aws-sdk/client-s3": "^3.808.0", + "chokidar": "^4.0.0", "@smithy/fetch-http-handler": "^5.3.10", "@smithy/md5-js": "^4.2.9", "@smithy/middleware-apply-body-checksum": "^4.3.9", diff --git a/src/apps/cli/adapters/NodeFileSystemAdapter.ts b/src/apps/cli/adapters/NodeFileSystemAdapter.ts index 1593cda..4908a34 100644 --- a/src/apps/cli/adapters/NodeFileSystemAdapter.ts +++ b/src/apps/cli/adapters/NodeFileSystemAdapter.ts @@ -39,12 +39,6 @@ export class NodeFileSystemAdapter implements IFileSystemAdapter { const pathStr = this.normalisePath(p); - - const cached = this.fileCache.get(pathStr); - if (cached) { - return cached; - } - return await this.refreshFile(pathStr); } diff --git a/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts b/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts index 1334b6a..ea6e31e 100644 --- a/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts +++ b/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts @@ -11,8 +11,10 @@ import type { } from "@lib/managers/adapters"; import type { FileEventItemSentinel } from "@lib/managers/StorageEventManager"; import type { NodeFile, NodeFolder } from "../adapters/NodeTypes"; +import type { Stats } from "fs"; import * as fs from "fs/promises"; import * as path from "path"; +import { watch as chokidarWatch, type FSWatcher } from "chokidar"; /** * CLI-specific type guard adapter @@ -56,22 +58,11 @@ class CLIPersistenceAdapter implements IStorageEventPersistenceAdapter { } /** - * CLI-specific status adapter (console logging) + * CLI-specific status adapter (no-op — daemon uses journald for status) */ class CLIStatusAdapter implements IStorageEventStatusAdapter { - private lastUpdate = 0; - private updateInterval = 5000; // Update every 5 seconds - - updateStatus(status: { batched: number; processing: number; totalQueued: number }): void { - const now = Date.now(); - if (now - this.lastUpdate > this.updateInterval) { - if (status.totalQueued > 0 || status.processing > 0) { - // console.log( - // `[StorageEventManager] Batched: ${status.batched}, Processing: ${status.processing}, Total Queued: ${status.totalQueued}` - // ); - } - this.lastUpdate = now; - } + updateStatus(_status: { batched: number; processing: number; totalQueued: number }): void { + // intentional no-op } } @@ -100,15 +91,100 @@ class CLIConverterAdapter implements IStorageEventConverterAdapter { } /** - * CLI-specific watch adapter (optional file watching with chokidar) + * CLI-specific watch adapter using chokidar for real-time filesystem monitoring. */ class CLIWatchAdapter implements IStorageEventWatchAdapter { - constructor(private basePath: string) {} + private _watcher: FSWatcher | undefined; + + constructor(private basePath: string, private watchEnabled: boolean = false) {} + + private _toNodeFile(filePath: string, stats: Stats | undefined): NodeFile { + return { + path: path.relative(this.basePath, filePath) as FilePath, + stat: { + ctime: stats?.ctimeMs ?? Date.now(), + mtime: stats?.mtimeMs ?? Date.now(), + size: stats?.size ?? 0, + type: "file", + }, + }; + } + + private _toNodeFileStub(filePath: string): NodeFile { + return { + path: path.relative(this.basePath, filePath) as FilePath, + stat: { + ctime: Date.now(), + mtime: Date.now(), + size: 0, + type: "file", + }, + }; + } + + private _toNodeFolder(dirPath: string): NodeFolder { + return { + path: path.relative(this.basePath, dirPath) as FilePath, + isFolder: true, + }; + } async beginWatch(handlers: IStorageEventWatchHandlers): Promise { - // File watching is not activated in the CLI. - // Because the CLI is designed for push/pull operations, not real-time sync. - // console.error("[CLIWatchAdapter] File watching is not enabled in CLI version"); + if (!this.watchEnabled) return; + const watcher = chokidarWatch(this.basePath, { + ignored: [ + /(^|[/\\])\./, + ], + ignoreInitial: true, + persistent: true, + awaitWriteFinish: { + stabilityThreshold: 500, + pollInterval: 100, + }, + }); + + watcher.on("add", (filePath, stats) => { + const nodeFile = this._toNodeFile(filePath, stats); + handlers.onCreate(nodeFile); + }); + + watcher.on("change", (filePath, stats) => { + const nodeFile = this._toNodeFile(filePath, stats); + handlers.onChange(nodeFile); + }); + + watcher.on("unlink", (filePath) => { + const nodeFile = this._toNodeFileStub(filePath); + handlers.onDelete(nodeFile); + }); + + watcher.on("addDir", (dirPath) => { + const nodeFolder = this._toNodeFolder(dirPath); + handlers.onCreate(nodeFolder); + }); + + watcher.on("unlinkDir", (dirPath) => { + const nodeFolder = this._toNodeFolder(dirPath); + handlers.onDelete(nodeFolder); + }); + + watcher.on("error", (err) => { + console.error("[CLIWatchAdapter] Fatal watcher error — file watching stopped:", err); + console.error("[CLIWatchAdapter] Exiting for systemd restart."); + void watcher.close(); + this._watcher = undefined; + // Use exit(1) rather than SIGTERM so systemd Restart=on-failure engages. + process.exit(1); + }); + + await new Promise((resolve) => watcher.once("ready", resolve)); + this._watcher = watcher; + } + + close(): Promise { + if (this._watcher) { + return this._watcher.close(); + } return Promise.resolve(); } } @@ -123,11 +199,15 @@ export class CLIStorageEventManagerAdapter implements IStorageEventManagerAdapte readonly status: CLIStatusAdapter; readonly converter: CLIConverterAdapter; - constructor(basePath: string) { + constructor(basePath: string, watchEnabled: boolean = false) { this.typeGuard = new CLITypeGuardAdapter(); this.persistence = new CLIPersistenceAdapter(basePath); - this.watch = new CLIWatchAdapter(basePath); + this.watch = new CLIWatchAdapter(basePath, watchEnabled); this.status = new CLIStatusAdapter(); this.converter = new CLIConverterAdapter(); } + + close(): Promise { + return this.watch.close(); + } } diff --git a/src/apps/cli/managers/CLIStorageEventManagerAdapter.unit.spec.ts b/src/apps/cli/managers/CLIStorageEventManagerAdapter.unit.spec.ts new file mode 100644 index 0000000..edfb222 --- /dev/null +++ b/src/apps/cli/managers/CLIStorageEventManagerAdapter.unit.spec.ts @@ -0,0 +1,126 @@ +import { describe, expect, it, vi, beforeEach } from "vitest"; +import type { IStorageEventWatchHandlers } from "@lib/managers/adapters"; +import type { NodeFile } from "../adapters/NodeTypes"; + +// ── chokidar mock ────────────────────────────────────────────────────────────── +// Must be hoisted before imports that pull in chokidar. + +const mockWatcher = { + on: vi.fn().mockReturnThis(), + once: vi.fn((event: string, cb: () => void) => { + if (event === "ready") cb(); + return mockWatcher; + }), + close: vi.fn(() => Promise.resolve()), +}; + +vi.mock("chokidar", () => ({ + watch: vi.fn(() => mockWatcher), +})); + +import * as chokidar from "chokidar"; +import { CLIStorageEventManagerAdapter } from "./CLIStorageEventManagerAdapter"; + +// ── helpers ─────────────────────────────────────────────────────────────────── + +function makeHandlers(): IStorageEventWatchHandlers { + return { + onCreate: vi.fn(), + onChange: vi.fn(), + onDelete: vi.fn(), + onRename: vi.fn(), + } as any; +} + +// ── tests ───────────────────────────────────────────────────────────────────── + +describe("CLIStorageEventManagerAdapter", () => { + beforeEach(() => { + vi.clearAllMocks(); + // Restore the default once() behaviour (ready fires synchronously). + mockWatcher.once.mockImplementation((event: string, cb: () => void) => { + if (event === "ready") cb(); + return mockWatcher; + }); + }); + + it("beginWatch is no-op when watchEnabled=false", async () => { + const adapter = new CLIStorageEventManagerAdapter("/base", undefined, false); + const handlers = makeHandlers(); + + await adapter.watch.beginWatch(handlers); + + expect(chokidar.watch).not.toHaveBeenCalled(); + }); + + it("beginWatch calls chokidar.watch when watchEnabled=true", async () => { + const adapter = new CLIStorageEventManagerAdapter("/base", undefined, true); + const handlers = makeHandlers(); + + await adapter.watch.beginWatch(handlers); + + expect(chokidar.watch).toHaveBeenCalledTimes(1); + expect(chokidar.watch).toHaveBeenCalledWith( + "/base", + expect.objectContaining({ ignoreInitial: true }) + ); + }); + + it("add event produces NodeFile with correct relative path via onCreate", async () => { + const basePath = "/vault/base"; + const adapter = new CLIStorageEventManagerAdapter(basePath, undefined, true); + const handlers = makeHandlers(); + + await adapter.watch.beginWatch(handlers); + + // Find the callback registered for the "add" event. + const addCall = mockWatcher.on.mock.calls.find(([event]) => event === "add"); + expect(addCall).toBeDefined(); + const addCallback = addCall![1] as (filePath: string, stats: any) => void; + + const fakeStats = { ctimeMs: 1000, mtimeMs: 2000, size: 42 }; + addCallback(`${basePath}/subdir/note.md`, fakeStats); + + expect(handlers.onCreate).toHaveBeenCalledTimes(1); + const created = (handlers.onCreate as ReturnType).mock.calls[0][0] as NodeFile; + expect(created.path).toBe("subdir/note.md"); + expect(created.stat?.size).toBe(42); + }); + + it("close() calls watcher.close()", async () => { + const adapter = new CLIStorageEventManagerAdapter("/base", undefined, true); + const handlers = makeHandlers(); + + await adapter.watch.beginWatch(handlers); + await adapter.close(); + + expect(mockWatcher.close).toHaveBeenCalledTimes(1); + }); + + it("close() is safe when no watcher was started", async () => { + const adapter = new CLIStorageEventManagerAdapter("/base", undefined, false); + + // Should not throw. + await expect(adapter.close()).resolves.toBeUndefined(); + expect(mockWatcher.close).not.toHaveBeenCalled(); + }); + + it("error event triggers process.exit(1)", async () => { + const adapter = new CLIStorageEventManagerAdapter("/base", undefined, true); + const handlers = makeHandlers(); + + await adapter.watch.beginWatch(handlers); + + const processExitSpy = vi.spyOn(process, "exit").mockImplementation((() => {}) as any); + + const errorCall = mockWatcher.on.mock.calls.find(([event]) => event === "error"); + expect(errorCall).toBeDefined(); + const errorCallback = errorCall![1] as (err: Error) => void; + + errorCallback(new Error("disk failure")); + + expect(processExitSpy).toHaveBeenCalledWith(1); + + processExitSpy.mockRestore(); + }); +}); diff --git a/src/apps/cli/managers/StorageEventManagerCLI.ts b/src/apps/cli/managers/StorageEventManagerCLI.ts index d1f2504..c8edb3d 100644 --- a/src/apps/cli/managers/StorageEventManagerCLI.ts +++ b/src/apps/cli/managers/StorageEventManagerCLI.ts @@ -10,9 +10,10 @@ export class StorageEventManagerCLI extends StorageEventManagerBase, - dependencies: StorageEventManagerBaseDependencies + dependencies: StorageEventManagerBaseDependencies, + watchEnabled?: boolean ) { - const adapter = new CLIStorageEventManagerAdapter(basePath); + const adapter = new CLIStorageEventManagerAdapter(basePath, watchEnabled); super(adapter, dependencies); this.core = core; } @@ -25,4 +26,11 @@ export class StorageEventManagerCLI extends StorageEventManagerBase { + return this.adapter.close(); + } } diff --git a/src/apps/cli/runtime-package.json b/src/apps/cli/runtime-package.json index 5791992..305d966 100644 --- a/src/apps/cli/runtime-package.json +++ b/src/apps/cli/runtime-package.json @@ -4,6 +4,7 @@ "version": "0.0.0", "description": "Runtime dependencies for Self-hosted LiveSync CLI Docker image", "dependencies": { + "chokidar": "^4.0.0", "commander": "^14.0.3", "werift": "^0.22.9", "pouchdb-adapter-http": "^9.0.0", diff --git a/src/apps/cli/serviceModules/CLIServiceModules.ts b/src/apps/cli/serviceModules/CLIServiceModules.ts index 8cf0f40..b0e67ba 100644 --- a/src/apps/cli/serviceModules/CLIServiceModules.ts +++ b/src/apps/cli/serviceModules/CLIServiceModules.ts @@ -22,7 +22,8 @@ import type { ServiceModules } from "@lib/interfaces/ServiceModule"; export function initialiseServiceModulesCLI( basePath: string, core: LiveSyncBaseCore, - services: InjectableServiceHub + services: InjectableServiceHub, + watchEnabled: boolean = false, ): ServiceModules { const storageAccessManager = new StorageAccessManager(); @@ -42,6 +43,12 @@ export function initialiseServiceModulesCLI( vaultService: services.vault, storageAccessManager: storageAccessManager, APIService: services.API, + }, false); + + // Close the file watcher during graceful shutdown so the process can exit cleanly. + services.appLifecycle.onUnload.addHandler(async () => { + await storageEventManager.close(); + return true; }); // Storage access using CLI file system adapter diff --git a/src/apps/cli/vite.config.ts b/src/apps/cli/vite.config.ts index e78642c..6850a94 100644 --- a/src/apps/cli/vite.config.ts +++ b/src/apps/cli/vite.config.ts @@ -11,11 +11,50 @@ const defaultExternal = [ "crypto", "pouchdb-adapter-leveldb", "commander", + "chokidar", "punycode", "werift", ]; +// Polyfill FileReader at the very top of the CJS bundle. octagonal-wheels uses +// FileReader for base64 conversion when Uint8Array.toBase64 (TC39 proposal) is +// unavailable. Node.js has neither, so we inject a minimal FileReader shim before +// any module-scope code evaluates. +const fileReaderPolyfillBanner = ` +if (typeof globalThis.FileReader === "undefined") { + globalThis.FileReader = class FileReader { + constructor() { this.result = null; this.onload = null; this.onerror = null; } + readAsDataURL(blob) { + blob.arrayBuffer().then((buf) => { + var b64 = require("buffer").Buffer.from(buf).toString("base64"); + this.result = "data:" + (blob.type || "application/octet-stream") + ";base64," + b64; + if (this.onload) this.onload({ target: this }); + }).catch((err) => { if (this.onerror) this.onerror({ target: this, error: err }); }); + } + }; +} +`; + +function injectBanner(): import("vite").Plugin { + return { + name: "inject-banner", + generateBundle(_options, bundle) { + for (const chunk of Object.values(bundle)) { + if (chunk.type === "chunk" && chunk.fileName.startsWith("entrypoint")) { + // Insert after the shebang line if present, otherwise at the top. + if (chunk.code.startsWith("#!")) { + const newline = chunk.code.indexOf("\n"); + chunk.code = chunk.code.slice(0, newline + 1) + fileReaderPolyfillBanner + chunk.code.slice(newline + 1); + } else { + chunk.code = fileReaderPolyfillBanner + chunk.code; + } + } + } + }, + }; +} + export default defineConfig({ - plugins: [svelte()], + plugins: [svelte(), injectBanner()], resolve: { alias: { "@lib/worker/bgWorker.ts": "../../lib/src/worker/bgWorker.mock.ts", From c0ad8ee15af8dfb19ff2e1a10fbf6d91abe7d651 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 1 Apr 2026 19:22:24 +1100 Subject: [PATCH 05/73] cli: add configurable ignore rules and deployment artifacts IgnoreRules (src/apps/cli/serviceModules/IgnoreRules.ts): - Reads .livesync/ignore for user-defined glob patterns - Applies gitignore matchBase semantics: patterns without / get **/ prefix, patterns ending with / get ** appended for directory contents - Supports `import: .gitignore` directive to merge gitignore patterns - Rejects negation patterns with a warning (not fully supportable) - Integrated into both daemon and mirror commands via isTargetFile handler Wiring: - IgnoreRules loaded before LiveSyncBaseCore construction so beginWatch() receives rules when it fires during onLoad/onFirstInitialise - Passed through initialiseServiceModulesCLI -> StorageEventManagerCLI -> CLIStorageEventManagerAdapter -> CLIWatchAdapter Deployment: - src/apps/cli/deploy/livesync-cli.service - systemd unit template - src/apps/cli/deploy/install.sh - user/system install script Testing: - src/apps/cli/test/test-daemon-linux.sh - e2e tests for ignore rules - src/apps/cli/serviceModules/IgnoreRules.unit.spec.ts - 15 unit tests - src/apps/cli/commands/daemonCommand.unit.spec.ts - 7 unit tests --- package-lock.json | 25 ++- package.json | 4 +- src/apps/cli/README.md | 140 ++++++++++--- src/apps/cli/commands/runCommand.ts | 1 + src/apps/cli/deploy/install.sh | 187 ++++++++++++++++++ src/apps/cli/deploy/livesync-cli.service | 17 ++ src/apps/cli/main.ts | 31 ++- .../managers/CLIStorageEventManagerAdapter.ts | 36 ++-- .../cli/managers/StorageEventManagerCLI.ts | 4 +- .../cli/serviceModules/CLIServiceModules.ts | 4 +- src/apps/cli/serviceModules/IgnoreRules.ts | 129 ++++++++++++ .../serviceModules/IgnoreRules.unit.spec.ts | 172 ++++++++++++++++ src/apps/cli/test/test-daemon-linux.sh | 166 ++++++++++++++++ src/apps/cli/vite.config.ts | 4 + 14 files changed, 856 insertions(+), 64 deletions(-) create mode 100755 src/apps/cli/deploy/install.sh create mode 100644 src/apps/cli/deploy/livesync-cli.service create mode 100644 src/apps/cli/serviceModules/IgnoreRules.ts create mode 100644 src/apps/cli/serviceModules/IgnoreRules.unit.spec.ts create mode 100755 src/apps/cli/test/test-daemon-linux.sh diff --git a/package-lock.json b/package-lock.json index 56a150c..4c2fba3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "fflate": "^0.8.2", "idb": "^8.0.3", "markdown-it": "^14.1.1", + "micromatch": "^4.0.0", "minimatch": "^10.2.2", "octagonal-wheels": "^0.1.45", "pouchdb-adapter-leveldb": "^9.0.0", @@ -39,6 +40,7 @@ "@types/deno": "^2.5.0", "@types/diff-match-patch": "^1.0.36", "@types/markdown-it": "^14.1.2", + "@types/micromatch": "^4.0.10", "@types/node": "^24.10.13", "@types/pouchdb": "^6.4.2", "@types/pouchdb-adapter-http": "^6.1.6", @@ -4298,6 +4300,13 @@ "@babel/types": "^7.0.0" } }, + "node_modules/@types/braces": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.5.tgz", + "integrity": "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -4417,6 +4426,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/micromatch": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.10.tgz", + "integrity": "sha512-5jOhFDElqr4DKTrTEbnW8DZ4Hz5LRUEmyrGpCMrD/NphYv3nUnaF08xmSLx1rGGnyEs/kFnhiw6dCgcDqMr5PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/braces": "*" + } + }, "node_modules/@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", @@ -6119,7 +6138,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -8248,7 +8266,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -9351,7 +9368,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -10401,7 +10417,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -11111,7 +11126,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -13345,7 +13359,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" diff --git a/package.json b/package.json index 92eff77..bc96572 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "@types/deno": "^2.5.0", "@types/diff-match-patch": "^1.0.36", "@types/markdown-it": "^14.1.2", + "@types/micromatch": "^4.0.10", "@types/node": "^24.10.13", "@types/pouchdb": "^6.4.2", "@types/pouchdb-adapter-http": "^6.1.6", @@ -127,18 +128,19 @@ }, "dependencies": { "@aws-sdk/client-s3": "^3.808.0", - "chokidar": "^4.0.0", "@smithy/fetch-http-handler": "^5.3.10", "@smithy/md5-js": "^4.2.9", "@smithy/middleware-apply-body-checksum": "^4.3.9", "@smithy/protocol-http": "^5.3.9", "@smithy/querystring-builder": "^4.2.9", "@trystero-p2p/nostr": "^0.23.0", + "chokidar": "^4.0.0", "commander": "^14.0.3", "diff-match-patch": "^1.0.5", "fflate": "^0.8.2", "idb": "^8.0.3", "markdown-it": "^14.1.1", + "micromatch": "^4.0.0", "minimatch": "^10.2.2", "octagonal-wheels": "^0.1.45", "pouchdb-adapter-leveldb": "^9.0.0", diff --git a/src/apps/cli/README.md b/src/apps/cli/README.md index 6bd0082..d45f985 100644 --- a/src/apps/cli/README.md +++ b/src/apps/cli/README.md @@ -92,39 +92,39 @@ livesync-cli ./my-db pull folder/note.md ./note.md ## Installation -### Build from source - -```bash -# Clone with submodules, because the shared core lives in src/lib -git clone --recurse-submodules -cd obsidian-livesync - -# If you already cloned without submodules, run this once instead -git submodule update --init --recursive - -# Install dependencies from the repository root -npm install - -# Build the CLI from its package directory -cd src/apps/cli -npm run build -``` - -If `src/lib` is missing, `npm run build` now stops early with a targeted message -instead of a low-level Vite `ENOENT` error. +### Build from source -Run the CLI: - -```bash -# Run with npm script (from repository root) -npm run --silent cli -- [database-path] [command] [args...] +```bash +# Clone with submodules, because the shared core lives in src/lib +git clone --recurse-submodules +cd obsidian-livesync + +# If you already cloned without submodules, run this once instead +git submodule update --init --recursive + +# Install dependencies from the repository root +npm install + +# Build the CLI from its package directory +cd src/apps/cli +npm run build +``` + +If `src/lib` is missing, `npm run build` now stops early with a targeted message +instead of a low-level Vite `ENOENT` error. + +Run the CLI: + +```bash +# Run with npm script (from repository root) +npm run --silent cli -- [database-path] [command] [args...] # Run the built executable directly node src/apps/cli/dist/index.cjs [database-path] [command] [args...] ``` -### Docker - -A Docker image is provided for headless / server deployments. Build from the repository root: +### Docker + +A Docker image is provided for headless / server deployments. Build from the repository root: ```bash docker build -f src/apps/cli/Dockerfile -t livesync-cli . @@ -297,9 +297,11 @@ Options: --force, -f Overwrite existing file on init-settings --verbose, -v Enable verbose logging --debug, -d Enable debug logging (includes verbose) - --help, -h Show help message + --interval , -i (daemon only) Poll CouchDB every N seconds instead of using the _changes feed + --help, -h Show this help message Commands: + daemon (default) Run mirror scan then continuously sync CouchDB <-> local filesystem init-settings [path] Create settings JSON from DEFAULT_SETTINGS sync Run one replication cycle and exit p2p-peers Show discovered peers as [peer] @@ -406,6 +408,86 @@ In other words, it performs the following actions: Note: `mirror` does not respect file deletions. If a file is deleted in storage, it will be restored on the next `mirror` run. To delete a file, use the `rm` command instead. This is a little inconvenient, but it is intentional behaviour (if we handle this automatically in `mirror`, we should be against a ton of edge cases). +##### daemon + +`daemon` is the default command when no command is specified. It runs an initial mirror scan and then continuously syncs changes in both directions: + +- **CouchDB → local filesystem**: via the `_changes` feed (LiveSync mode, default) or periodic polling (`--interval N`). +- **local filesystem → CouchDB**: via chokidar file watching. Any file created, modified, or deleted in the vault directory is pushed to CouchDB. + +In **LiveSync mode** the `_changes` feed delivers remote changes as they arrive, with sub-second latency. In **polling mode** (`--interval N`) the CLI polls CouchDB every N seconds. Use polling mode if your CouchDB instance does not support long-lived HTTP connections, or if you need predictable network usage. + +The daemon exits cleanly on `SIGINT` or `SIGTERM`. + +```bash +# LiveSync mode (default — _changes feed, near-real-time) +livesync-cli /path/to/vault + +# Polling mode — poll every 60 seconds +livesync-cli /path/to/vault --interval 60 +``` + +### .livesync/ignore + +Place a `.livesync/ignore` file in your vault root to exclude files from sync in both directions (local → CouchDB and CouchDB → local). + +**Format:** + +- Lines beginning with `#` are comments. +- Blank lines are ignored. +- All other lines are [minimatch](https://github.com/isaacs/minimatch) glob patterns, relative to the vault root. +- The directive `import: .gitignore` (exactly this string) reads `.gitignore` from the vault root and merges its non-comment, non-blank lines into the ignore rules. +- Negation patterns (lines starting with `!`) are not supported and will cause an error on load. + +**Example `.livesync/ignore`:** + +``` +# Ignore temporary files +*.tmp +*.swp + +# Ignore build output +build/ +dist/ + +# Merge patterns from .gitignore +import: .gitignore +``` + +Patterns apply in both directions: the chokidar watcher will not emit events for matched files, and the `isTargetFile` filter will exclude them from CouchDB → local sync. + +Changes to this file require a daemon restart to take effect. + +### Systemd Installation + +The `deploy/` directory contains a systemd unit template and an install script. + +**Automated install (user service, recommended):** + +```bash +bash src/apps/cli/deploy/install.sh --vault /path/to/vault +``` + +**With polling interval:** + +```bash +bash src/apps/cli/deploy/install.sh --vault /path/to/vault --interval 60 +``` + +**System-wide install** (requires root / sudo for `/etc/systemd/system/`): + +```bash +bash src/apps/cli/deploy/install.sh --system --vault /path/to/vault +``` + +The script: +1. Builds the CLI (`npm install` + `npm run build`). +2. Installs the binary to `~/.local/bin/livesync-cli` (user) or `/usr/local/bin/livesync-cli` (system). +3. Writes the unit file to `~/.config/systemd/user/livesync-cli.service` (user) or `/etc/systemd/system/livesync-cli.service` (system). +4. Runs `systemctl [--user] daemon-reload && systemctl [--user] enable --now livesync-cli`. + +**Manual setup** — if you prefer to manage the unit yourself, copy `deploy/livesync-cli.service`, replace `LIVESYNC_BIN` and `LIVESYNC_VAULT_PATH` with the actual binary path and vault path, then install to the appropriate systemd directory. + ### Planned options: - `--immediate`: Perform sync after the command (e.g. `push`, `pull`, `put`, `rm`). diff --git a/src/apps/cli/commands/runCommand.ts b/src/apps/cli/commands/runCommand.ts index 5e1af90..9acb19d 100644 --- a/src/apps/cli/commands/runCommand.ts +++ b/src/apps/cli/commands/runCommand.ts @@ -22,6 +22,7 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext // accept whatever configuration the remote has. await core.services.setting.applyPartial({ disableCheckingConfigMismatch: true }, true); + // 1. Replicate CouchDB → local PouchDB so the mirror scan has content to work with. log("Replicating from CouchDB..."); const replResult = await core.services.replication.replicate(true); diff --git a/src/apps/cli/deploy/install.sh b/src/apps/cli/deploy/install.sh new file mode 100755 index 0000000..d0d3a2e --- /dev/null +++ b/src/apps/cli/deploy/install.sh @@ -0,0 +1,187 @@ +#!/usr/bin/env bash +# install.sh — install livesync-cli as a systemd service +# +# Usage: +# install.sh [--user] [--system] [--vault ] [--interval ] +# +# Defaults: user install, prompts for vault path if not supplied. +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd -- "$SCRIPT_DIR/../../.." && pwd)" +CLI_DIR="$REPO_ROOT/src/apps/cli" +SERVICE_TEMPLATE="$SCRIPT_DIR/livesync-cli.service" + +# ── Argument parsing ──────────────────────────────────────────────────────── +INSTALL_MODE="user" +VAULT_PATH="" +INTERVAL="" +FORCE=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + --user) + INSTALL_MODE="user" + shift + ;; + --system) + INSTALL_MODE="system" + shift + ;; + --vault) + if [[ -z "${2:-}" ]]; then + echo "Error: --vault requires a path argument" >&2 + exit 1 + fi + VAULT_PATH="$2" + shift 2 + ;; + --interval) + if [[ -z "${2:-}" ]]; then + echo "Error: --interval requires a numeric argument" >&2 + exit 1 + fi + INTERVAL="$2" + if ! [[ "$INTERVAL" =~ ^[1-9][0-9]*$ ]]; then + echo "Error: --interval requires a positive integer, got '$INTERVAL'" >&2 + exit 1 + fi + shift 2 + ;; + --force|-f) + FORCE=1 + shift + ;; + --help|-h) + cat <] [--interval ] [--force] + + --user Install as a user systemd service (default, ~/.config/systemd/user/) + --system Install as a system systemd service (/etc/systemd/system/) + --vault Path to the vault directory (prompted if omitted) + --interval Poll CouchDB every N seconds instead of using the _changes feed + --force Overwrite existing service unit without prompting +EOF + exit 0 + ;; + *) + echo "Error: Unknown argument: $1" >&2 + exit 1 + ;; + esac +done + +# ── Vault path ────────────────────────────────────────────────────────────── +if [[ -z "$VAULT_PATH" ]]; then + if [ ! -t 0 ]; then + echo "Error: --vault is required in non-interactive mode" >&2 + exit 1 + fi + printf 'Vault path: ' + read -r VAULT_PATH +fi + +_orig_vault="$VAULT_PATH" +if ! VAULT_PATH="$(cd -- "$VAULT_PATH" 2>/dev/null && pwd)"; then + echo "Error: vault directory does not exist: $_orig_vault" >&2 + exit 1 +fi + +echo "[INFO] Vault: $VAULT_PATH" +echo "[INFO] Install mode: $INSTALL_MODE" + +# ── Build ──────────────────────────────────────────────────────────────────── +echo "[INFO] Building CLI from $REPO_ROOT..." +(cd "$REPO_ROOT" && npm install --silent) +(cd "$CLI_DIR" && npm run build) + +BUILT_CJS="$CLI_DIR/dist/index.cjs" +if [[ ! -f "$BUILT_CJS" ]]; then + echo "Error: build output not found: $BUILT_CJS" >&2 + exit 1 +fi + +# ── Install binary ─────────────────────────────────────────────────────────── +if [[ "$INSTALL_MODE" == "user" ]]; then + BIN_DIR="$HOME/.local/bin" + UNIT_DIR="$HOME/.config/systemd/user" + SYSTEMCTL_FLAGS="--user" +else + BIN_DIR="/usr/local/bin" + UNIT_DIR="/etc/systemd/system" + SYSTEMCTL_FLAGS="" +fi + +mkdir -p "$BIN_DIR" + +LIVESYNC_BIN="$BIN_DIR/livesync-cli" +LIVESYNC_JS="$BIN_DIR/livesync-cli.js" + +# Copy the CJS bundle so the wrapper is self-contained and independent of the +# build directory location. +cp "$BUILT_CJS" "$LIVESYNC_JS" + +# Write a bash wrapper that invokes node on the installed bundle. +cat > "$LIVESYNC_BIN" <&2 + exit 1 + fi + printf 'Service unit already exists at %s. Overwrite? [y/N]: ' "$UNIT_PATH" + read -r CONFIRM + case "$CONFIRM" in + [yY]|[yY][eE][sS]) : ;; + *) + echo "[INFO] Aborted. Existing unit left in place." + exit 0 + ;; + esac +fi + +# In awk gsub(), '&' in the replacement means "matched text"; escape any literal '&' +# in path variables before passing them as awk replacement strings. +AWK_BIN="${LIVESYNC_BIN//&/\\&}" +AWK_VAULT="${VAULT_PATH//&/\\&}" +awk -v bin="$AWK_BIN" -v vault="$AWK_VAULT" -v exec_start="ExecStart=$EXEC_START" \ + '/^ExecStart=/ { print exec_start; next } {gsub("LIVESYNC_BIN", bin); gsub("LIVESYNC_VAULT_PATH", vault); print}' \ + "$SERVICE_TEMPLATE" > "$UNIT_PATH" + +echo "[INFO] Installed unit: $UNIT_PATH" + +# ── Enable service ─────────────────────────────────────────────────────────── +if ! command -v systemctl >/dev/null 2>&1; then + echo "[WARN] systemctl not found — skipping service activation" + echo "[INFO] To enable manually, copy $UNIT_PATH to the correct systemd directory and run:" + echo " systemctl $SYSTEMCTL_FLAGS daemon-reload" + echo " systemctl $SYSTEMCTL_FLAGS enable --now livesync-cli" + exit 0 +fi + +# shellcheck disable=SC2086 +systemctl $SYSTEMCTL_FLAGS daemon-reload +# shellcheck disable=SC2086 +systemctl $SYSTEMCTL_FLAGS enable --now livesync-cli + +echo "" +echo "[Done] livesync-cli service installed and started." +echo "" +# shellcheck disable=SC2086 +systemctl $SYSTEMCTL_FLAGS status livesync-cli --no-pager || true diff --git a/src/apps/cli/deploy/livesync-cli.service b/src/apps/cli/deploy/livesync-cli.service new file mode 100644 index 0000000..b76e786 --- /dev/null +++ b/src/apps/cli/deploy/livesync-cli.service @@ -0,0 +1,17 @@ +[Unit] +Description=Self-hosted LiveSync CLI Daemon +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +ExecStart=LIVESYNC_BIN LIVESYNC_VAULT_PATH +Restart=on-failure +RestartSec=10 +TimeoutStartSec=300 +StandardOutput=journal +StandardError=journal +LimitNOFILE=65536 + +[Install] +WantedBy=default.target diff --git a/src/apps/cli/main.ts b/src/apps/cli/main.ts index 07ef657..535d137 100644 --- a/src/apps/cli/main.ts +++ b/src/apps/cli/main.ts @@ -26,6 +26,7 @@ import { VALID_COMMANDS } from "./commands/types"; import type { CLICommand, CLIOptions } from "./commands/types"; import { getPathFromUXFileInfo } from "@lib/common/typeUtils"; import { stripAllPrefixes } from "@lib/string_and_binary/path"; +import { IgnoreRules } from "./serviceModules/IgnoreRules"; const SETTINGS_FILE = ".livesync/settings.json"; ensureGlobalNodeLocalStorage(); @@ -221,6 +222,9 @@ async function createDefaultSettingsFile(options: CLIOptions) { export async function main() { const options = parseArgs(); + if (options.interval && options.command !== "daemon") { + console.error(`Warning: --interval is only used in daemon mode, ignored for '${options.command}'`); + } const avoidStdoutNoise = options.command === "cat" || options.command === "cat-rev" || @@ -275,13 +279,13 @@ export async function main() { // For daemon and mirror mode, load ignore rules before the core is constructed so that // chokidar's ignored option is populated when beginWatch() fires during onLoad(). const watchEnabled = options.command === "daemon"; - const vaultPathForIgnoreRules = + const vaultPath = options.command === "mirror" && options.commandArgs[0] ? path.resolve(options.commandArgs[0]) : databasePath; let ignoreRules: IgnoreRules | undefined; if (options.command === "daemon" || options.command === "mirror") { - ignoreRules = new IgnoreRules(vaultPathForIgnoreRules); + ignoreRules = new IgnoreRules(vaultPath); await ignoreRules.load(); } @@ -365,11 +369,7 @@ export async function main() { const core = new LiveSyncBaseCore( serviceHubInstance, (core: LiveSyncBaseCore, serviceHub: InjectableServiceHub) => { - const mirrorVaultPath = - options.command === "mirror" && options.commandArgs[0] - ? path.resolve(options.commandArgs[0]) - : databasePath; - return initialiseServiceModulesCLI(mirrorVaultPath, core, serviceHub); + return initialiseServiceModulesCLI(vaultPath, core, serviceHub, ignoreRules, watchEnabled); }, (core) => [ // No modules need to be registered for P2P replication in CLI. Directly using Replicators in p2p.ts @@ -385,8 +385,25 @@ export async function main() { if (parts.some((part) => part.startsWith("."))) { return await Promise.resolve(false); } + // PouchDB LevelDB database directory lives in the vault directory. + if (parts[0]?.endsWith("-livesync-v2")) { + return await Promise.resolve(false); + } return await Promise.resolve(true); }, -1 /* highest priority */); + + // Apply user-defined ignore rules for daemon mode (lower priority, runs after dotfile check). + if (ignoreRules) { + const rules = ignoreRules; + core.services.vault.isTargetFile.addHandler(async (target) => { + const targetPath = stripAllPrefixes(getPathFromUXFileInfo(target)); + if (rules.shouldIgnore(targetPath)) { + return false; + } + // undefined = pass through to next handler in chain + return undefined; + }, 0); + } } ); diff --git a/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts b/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts index ea6e31e..9abc5fd 100644 --- a/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts +++ b/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts @@ -15,6 +15,7 @@ import type { Stats } from "fs"; import * as fs from "fs/promises"; import * as path from "path"; import { watch as chokidarWatch, type FSWatcher } from "chokidar"; +import type { IgnoreRules } from "../serviceModules/IgnoreRules"; /** * CLI-specific type guard adapter @@ -96,7 +97,7 @@ class CLIConverterAdapter implements IStorageEventConverterAdapter { class CLIWatchAdapter implements IStorageEventWatchAdapter { private _watcher: FSWatcher | undefined; - constructor(private basePath: string, private watchEnabled: boolean = false) {} + constructor(private basePath: string, private ignoreRules?: IgnoreRules, private watchEnabled: boolean = false) {} private _toNodeFile(filePath: string, stats: Stats | undefined): NodeFile { return { @@ -110,18 +111,6 @@ class CLIWatchAdapter implements IStorageEventWatchAdapter { }; } - private _toNodeFileStub(filePath: string): NodeFile { - return { - path: path.relative(this.basePath, filePath) as FilePath, - stat: { - ctime: Date.now(), - mtime: Date.now(), - size: 0, - type: "file", - }, - }; - } - private _toNodeFolder(dirPath: string): NodeFolder { return { path: path.relative(this.basePath, dirPath) as FilePath, @@ -131,10 +120,19 @@ class CLIWatchAdapter implements IStorageEventWatchAdapter { async beginWatch(handlers: IStorageEventWatchHandlers): Promise { if (!this.watchEnabled) return; + const baseIgnored: Array boolean)> = [ + /(^|[/\\])\./, + /(^|[/\\])[^/\\]*-livesync-v2([/\\]|$)/, + ]; + // Bind rules to a local const before the closure — chokidar v4 requires a + // MatchFunction, not glob strings, for custom patterns. + const rules = this.ignoreRules; + const ignored = rules + ? [...baseIgnored, (p: string) => rules.shouldIgnore(path.relative(this.basePath, p))] + : baseIgnored; + const watcher = chokidarWatch(this.basePath, { - ignored: [ - /(^|[/\\])\./, - ], + ignored, ignoreInitial: true, persistent: true, awaitWriteFinish: { @@ -154,7 +152,7 @@ class CLIWatchAdapter implements IStorageEventWatchAdapter { }); watcher.on("unlink", (filePath) => { - const nodeFile = this._toNodeFileStub(filePath); + const nodeFile = this._toNodeFile(filePath, undefined); handlers.onDelete(nodeFile); }); @@ -199,10 +197,10 @@ export class CLIStorageEventManagerAdapter implements IStorageEventManagerAdapte readonly status: CLIStatusAdapter; readonly converter: CLIConverterAdapter; - constructor(basePath: string, watchEnabled: boolean = false) { + constructor(basePath: string, ignoreRules?: IgnoreRules, watchEnabled: boolean = false) { this.typeGuard = new CLITypeGuardAdapter(); this.persistence = new CLIPersistenceAdapter(basePath); - this.watch = new CLIWatchAdapter(basePath, watchEnabled); + this.watch = new CLIWatchAdapter(basePath, ignoreRules, watchEnabled); this.status = new CLIStatusAdapter(); this.converter = new CLIConverterAdapter(); } diff --git a/src/apps/cli/managers/StorageEventManagerCLI.ts b/src/apps/cli/managers/StorageEventManagerCLI.ts index c8edb3d..7838ef3 100644 --- a/src/apps/cli/managers/StorageEventManagerCLI.ts +++ b/src/apps/cli/managers/StorageEventManagerCLI.ts @@ -2,6 +2,7 @@ import { StorageEventManagerBase, type StorageEventManagerBaseDependencies } fro import { CLIStorageEventManagerAdapter } from "./CLIStorageEventManagerAdapter"; import type { IMinimumLiveSyncCommands, LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; import type { ServiceContext } from "@lib/services/base/ServiceBase"; +import type { IgnoreRules } from "../serviceModules/IgnoreRules"; // import type { IMinimumLiveSyncCommands } from "@lib/services/base/IService"; export class StorageEventManagerCLI extends StorageEventManagerBase { @@ -11,9 +12,10 @@ export class StorageEventManagerCLI extends StorageEventManagerBase, dependencies: StorageEventManagerBaseDependencies, + ignoreRules?: IgnoreRules, watchEnabled?: boolean ) { - const adapter = new CLIStorageEventManagerAdapter(basePath, watchEnabled); + const adapter = new CLIStorageEventManagerAdapter(basePath, ignoreRules, watchEnabled); super(adapter, dependencies); this.core = core; } diff --git a/src/apps/cli/serviceModules/CLIServiceModules.ts b/src/apps/cli/serviceModules/CLIServiceModules.ts index b0e67ba..6c4cce5 100644 --- a/src/apps/cli/serviceModules/CLIServiceModules.ts +++ b/src/apps/cli/serviceModules/CLIServiceModules.ts @@ -9,6 +9,7 @@ import { ServiceFileAccessCLI } from "./ServiceFileAccessImpl"; import { ServiceDatabaseFileAccessCLI } from "./DatabaseFileAccess"; import { StorageEventManagerCLI } from "../managers/StorageEventManagerCLI"; import type { ServiceModules } from "@lib/interfaces/ServiceModule"; +import type { IgnoreRules } from "./IgnoreRules"; /** * Initialize service modules for CLI version @@ -23,6 +24,7 @@ export function initialiseServiceModulesCLI( basePath: string, core: LiveSyncBaseCore, services: InjectableServiceHub, + ignoreRules?: IgnoreRules, watchEnabled: boolean = false, ): ServiceModules { const storageAccessManager = new StorageAccessManager(); @@ -43,7 +45,7 @@ export function initialiseServiceModulesCLI( vaultService: services.vault, storageAccessManager: storageAccessManager, APIService: services.API, - }, false); + }, ignoreRules, watchEnabled); // Close the file watcher during graceful shutdown so the process can exit cleanly. services.appLifecycle.onUnload.addHandler(async () => { diff --git a/src/apps/cli/serviceModules/IgnoreRules.ts b/src/apps/cli/serviceModules/IgnoreRules.ts new file mode 100644 index 0000000..9764fd2 --- /dev/null +++ b/src/apps/cli/serviceModules/IgnoreRules.ts @@ -0,0 +1,129 @@ +import * as fs from "fs/promises"; +import * as path from "path"; + +import { minimatch } from "minimatch"; + +/** + * Loads and evaluates ignore rules from `.livesync/ignore` inside the vault. + * + * File format: + * - Lines starting with `#` are comments. + * - Blank lines are ignored. + * - `import: .gitignore` (exactly) — merges patterns from the vault's `.gitignore`. + * - All other lines are minimatch glob patterns relative to the vault root. + * + * Negation patterns (lines starting with `!`) are not supported. Loading a + * ruleset containing them throws an error — use separate include/exclude files + * instead. + * + * Missing files (`.livesync/ignore` or `.gitignore`) are silently skipped. + */ +export class IgnoreRules { + private patterns: string[] = []; + + constructor(private vaultPath: string) {} + + /** + * Reads `.livesync/ignore` (and optionally `.gitignore`) and populates the + * pattern list. Safe to call multiple times — each call replaces the + * previous state. Does not throw if files are absent. + * + * @throws if any pattern line begins with `!` (negation is unsupported). + */ + async load(): Promise { + this.patterns = []; + const ignorePath = path.join(this.vaultPath, ".livesync", "ignore"); + let rawLines: string[]; + try { + const content = await fs.readFile(ignorePath, "utf-8"); + rawLines = content.split(/\r?\n/); + } catch { + // File absent or unreadable — treat as empty ruleset. + return; + } + + for (const line of rawLines) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("#")) { + continue; + } + // NOTE: Only the exact string "import: .gitignore" is recognised. + // Any future generalisation of this directive must validate that + // the resolved path stays within the vault directory. + if (trimmed === "import: .gitignore") { + await this._importGitignore(); + continue; + } + if (trimmed.startsWith("import:")) { + console.error(`[IgnoreRules] Warning: unrecognised directive '${trimmed}' — only 'import: .gitignore' is supported`); + continue; + } + this._addPattern(trimmed); + } + if (this.patterns.length > 0) { + console.error(`[IgnoreRules] Loaded ${this.patterns.length} ignore patterns`); + } + } + + // Normalises a single gitignore-style pattern: + // - Patterns ending with `/` (directory patterns like `build/`) are + // converted to `build/**` so they match all files inside that directory. + // - Patterns without a `/` are prefixed with `**/` to give them matchBase + // semantics (e.g. `*.tmp` → `**/*.tmp`), matching the basename in any + // subdirectory as gitignore does. + // - Patterns that already contain a `/` (but don't end with one) are + // path-specific and used as-is. + private _normalisePattern(pattern: string): string { + if (pattern.endsWith("/")) { + return "**/" + pattern + "**"; + } else if (!pattern.includes("/")) { + return "**/" + pattern; + } + return pattern; + } + + private async _importGitignore(): Promise { + const gitignorePath = path.join(this.vaultPath, ".gitignore"); + let content: string; + try { + content = await fs.readFile(gitignorePath, "utf-8"); + } catch { + return; + } + this._parseLines(content); + } + + private _parseLines(content: string): void { + for (const line of content.split(/\r?\n/)) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("#")) continue; + this._addPattern(trimmed); + } + } + + private _addPattern(raw: string): void { + if (raw.startsWith("!")) { + throw new Error( + `[IgnoreRules] Negation pattern '${raw}' is not supported. ` + + `Remove it from .livesync/ignore or use a separate include/exclude file.` + ); + } + this.patterns.push(this._normalisePattern(raw)); + } + + /** + * Returns `true` if the given vault-relative path matches any loaded + * ignore pattern. + * + * @param relativePath - Path relative to the vault root, using forward + * slashes or the OS separator. + */ + shouldIgnore(relativePath: string): boolean { + if (this.patterns.length === 0) { + return false; + } + // Normalise to forward slashes for minimatch. + const normalised = relativePath.replace(/\\/g, "/"); + return this.patterns.some((p) => minimatch(normalised, p, { dot: true })); + } +} diff --git a/src/apps/cli/serviceModules/IgnoreRules.unit.spec.ts b/src/apps/cli/serviceModules/IgnoreRules.unit.spec.ts new file mode 100644 index 0000000..59bfb12 --- /dev/null +++ b/src/apps/cli/serviceModules/IgnoreRules.unit.spec.ts @@ -0,0 +1,172 @@ +import * as fs from "node:fs/promises"; +import * as os from "node:os"; +import * as path from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { IgnoreRules } from "./IgnoreRules"; + +describe("IgnoreRules", () => { + const tempDirs: string[] = []; + + async function createVault(): Promise { + const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "livesync-ignorerules-")); + tempDirs.push(tempDir); + return tempDir; + } + + async function writeIgnoreFile(vaultPath: string, content: string): Promise { + const ignoreDir = path.join(vaultPath, ".livesync"); + await fs.mkdir(ignoreDir, { recursive: true }); + await fs.writeFile(path.join(ignoreDir, "ignore"), content, "utf-8"); + } + + afterEach(async () => { + await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true }))); + }); + + describe("pattern normalisation", () => { + it("adds **/ prefix to basename patterns (no slash)", async () => { + const vaultPath = await createVault(); + await writeIgnoreFile(vaultPath, "*.tmp\n"); + const rules = new IgnoreRules(vaultPath); + await rules.load(); + expect(rules.shouldIgnore("notes/scratch.tmp")).toBe(true); + expect(rules.shouldIgnore("scratch.tmp")).toBe(true); + expect(rules.shouldIgnore("deep/nested/file.tmp")).toBe(true); + }); + + it("appends ** to directory patterns ending with / and prepends **/", async () => { + const vaultPath = await createVault(); + await writeIgnoreFile(vaultPath, "build/\n"); + const rules = new IgnoreRules(vaultPath); + await rules.load(); + expect(rules.shouldIgnore("build/output.js")).toBe(true); + expect(rules.shouldIgnore("build/nested/file.js")).toBe(true); + expect(rules.shouldIgnore("subproject/build/output.js")).toBe(true); + }); + + it("leaves patterns containing / as-is", async () => { + const vaultPath = await createVault(); + await writeIgnoreFile(vaultPath, "docs/private.md\n"); + const rules = new IgnoreRules(vaultPath); + await rules.load(); + expect(rules.shouldIgnore("docs/private.md")).toBe(true); + expect(rules.shouldIgnore("other/docs/private.md")).toBe(false); + }); + }); + + describe("shouldIgnore", () => { + it("matches **/*.tmp against notes/scratch.tmp", async () => { + const vaultPath = await createVault(); + await writeIgnoreFile(vaultPath, "*.tmp\n"); + const rules = new IgnoreRules(vaultPath); + await rules.load(); + expect(rules.shouldIgnore("notes/scratch.tmp")).toBe(true); + }); + + it("does not match notes/readme.md against **/*.tmp", async () => { + const vaultPath = await createVault(); + await writeIgnoreFile(vaultPath, "*.tmp\n"); + const rules = new IgnoreRules(vaultPath); + await rules.load(); + expect(rules.shouldIgnore("notes/readme.md")).toBe(false); + }); + + it("returns false when no patterns are loaded", async () => { + const vaultPath = await createVault(); + const rules = new IgnoreRules(vaultPath); + // No load() call — patterns are empty + expect(rules.shouldIgnore("anything.md")).toBe(false); + }); + }); + + describe("negation patterns", () => { + it("throws when a negation pattern is encountered", async () => { + const vaultPath = await createVault(); + await writeIgnoreFile(vaultPath, "*.tmp\n!important.tmp\n"); + const rules = new IgnoreRules(vaultPath); + await expect(rules.load()).rejects.toThrow(/Negation pattern/); + }); + + it("throws when a .gitignore imported via directive contains negation", async () => { + const vaultPath = await createVault(); + await writeIgnoreFile(vaultPath, "import: .gitignore\n"); + await fs.writeFile(path.join(vaultPath, ".gitignore"), "*.log\n!keep.log\n", "utf-8"); + const rules = new IgnoreRules(vaultPath); + await expect(rules.load()).rejects.toThrow(/Negation pattern/); + }); + }); + + describe("unrecognised import: directives", () => { + it("warns and skips unrecognised import: forms (does not add as literal pattern)", async () => { + const vaultPath = await createVault(); + // Typo: "import:.gitignore" instead of "import: .gitignore" + await writeIgnoreFile(vaultPath, "*.tmp\nimport:.gitignore\n"); + const rules = new IgnoreRules(vaultPath); + await rules.load(); + // *.tmp still loaded; import:.gitignore is skipped (not treated as a literal pattern) + expect(rules.shouldIgnore("scratch.tmp")).toBe(true); + expect(rules.shouldIgnore("import:.gitignore")).toBe(false); + }); + }); + + describe("load() with missing file", () => { + it("returns without error when .livesync/ignore is absent", async () => { + const vaultPath = await createVault(); + // No ignore file created + const rules = new IgnoreRules(vaultPath); + await expect(rules.load()).resolves.toBeUndefined(); + expect(rules.shouldIgnore("anything.md")).toBe(false); + }); + }); + + describe("load() with comments and blank lines", () => { + it("skips # comment lines and blank lines", async () => { + const vaultPath = await createVault(); + await writeIgnoreFile( + vaultPath, + "# This is a comment\n\n \n*.tmp\n# another comment\nbuild/\n" + ); + const rules = new IgnoreRules(vaultPath); + await rules.load(); + expect(rules.shouldIgnore("scratch.tmp")).toBe(true); + expect(rules.shouldIgnore("build/output.js")).toBe(true); + expect(rules.shouldIgnore("readme.md")).toBe(false); + }); + }); + + describe("import: .gitignore directive", () => { + it("reads and normalises patterns from .gitignore", async () => { + const vaultPath = await createVault(); + await writeIgnoreFile(vaultPath, "import: .gitignore\n"); + await fs.writeFile(path.join(vaultPath, ".gitignore"), "*.log\nnode_modules/\n", "utf-8"); + const rules = new IgnoreRules(vaultPath); + await rules.load(); + expect(rules.shouldIgnore("app.log")).toBe(true); + expect(rules.shouldIgnore("node_modules/package.json")).toBe(true); + expect(rules.shouldIgnore("src/node_modules/package.json")).toBe(true); + expect(rules.shouldIgnore("src/index.ts")).toBe(false); + }); + + it("merges .gitignore patterns with other patterns", async () => { + const vaultPath = await createVault(); + await writeIgnoreFile(vaultPath, "*.tmp\nimport: .gitignore\n"); + await fs.writeFile(path.join(vaultPath, ".gitignore"), "*.log\n", "utf-8"); + const rules = new IgnoreRules(vaultPath); + await rules.load(); + expect(rules.shouldIgnore("scratch.tmp")).toBe(true); + expect(rules.shouldIgnore("error.log")).toBe(true); + }); + }); + + describe("import: .gitignore with missing .gitignore", () => { + it("does not throw when .gitignore is absent", async () => { + const vaultPath = await createVault(); + await writeIgnoreFile(vaultPath, "*.tmp\nimport: .gitignore\n"); + // No .gitignore created + const rules = new IgnoreRules(vaultPath); + await expect(rules.load()).resolves.toBeUndefined(); + // The *.tmp pattern from the ignore file still works + expect(rules.shouldIgnore("scratch.tmp")).toBe(true); + }); + }); +}); diff --git a/src/apps/cli/test/test-daemon-linux.sh b/src/apps/cli/test/test-daemon-linux.sh new file mode 100755 index 0000000..96db2c7 --- /dev/null +++ b/src/apps/cli/test/test-daemon-linux.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash +# Test: daemon-related ignore rules behaviour +# +# Tests that are runnable without a long-running daemon process are exercised +# here using the `mirror` command, which calls the same `isTargetFile` handler +# stack that the daemon uses. +# +# Covered cases: +# 1. .livesync/ignore with *.tmp pattern → ignored file is NOT synced to DB +# 2. .livesync/ignore missing → no error, normal sync continues +# 3. import: .gitignore directive → patterns from .gitignore are merged +# +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +CLI_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)" +cd "$CLI_DIR" +source "$SCRIPT_DIR/test-helpers.sh" +display_test_info + +RUN_BUILD="${RUN_BUILD:-1}" +cli_test_init_cli_cmd + +WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-daemon-test.XXXXXX")" +trap 'rm -rf "$WORK_DIR"' EXIT + +SETTINGS_FILE="$WORK_DIR/data.json" +VAULT_DIR="$WORK_DIR/vault" +mkdir -p "$VAULT_DIR/notes" + +if [[ "$RUN_BUILD" == "1" ]]; then + echo "[INFO] building CLI..." + npm run build +fi + +echo "[INFO] generating settings -> $SETTINGS_FILE" +cli_test_init_settings_file "$SETTINGS_FILE" +cli_test_mark_settings_configured "$SETTINGS_FILE" + +PASS=0 +FAIL=0 + +assert_pass() { echo "[PASS] $1"; PASS=$((PASS + 1)); } +assert_fail() { echo "[FAIL] $1" >&2; FAIL=$((FAIL + 1)); } + +# ───────────────────────────────────────────────────────────────────────────── +# Case 1: .livesync/ignore with *.tmp → matched file should NOT appear in DB +# ───────────────────────────────────────────────────────────────────────────── +echo "" +echo "=== Case 1: .livesync/ignore *.tmp → ignored file not synced to DB ===" + +mkdir -p "$VAULT_DIR/.livesync" +printf '*.tmp\n' > "$VAULT_DIR/.livesync/ignore" + +# Also write a normal file so we can confirm mirror ran at all. +printf 'normal content\n' > "$VAULT_DIR/notes/normal.md" +# Write the file that should be ignored. +printf 'tmp content\n' > "$VAULT_DIR/notes/scratch.tmp" + +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror + +# The normal file should be in the DB. +RESULT_NORMAL="$WORK_DIR/case1-normal.txt" +if run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" pull notes/normal.md "$RESULT_NORMAL" 2>/dev/null; then + if cmp -s "$VAULT_DIR/notes/normal.md" "$RESULT_NORMAL"; then + assert_pass "normal.md was synced to DB" + else + assert_fail "normal.md content mismatch after mirror" + fi +else + assert_fail "normal.md was not found in DB after mirror" +fi + +# The .tmp file should NOT be in the DB. +DB_LIST="$WORK_DIR/case1-ls.txt" +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" ls > "$DB_LIST" +if grep -q "scratch.tmp" "$DB_LIST"; then + assert_fail "scratch.tmp (ignored) was unexpectedly synced to DB" + echo "--- DB listing ---" >&2; cat "$DB_LIST" >&2 +else + assert_pass "scratch.tmp (*.tmp pattern) was NOT synced to DB" +fi + +# ───────────────────────────────────────────────────────────────────────────── +# Case 2: .livesync/ignore absent → no error, normal sync continues +# ───────────────────────────────────────────────────────────────────────────── +echo "" +echo "=== Case 2: .livesync/ignore absent → no error, sync continues ===" + +VAULT_DIR2="$WORK_DIR/vault2" +mkdir -p "$VAULT_DIR2/notes" +SETTINGS_FILE2="$WORK_DIR/data2.json" +cli_test_init_settings_file "$SETTINGS_FILE2" +cli_test_mark_settings_configured "$SETTINGS_FILE2" + +# No .livesync directory at all. +printf 'hello\n' > "$VAULT_DIR2/notes/hello.md" + +# mirror should succeed without error. +set +e +MIRROR_OUTPUT="$WORK_DIR/case2-mirror.txt" +run_cli "$VAULT_DIR2" --settings "$SETTINGS_FILE2" mirror >"$MIRROR_OUTPUT" 2>&1 +MIRROR_EXIT=$? +set -e + +if [[ "$MIRROR_EXIT" -ne 0 ]]; then + assert_fail "mirror exited non-zero ($MIRROR_EXIT) when .livesync/ignore is absent" + cat "$MIRROR_OUTPUT" >&2 +else + assert_pass "mirror succeeded when .livesync/ignore is absent" +fi + +# The normal file should have been synced. +RESULT_HELLO="$WORK_DIR/case2-hello.txt" +if run_cli "$VAULT_DIR2" --settings "$SETTINGS_FILE2" pull notes/hello.md "$RESULT_HELLO" 2>/dev/null; then + assert_pass "file synced normally when .livesync/ignore is absent" +else + assert_fail "file was not synced when .livesync/ignore is absent" +fi + +# ───────────────────────────────────────────────────────────────────────────── +# Case 3: import: .gitignore merges patterns +# ───────────────────────────────────────────────────────────────────────────── +echo "" +echo "=== Case 3: import: .gitignore directive merges patterns ===" + +VAULT_DIR3="$WORK_DIR/vault3" +mkdir -p "$VAULT_DIR3/notes" +SETTINGS_FILE3="$WORK_DIR/data3.json" +cli_test_init_settings_file "$SETTINGS_FILE3" +cli_test_mark_settings_configured "$SETTINGS_FILE3" + +mkdir -p "$VAULT_DIR3/.livesync" +printf 'import: .gitignore\n' > "$VAULT_DIR3/.livesync/ignore" +printf '# gitignore comment\n*.log\nbuild/\n' > "$VAULT_DIR3/.gitignore" + +printf 'regular note\n' > "$VAULT_DIR3/notes/regular.md" +printf 'log content\n' > "$VAULT_DIR3/notes/debug.log" + +run_cli "$VAULT_DIR3" --settings "$SETTINGS_FILE3" mirror + +DB_LIST3="$WORK_DIR/case3-ls.txt" +run_cli "$VAULT_DIR3" --settings "$SETTINGS_FILE3" ls > "$DB_LIST3" + +if grep -q "debug.log" "$DB_LIST3"; then + assert_fail "debug.log (ignored via .gitignore import) was unexpectedly synced to DB" + echo "--- DB listing ---" >&2; cat "$DB_LIST3" >&2 +else + assert_pass "debug.log (*.log from imported .gitignore) was NOT synced to DB" +fi + +# regular.md should still be present. +if grep -q "regular.md" "$DB_LIST3"; then + assert_pass "regular.md was synced normally alongside .gitignore import rules" +else + assert_fail "regular.md was NOT synced — .gitignore import may have been too broad" +fi + +# ───────────────────────────────────────────────────────────────────────────── +# Summary +# ───────────────────────────────────────────────────────────────────────────── +echo "" +echo "Results: PASS=$PASS FAIL=$FAIL" +if [[ "$FAIL" -gt 0 ]]; then + exit 1 +fi diff --git a/src/apps/cli/vite.config.ts b/src/apps/cli/vite.config.ts index 6850a94..74c4ba6 100644 --- a/src/apps/cli/vite.config.ts +++ b/src/apps/cli/vite.config.ts @@ -30,6 +30,10 @@ if (typeof globalThis.FileReader === "undefined") { if (this.onload) this.onload({ target: this }); }).catch((err) => { if (this.onerror) this.onerror({ target: this, error: err }); }); } + readAsArrayBuffer() { throw new Error("FileReader.readAsArrayBuffer is not implemented in this polyfill"); } + readAsBinaryString() { throw new Error("FileReader.readAsBinaryString is not implemented in this polyfill"); } + readAsText() { throw new Error("FileReader.readAsText is not implemented in this polyfill"); } + abort() { throw new Error("FileReader.abort is not implemented in this polyfill"); } }; } `; From 4ab2e41d1876c8d3fecdbb09285977289b2de90f Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Thu, 9 Apr 2026 13:34:40 +1000 Subject: [PATCH 06/73] cli daemon: set disableCheckingConfigMismatch for headless operation The config mismatch dialog's defaultAction is "Dismiss" which blocks replication. Since the daemon cannot resolve mismatches interactively, skip the check entirely and accept the remote configuration as-is. --- src/apps/cli/commands/runCommand.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/apps/cli/commands/runCommand.ts b/src/apps/cli/commands/runCommand.ts index 9acb19d..5e1af90 100644 --- a/src/apps/cli/commands/runCommand.ts +++ b/src/apps/cli/commands/runCommand.ts @@ -22,7 +22,6 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext // accept whatever configuration the remote has. await core.services.setting.applyPartial({ disableCheckingConfigMismatch: true }, true); - // 1. Replicate CouchDB → local PouchDB so the mirror scan has content to work with. log("Replicating from CouchDB..."); const replResult = await core.services.replication.replicate(true); From 2e9b8b7b628fd362bd30eb87c15e01c339b9a002 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 03:55:11 +0100 Subject: [PATCH 07/73] (chore): removing DOM Operation --- src/apps/webapp/bootstrap.ts | 2 +- src/lib | 2 +- .../DocumentHistory/DocumentHistoryModal.ts | 119 ++++++++++++------ .../ConflictResolveModal.ts | 52 +++++--- .../features/SettingDialogue/PaneChangeLog.ts | 7 +- .../features/SettingDialogue/PaneSetup.ts | 9 +- .../SettingDialogue/utilFixCouchDBSetting.ts | 9 +- 7 files changed, 131 insertions(+), 69 deletions(-) diff --git a/src/apps/webapp/bootstrap.ts b/src/apps/webapp/bootstrap.ts index 2450285..b3fa072 100644 --- a/src/apps/webapp/bootstrap.ts +++ b/src/apps/webapp/bootstrap.ts @@ -41,7 +41,7 @@ async function renderHistoryList(): Promise { const [items, lastUsedId] = await Promise.all([historyStore.getVaultHistory(), historyStore.getLastUsedVaultId()]); - listEl.innerHTML = ""; + listEl.replaceChildren(); emptyEl.classList.toggle("is-hidden", items.length > 0); for (const item of items) { diff --git a/src/lib b/src/lib index 9753055..00f7e65 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 97530553a63dc206ea3fb7ef60721cabda6c74cc +Subproject commit 00f7e653043daebe6b27594a275698bb7583123c diff --git a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts index 7e7560a..40a5254 100644 --- a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts +++ b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts @@ -1,6 +1,6 @@ import { TFile, Modal, App, DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "../../../deps.ts"; import { getPathFromTFile, isValidPath } from "../../../common/utils.ts"; -import { decodeBinary, escapeStringToHTML, readString } from "../../../lib/src/string_and_binary/convert.ts"; +import { decodeBinary, readString } from "../../../lib/src/string_and_binary/convert.ts"; import ObsidianLiveSyncPlugin from "../../../main.ts"; import { type DocumentID, @@ -145,22 +145,66 @@ export class DocumentHistoryModal extends Modal { return v; } + prepareContentView(usePreformatted = true) { + this.contentView.empty(); + this.contentView.toggleClass("op-pre", usePreformatted); + } + + appendTextDiff(diff: [number, string][]) { + for (const [operation, text] of diff) { + if (operation == DIFF_DELETE) { + this.contentView.createSpan({ text, cls: "history-deleted" }); + } else if (operation == DIFF_EQUAL) { + this.contentView.createSpan({ text, cls: "history-normal" }); + } else if (operation == DIFF_INSERT) { + this.contentView.createSpan({ text, cls: "history-added" }); + } + } + } + + appendImageDiff(baseSrc: string, overlaySrc?: string) { + const wrap = this.contentView.createDiv({ cls: "ls-imgdiff-wrap" }); + const overlay = wrap.createDiv({ cls: "overlay" }); + overlay.createEl("img", { cls: "img-base" }, (img) => { + img.src = baseSrc; + }); + if (overlaySrc) { + overlay.createEl("img", { cls: "img-overlay" }, (img) => { + img.src = overlaySrc; + }); + } + } + + appendDeletedNotice(usePreformatted = true) { + const notice = "(At this revision, the file has been deleted)"; + if (usePreformatted) { + this.contentView.appendText(`${notice}\n`); + } else { + this.contentView.createDiv({ text: notice }); + } + } + async showExactRev(rev: string) { const db = this.core.localDatabase; const w = await db.getDBEntry(this.file, { rev: rev }, false, false, true); this.currentText = ""; this.currentDeleted = false; + this.prepareContentView(); if (w === false) { this.currentDeleted = true; - this.info.innerHTML = ""; - this.contentView.innerHTML = `Could not read this revision
(${rev})`; + this.info.empty(); + this.contentView.appendText("Could not read this revision"); + this.contentView.createEl("br"); + this.contentView.appendText(`(${rev})`); } else { this.currentDoc = w; - this.info.innerHTML = `Modified:${new Date(w.mtime).toLocaleString()}`; - let result = undefined; + this.info.setText(`Modified:${new Date(w.mtime).toLocaleString()}`); const w1data = readDocument(w); this.currentDeleted = !!w.deleted; - // this.currentText = w1data; + if (typeof w1data == "string") { + this.currentText = w1data; + } + let rendered = false; if (this.showDiff) { const prevRevIdx = this.revs_info.length - 1 - ((this.range.value as any) / 1 - 1); if (prevRevIdx >= 0 && prevRevIdx < this.revs_info.length) { @@ -168,58 +212,55 @@ export class DocumentHistoryModal extends Modal { const w2 = await db.getDBEntry(this.file, { rev: oldRev }, false, false, true); if (w2 != false) { if (typeof w1data == "string") { - result = ""; - const dmp = new diff_match_patch(); - const w2data = readDocument(w2) as string; - const diff = dmp.diff_main(w2data, w1data); - dmp.diff_cleanupSemantic(diff); - for (const v of diff) { - const x1 = v[0]; - const x2 = v[1]; - if (x1 == DIFF_DELETE) { - result += "" + escapeStringToHTML(x2) + ""; - } else if (x1 == DIFF_EQUAL) { - result += "" + escapeStringToHTML(x2) + ""; - } else if (x1 == DIFF_INSERT) { - result += "" + escapeStringToHTML(x2) + ""; + const w2data = readDocument(w2); + if (typeof w2data == "string") { + const dmp = new diff_match_patch(); + const diff = dmp.diff_main(w2data, w1data); + dmp.diff_cleanupSemantic(diff); + if (this.currentDeleted) { + this.appendDeletedNotice(); } + this.appendTextDiff(diff); + rendered = true; } - result = result.replace(/\n/g, "
"); } else if (isImage(this.file)) { const src = this.generateBlobURL("base", w1data); const overlay = this.generateBlobURL( "overlay", readDocument(w2) as Uint8Array ); - result = `
-
- - -
-
`; - this.contentView.removeClass("op-pre"); + this.prepareContentView(false); + if (this.currentDeleted) { + this.appendDeletedNotice(false); + } + this.appendImageDiff(src, overlay); + rendered = true; } } } } - if (result == undefined) { + if (!rendered) { if (typeof w1data != "string") { if (isImage(this.file)) { const src = this.generateBlobURL("base", w1data); - result = `
-
- -
-
`; - this.contentView.removeClass("op-pre"); + this.prepareContentView(false); + if (this.currentDeleted) { + this.appendDeletedNotice(false); + } + this.appendImageDiff(src); + } else { + if (this.currentDeleted) { + this.appendDeletedNotice(); + } + this.contentView.appendText("Binary file"); } } else { - result = escapeStringToHTML(w1data); + if (this.currentDeleted) { + this.appendDeletedNotice(); + } + this.contentView.appendText(w1data); } } - if (result == undefined) result = typeof w1data == "string" ? escapeStringToHTML(w1data) : "Binary file"; - this.contentView.innerHTML = - (this.currentDeleted ? "(At this revision, the file has been deleted)\n" : "") + result; } // Reset diff navigation after content changes this.resetDiffNavigation(); diff --git a/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts b/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts index ad308e5..eec5332 100644 --- a/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts +++ b/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts @@ -1,7 +1,6 @@ import { App, Modal } from "../../../deps.ts"; import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT } from "diff-match-patch"; import { CANCELLED, LEAVE_TO_SUBSEQUENT, type diff_result } from "../../../lib/src/common/types.ts"; -import { escapeStringToHTML } from "../../../lib/src/string_and_binary/convert.ts"; import { delay } from "../../../lib/src/common/utils.ts"; import { eventHub } from "../../../common/events.ts"; import { globalSlipBoard } from "../../../lib/src/bureau/bureau.ts"; @@ -44,6 +43,25 @@ export class ConflictResolveModal extends Modal { // sendValue("close-resolve-conflict:" + this.filename, false); } + appendDiffFragment(container: HTMLDivElement, text: string, cls: string) { + const lines = text.split("\n"); + lines.forEach((line, index) => { + const span = container.createSpan({ cls }); + span.textContent = line; + if (index < lines.length - 1) { + container.createSpan({ cls: "ls-mark-cr" }); + container.createEl("br"); + } + }); + } + + appendVersionInfo(container: HTMLDivElement, cls: string, name: string, date: string) { + const line = container.createSpan({ cls }); + line.createSpan({ text: name, cls: "conflict-dev-name" }); + line.appendText(`: ${date}`); + container.createEl("br"); + } + override onOpen() { const { contentEl } = this; // Send cancel signal for the previous merge dialogue @@ -64,25 +82,21 @@ export class ConflictResolveModal extends Modal { const div = contentEl.createDiv(""); div.addClass("op-scrollable"); div.addClass("ls-dialog"); - let diff = ""; + let diffLength = 0; for (const v of this.result.diff) { const x1 = v[0]; const x2 = v[1]; + diffLength += x2.length; + if (diffLength > 100 * 1024) { + continue; + } if (x1 == DIFF_DELETE) { - diff += - "" + - escapeStringToHTML(x2).replace(/\n/g, "\n") + - ""; + this.appendDiffFragment(div, x2, "deleted"); + div.createEl("span", { text: x2, cls: "deleted normal conflict-dev-name" }); } else if (x1 == DIFF_EQUAL) { - diff += - "" + - escapeStringToHTML(x2).replace(/\n/g, "\n") + - ""; + this.appendDiffFragment(div, x2, "normal"); } else if (x1 == DIFF_INSERT) { - diff += - "" + - escapeStringToHTML(x2).replace(/\n/g, "\n") + - ""; + this.appendDiffFragment(div, x2, "added"); } } @@ -92,8 +106,8 @@ export class ConflictResolveModal extends Modal { new Date(this.result.left.mtime).toLocaleString() + (this.result.left.deleted ? " (Deleted)" : ""); const date2 = new Date(this.result.right.mtime).toLocaleString() + (this.result.right.deleted ? " (Deleted)" : ""); - div2.innerHTML = `${this.localName}: ${date1}
-${this.remoteName}: ${date2}
`; + this.appendVersionInfo(div2, "deleted", this.localName, date1); + this.appendVersionInfo(div2, "added", this.remoteName, date2); contentEl.createEl("button", { text: `Use ${this.localName}` }, (e) => e.addEventListener("click", () => this.sendResponse(this.result.right.rev)) ).style.marginRight = "4px"; @@ -108,11 +122,9 @@ export class ConflictResolveModal extends Modal { contentEl.createEl("button", { text: !this.pluginPickMode ? "Not now" : "Cancel" }, (e) => e.addEventListener("click", () => this.sendResponse(CANCELLED)) ).style.marginRight = "4px"; - diff = diff.replace(/\n/g, "
"); - if (diff.length > 100 * 1024) { + if (diffLength > 100 * 1024) { + div.empty(); div.innerText = "(Too large diff to display)"; - } else { - div.innerHTML = diff; } } diff --git a/src/modules/features/SettingDialogue/PaneChangeLog.ts b/src/modules/features/SettingDialogue/PaneChangeLog.ts index be0e0c3..3d8ceb9 100644 --- a/src/modules/features/SettingDialogue/PaneChangeLog.ts +++ b/src/modules/features/SettingDialogue/PaneChangeLog.ts @@ -43,10 +43,13 @@ export function paneChangeLog(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElem // tmpDiv.addClass("sls-header-button"); tmpDiv.addClass("op-warn-info"); - tmpDiv.innerHTML = `

${$msg("obsidianLiveSyncSettingTab.msgNewVersionNote")}

`; + tmpDiv.createEl("p", { text: $msg("obsidianLiveSyncSettingTab.msgNewVersionNote") }); + const readEverythingButton = tmpDiv.createEl("button", { + text: $msg("obsidianLiveSyncSettingTab.optionOkReadEverything"), + }); if (lastVersion > (this.editingSettings?.lastReadUpdates || 0)) { const informationButtonDiv = informationDivEl.appendChild(tmpDiv); - informationButtonDiv.querySelector("button")?.addEventListener("click", () => { + readEverythingButton.addEventListener("click", () => { fireAndForget(async () => { this.editingSettings.lastReadUpdates = lastVersion; await this.saveAllDirtySettings(); diff --git a/src/modules/features/SettingDialogue/PaneSetup.ts b/src/modules/features/SettingDialogue/PaneSetup.ts index de996bc..40cd88b 100644 --- a/src/modules/features/SettingDialogue/PaneSetup.ts +++ b/src/modules/features/SettingDialogue/PaneSetup.ts @@ -125,8 +125,13 @@ export function paneSetup( paneEl, "div", "", - (el) => - (el.innerHTML = `${$msg("obsidianLiveSyncSettingTab.linkOpenInBrowser")}`) + (el) => { + el.createEl("a", { text: $msg("obsidianLiveSyncSettingTab.linkOpenInBrowser") }, (anchor) => { + anchor.href = `https://github.com/${repo}/blob/main${topPath}`; + anchor.target = "_blank"; + anchor.rel = "noopener"; + }); + } ); const troubleShootEl = this.createEl(paneEl, "div", { text: "", diff --git a/src/modules/features/SettingDialogue/utilFixCouchDBSetting.ts b/src/modules/features/SettingDialogue/utilFixCouchDBSetting.ts index 041c923..d061643 100644 --- a/src/modules/features/SettingDialogue/utilFixCouchDBSetting.ts +++ b/src/modules/features/SettingDialogue/utilFixCouchDBSetting.ts @@ -13,7 +13,7 @@ export const checkConfig = async ( Logger($msg("obsidianLiveSyncSettingTab.logCheckingDbConfig"), LOG_LEVEL_INFO); let isSuccessful = true; const emptyDiv = createDiv(); - emptyDiv.innerHTML = ""; + emptyDiv.createSpan(); checkResultDiv?.replaceChildren(...[emptyDiv]); const addResult = (msg: string, classes?: string[]) => { const tmpDiv = createDiv(); @@ -21,7 +21,7 @@ export const checkConfig = async ( if (classes) { tmpDiv.addClasses(classes); } - tmpDiv.innerHTML = `${msg}`; + tmpDiv.textContent = msg; checkResultDiv?.appendChild(tmpDiv); }; try { @@ -47,9 +47,10 @@ export const checkConfig = async ( if (!checkResultDiv) return; const tmpDiv = createDiv(); tmpDiv.addClass("ob-btn-config-fix"); - tmpDiv.innerHTML = ``; + tmpDiv.createEl("label", { text: title }); + const fixButton = tmpDiv.createEl("button", { text: $msg("obsidianLiveSyncSettingTab.btnFix") }); const x = checkResultDiv.appendChild(tmpDiv); - x.querySelector("button")?.addEventListener("click", () => { + fixButton.addEventListener("click", () => { fireAndForget(async () => { Logger($msg("obsidianLiveSyncSettingTab.logCouchDbConfigSet", { title, key, value })); const res = await requestToCouchDBWithCredentials( From 55529cd71e87e0fcfc1f689d075d47375d274913 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 03:58:08 +0100 Subject: [PATCH 08/73] prettify --- .../testdeno/test-e2e-two-vaults-couchdb.ts | 3 ++- src/lib | 2 +- .../DocumentHistory/DocumentHistoryModal.ts | 3 +-- .../features/SettingDialogue/PaneSetup.ts | 19 +++++++------------ 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts b/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts index 6f5244b..0c0151a 100644 --- a/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts +++ b/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts @@ -29,7 +29,8 @@ export async function runScenario(remoteType: RemoteType, encrypt: boolean): Pro const dbPrefix = remoteType === "COUCHDB" ? requireEnv("COUCHDB_DBNAME", "dbname") : ""; const dbname = remoteType === "COUCHDB" ? `${dbPrefix}-${dbSuffix}` : ""; - const minioEndpoint = remoteType === "MINIO" ? requireEnv("MINIO_ENDPOINT", "minioEndpoint").replace(/\/$/, "") : ""; + const minioEndpoint = + remoteType === "MINIO" ? requireEnv("MINIO_ENDPOINT", "minioEndpoint").replace(/\/$/, "") : ""; const minioAccessKey = remoteType === "MINIO" ? requireEnv("MINIO_ACCESS_KEY", "accessKey") : ""; const minioSecretKey = remoteType === "MINIO" ? requireEnv("MINIO_SECRET_KEY", "secretKey") : ""; const minioBucketBase = remoteType === "MINIO" ? requireEnv("MINIO_BUCKET_NAME", "bucketName") : ""; diff --git a/src/lib b/src/lib index 00f7e65..e2fd8c3 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 00f7e653043daebe6b27594a275698bb7583123c +Subproject commit e2fd8c37d7d86c99b4534e6e8e8d403653cb0b1f diff --git a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts index 40a5254..b9cf49a 100644 --- a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts +++ b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts @@ -286,8 +286,7 @@ export class DocumentHistoryModal extends Modal { if (direction === "next") { this.currentDiffIndex = (this.currentDiffIndex + 1) % diffElements.length; } else { - this.currentDiffIndex = - this.currentDiffIndex <= 0 ? diffElements.length - 1 : this.currentDiffIndex - 1; + this.currentDiffIndex = this.currentDiffIndex <= 0 ? diffElements.length - 1 : this.currentDiffIndex - 1; } const target = diffElements[this.currentDiffIndex]; diff --git a/src/modules/features/SettingDialogue/PaneSetup.ts b/src/modules/features/SettingDialogue/PaneSetup.ts index 40cd88b..4e365c9 100644 --- a/src/modules/features/SettingDialogue/PaneSetup.ts +++ b/src/modules/features/SettingDialogue/PaneSetup.ts @@ -121,18 +121,13 @@ export function paneSetup( const repo = "vrtmrz/obsidian-livesync"; const topPath = $msg("obsidianLiveSyncSettingTab.linkTroubleshooting"); const rawRepoURI = `https://raw.githubusercontent.com/${repo}/main`; - this.createEl( - paneEl, - "div", - "", - (el) => { - el.createEl("a", { text: $msg("obsidianLiveSyncSettingTab.linkOpenInBrowser") }, (anchor) => { - anchor.href = `https://github.com/${repo}/blob/main${topPath}`; - anchor.target = "_blank"; - anchor.rel = "noopener"; - }); - } - ); + this.createEl(paneEl, "div", "", (el) => { + el.createEl("a", { text: $msg("obsidianLiveSyncSettingTab.linkOpenInBrowser") }, (anchor) => { + anchor.href = `https://github.com/${repo}/blob/main${topPath}`; + anchor.target = "_blank"; + anchor.rel = "noopener"; + }); + }); const troubleShootEl = this.createEl(paneEl, "div", { text: "", cls: "sls-troubleshoot-preview", From 5772811a455b160886d2b58cdf0b89d6b89b80fd Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 04:40:32 +0100 Subject: [PATCH 09/73] chore: Package modernise, update linter --- esbuild.config.mjs | 1 - eslint.config.mjs | 136 +- manifest.json | 2 +- package-lock.json | 1324 ++++++++++++++++- package.json | 11 +- .../SetupWizard/dialogs/ScanQRCode.svelte | 4 +- versions.json | 1 + vitest.config.p2p.ts | 8 +- vitest.config.ts | 9 +- 9 files changed, 1324 insertions(+), 172 deletions(-) diff --git a/esbuild.config.mjs b/esbuild.config.mjs index fe62f52..4350fab 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -2,7 +2,6 @@ import esbuild from "esbuild"; import process from "process"; -import builtins from "builtin-modules"; import sveltePlugin from "esbuild-svelte"; import { sveltePreprocess } from "svelte-preprocess"; import fs from "node:fs"; diff --git a/eslint.config.mjs b/eslint.config.mjs index 41cb90b..0efa595 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,103 +1,79 @@ -import typescriptEslint from "@typescript-eslint/eslint-plugin"; -import svelte from "eslint-plugin-svelte"; -import _import from "eslint-plugin-import"; -import { fixupPluginRules } from "@eslint/compat"; import tsParser from "@typescript-eslint/parser"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import js from "@eslint/js"; -import { FlatCompat } from "@eslint/eslintrc"; +import obsidianmd from "eslint-plugin-obsidianmd"; +import globals from "globals"; +import { defineConfig, globalIgnores } from "eslint/config"; +import * as sveltePlugin from "eslint-plugin-svelte"; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - allConfig: js.configs.all, -}); - -export default [ +export default defineConfig([ + globalIgnores([ + "**/node_modules/*", + "**/jest.config.js", + "src/lib/coverage", + "src/lib/browsertest", + "**/test.ts", + "**/tests.ts", + "**/**test.ts", + "**/**.test.ts", + "**/*.unit.spec.ts", + "**/esbuild.*.mjs", + "**/terser.*.mjs", + "**/node_modules", + "**/build", + "**/.eslintrc.js.bak", + "src/lib/src/patches/pouchdb-utils", + "**/esbuild.config.mjs", + "**/rollup.config.js", + "modules/octagonal-wheels/rollup.config.js", + "modules/octagonal-wheels/dist/**/*", + "src/lib/test", + "src/lib/_tools", + "src/lib/src/cli", + "**/main.js", + "src/apps/**/*", + ".prettierrc.*.mjs", + ".prettierrc.mjs", + "*.config.mjs", + ]), + ...sveltePlugin.configs["flat/base"], + ...obsidianmd.configs.recommended, { - ignores: [ - "**/node_modules/*", - "**/jest.config.js", - "src/lib/coverage", - "src/lib/browsertest", - "**/test.ts", - "**/tests.ts", - "**/**test.ts", - "**/**.test.ts", - "**/esbuild.*.mjs", - "**/terser.*.mjs", - "**/node_modules", - "**/build", - "**/.eslintrc.js.bak", - "src/lib/src/patches/pouchdb-utils", - "**/esbuild.config.mjs", - "**/rollup.config.js", - "modules/octagonal-wheels/rollup.config.js", - "modules/octagonal-wheels/dist/**/*", - "src/lib/test", - "src/lib/_tools", - "src/lib/src/cli", - "**/main.js", - "src/apps/**/*", - ".prettierrc.*.mjs", - ".prettierrc.mjs", - "*.config.mjs" - ], - }, - ...compat.extends( - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended" - ), - { - plugins: { - "@typescript-eslint": typescriptEslint, - svelte, - import: fixupPluginRules(_import), - }, - + files: ["**/*.ts"], languageOptions: { + globals: { ...globals.browser }, parser: tsParser, - ecmaVersion: 5, - sourceType: "module", - parserOptions: { - project: ["tsconfig.json"], + project: "./tsconfig.json", }, }, - rules: { "no-unused-vars": "off", - - "@typescript-eslint/no-unused-vars": [ - "error", - { - args: "none", - }, - ], - + "@typescript-eslint/no-unused-vars": ["error", { args: "none" }], "no-unused-labels": "off", "@typescript-eslint/ban-ts-comment": "off", "no-prototype-builtins": "off", "@typescript-eslint/no-empty-function": "off", "require-await": "error", + "obsidianmd/rule-custom-message": "off", // Temporary + "obsidianmd/ui/sentence-case": "off", // Temporary "@typescript-eslint/require-await": "warn", "@typescript-eslint/no-misused-promises": "warn", "@typescript-eslint/no-floating-promises": "warn", "no-async-promise-executor": "warn", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-unnecessary-type-assertion": "error", - - "no-constant-condition": [ - "error", - { - checkLoops: false, - }, - ], + "no-constant-condition": ["error", { checkLoops: false }], }, }, -]; - + { + files: ["**/*.svelte"], + languageOptions: { + parserOptions: { + parser: tsParser, + }, + }, + rules: { + "no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }], + "obsidianmd/no-plugin-as-component": "off", // Temporary + }, + }, +]); diff --git a/manifest.json b/manifest.json index cb9b9f1..f5ae865 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "id": "obsidian-livesync", "name": "Self-hosted LiveSync", "version": "0.25.60", - "minAppVersion": "0.9.12", + "minAppVersion": "1.2.3", "description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "author": "vorotamoroz", "authorUrl": "https://github.com/vrtmrz", diff --git a/package-lock.json b/package-lock.json index 0f1f682..03349a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "idb": "^8.0.3", "markdown-it": "^14.1.1", "minimatch": "^10.2.2", + "obsidian": "^1.12.3", "octagonal-wheels": "^0.1.45", "pouchdb-adapter-leveldb": "^9.0.0", "qrcode-generator": "^1.4.4", @@ -30,8 +31,6 @@ }, "devDependencies": { "@chialab/esbuild-plugin-worker": "^0.19.0", - "@eslint/compat": "^2.0.2", - "@eslint/eslintrc": "^3.3.4", "@eslint/js": "^9.39.3", "@sveltejs/vite-plugin-svelte": "^6.2.4", "@tsconfig/svelte": "^5.0.8", @@ -52,18 +51,15 @@ "@vitest/browser": "^4.1.1", "@vitest/browser-playwright": "^4.1.1", "@vitest/coverage-v8": "^4.1.1", - "builtin-modules": "5.0.0", - "dotenv": "^17.3.1", "dotenv-cli": "^11.0.0", "esbuild": "0.25.0", "esbuild-plugin-inline-worker": "^0.1.1", "esbuild-svelte": "^0.9.4", "eslint": "^9.39.3", - "eslint-plugin-import": "^2.32.0", + "eslint-plugin-obsidianmd": "^0.3.0", "eslint-plugin-svelte": "^3.15.0", "events": "^3.3.0", - "glob": "^13.0.6", - "obsidian": "^1.12.3", + "globals": "^14.0.0", "playwright": "^1.58.2", "postcss": "^8.5.6", "postcss-load-config": "^6.0.1", @@ -84,6 +80,7 @@ "svelte-check": "^4.4.3", "svelte-preprocess": "^6.0.3", "terser": "^5.39.0", + "tinyglobby": "^0.2.15", "transform-pouch": "^2.0.0", "tslib": "^2.8.1", "tsx": "^4.21.0", @@ -1338,7 +1335,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz", "integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==", - "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -1349,7 +1345,6 @@ "version": "6.38.6", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz", "integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==", - "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -1830,27 +1825,6 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/compat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.3.tgz", - "integrity": "sha512-SjIJhGigp8hmd1YGIBwh7Ovri7Kisl42GYFjrOyHhtfYGGoLW6teYi/5p8W50KSsawUPpuLOSmsq1bD0NGQLBw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.1.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "peerDependencies": { - "eslint": "^8.40 || 9 || 10" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, "node_modules/@eslint/config-array": { "version": "0.21.2", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", @@ -1923,19 +1897,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.0.tgz", - "integrity": "sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, "node_modules/@eslint/eslintrc": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", @@ -2004,6 +1965,35 @@ "url": "https://eslint.org/donate" } }, + "node_modules/@eslint/json": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/json/-/json-0.14.0.tgz", + "integrity": "sha512-rvR/EZtvUG3p9uqrSmcDJPYSH7atmWr0RnFWN6m917MAPx82+zQgPUmDu0whPFG6XTyM0vB/hR6c1Q63OaYtCQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "@eslint/plugin-kit": "^0.4.1", + "@humanwhocodes/momoa": "^3.3.10", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/json/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/object-schema": { "version": "2.1.7", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", @@ -2101,6 +2091,16 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@humanwhocodes/momoa": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/momoa/-/momoa-3.3.10.tgz", + "integrity": "sha512-KWiFQpSAqEIyrTXko3hFNLeQvSK8zXlJQzhhxsyVn58WFRYXST99b3Nqnu+ttOtjds2Pl2grUHGpe2NzhPynuQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/@humanwhocodes/retry": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", @@ -2377,9 +2377,56 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", - "dev": true, "license": "MIT" }, + "node_modules/@microsoft/eslint-plugin-sdl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@microsoft/eslint-plugin-sdl/-/eslint-plugin-sdl-1.1.0.tgz", + "integrity": "sha512-dxdNHOemLnBhfY3eByrujX9KyLigcNtW8sU+axzWv5nLGcsSBeKW2YYyTpfPo1hV8YPOmIGnfA4fZHyKVtWqBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-plugin-n": "17.10.3", + "eslint-plugin-react": "7.37.3", + "eslint-plugin-security": "1.4.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "eslint": "^9" + } + }, + "node_modules/@microsoft/eslint-plugin-sdl/node_modules/eslint-plugin-security": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-1.4.0.tgz", + "integrity": "sha512-xlS7P2PLMXeqfhyf3NpqbvbnW04kN8M9NtmhpR3XGyOvt/vNKS7XPXT5EDbwKW9vCjWH4PpfQvgD/+JgN0VJKA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-regex": "^1.1.0" + } + }, + "node_modules/@microsoft/eslint-plugin-sdl/node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12" + } + }, + "node_modules/@microsoft/eslint-plugin-sdl/node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ret": "~0.1.10" + } + }, "node_modules/@minhducsun2002/leb128": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@minhducsun2002/leb128/-/leb128-1.0.0.tgz", @@ -3002,6 +3049,19 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.2.tgz", + "integrity": "sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", @@ -4313,7 +4373,6 @@ "version": "5.60.8", "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", - "dev": true, "license": "MIT", "dependencies": { "@types/tern": "*" @@ -4350,11 +4409,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, "license": "MIT" }, "node_modules/@types/fs-extra": { @@ -4649,7 +4718,6 @@ "version": "0.23.9", "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "*" @@ -5756,6 +5824,27 @@ "node": ">=8" } }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.findlastindex": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", @@ -5816,6 +5905,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/arraybuffer.prototype.slice": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", @@ -6207,29 +6313,16 @@ "dev": true, "license": "MIT" }, - "node_modules/builtin-modules": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-5.0.0.tgz", - "integrity": "sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" }, "engines": { @@ -6647,7 +6740,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", - "dev": true, "license": "MIT" }, "node_modules/cross-spawn": { @@ -7206,6 +7298,16 @@ "dev": true, "license": "MIT" }, + "node_modules/empathic": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz", + "integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/encoding-down": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", @@ -7254,6 +7356,20 @@ "write-stream": "~0.4.3" } }, + "node_modules/enhanced-resolve": { + "version": "5.21.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.3.tgz", + "integrity": "sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -7279,9 +7395,9 @@ } }, "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", "dev": true, "license": "MIT", "dependencies": { @@ -7367,6 +7483,34 @@ "node": ">= 0.4" } }, + "node_modules/es-iterator-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz", + "integrity": "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -7610,6 +7754,22 @@ } } }, + "node_modules/eslint-compat-utils": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", + "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", @@ -7660,6 +7820,40 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-plugin-depend": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-depend/-/eslint-plugin-depend-1.3.1.tgz", + "integrity": "sha512-1uo2rFAr9vzNrCYdp7IBZRB54LiyVxfaIso0R6/QV3t6Dax6DTbW/EV2Hktf0f4UtmGHK8UyzJWI382pwW04jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "empathic": "^2.0.0", + "module-replacements": "^2.8.0", + "semver": "^7.6.3" + } + }, + "node_modules/eslint-plugin-es-x": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", + "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/ota-meshi", + "https://opencollective.com/eslint" + ], + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.11.0", + "eslint-compat-utils": "^0.5.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": ">=8" + } + }, "node_modules/eslint-plugin-import": { "version": "2.32.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", @@ -7745,6 +7939,354 @@ "semver": "bin/semver.js" } }, + "node_modules/eslint-plugin-json-schema-validator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-json-schema-validator/-/eslint-plugin-json-schema-validator-5.1.0.tgz", + "integrity": "sha512-ZmVyxRIjm58oqe2kTuy90PpmZPrrKvOjRPXKzq8WCgRgAkidCgm5X8domL2KSfadZ3QFAmifMgGTcVNhZ5ez2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.3.0", + "ajv": "^8.0.0", + "debug": "^4.3.1", + "eslint-compat-utils": "^0.5.0", + "json-schema-migrate": "^2.0.0", + "jsonc-eslint-parser": "^2.0.0", + "minimatch": "^8.0.0", + "synckit": "^0.9.0", + "toml-eslint-parser": "^0.9.0", + "tunnel-agent": "^0.6.0", + "yaml-eslint-parser": "^1.0.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-plugin-json-schema-validator/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint-plugin-json-schema-validator/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-json-schema-validator/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/eslint-plugin-json-schema-validator/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-json-schema-validator/node_modules/minimatch": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.7.tgz", + "integrity": "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint-plugin-n": { + "version": "17.10.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.10.3.tgz", + "integrity": "sha512-ySZBfKe49nQZWR1yFaA0v/GsH6Fgp8ah6XV0WDz6CN8WO0ek4McMzb7A2xnf4DCYV43frjCygvb9f/wx7UUxRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "enhanced-resolve": "^5.17.0", + "eslint-plugin-es-x": "^7.5.0", + "get-tsconfig": "^4.7.0", + "globals": "^15.8.0", + "ignore": "^5.2.4", + "minimatch": "^9.0.5", + "semver": "^7.5.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": ">=8.23.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-n/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-n/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint-plugin-no-unsanitized": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-4.1.5.tgz", + "integrity": "sha512-MSB4hXPVFQrI8weqzs6gzl7reP2k/qSjtCoL2vUMSDejIIq9YL1ZKvq5/ORBXab/PvfBBrWO2jWviYpL+4Ghfg==", + "dev": true, + "license": "MPL-2.0", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-plugin-obsidianmd": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-obsidianmd/-/eslint-plugin-obsidianmd-0.3.0.tgz", + "integrity": "sha512-QvGDI6B2nxJBrsZKGTg31da2A/fEJNlnwN+fRZkaoPIu1QL3fYXUdpP7ThyMdr/0iTYQxifb9lt2X9cpydQx1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint/config-helpers": "^0.4.2", + "@eslint/js": "^9.30.1", + "@eslint/json": "0.14.0", + "@microsoft/eslint-plugin-sdl": "^1.1.0", + "@types/eslint": "9.6.1", + "@types/node": "20.12.12", + "@typescript-eslint/types": "^8.33.1", + "@typescript-eslint/utils": "^8.33.1", + "eslint": ">=9.0.0", + "eslint-plugin-depend": "1.3.1", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-json-schema-validator": "5.1.0", + "eslint-plugin-no-unsanitized": "^4.1.5", + "eslint-plugin-security": "2.1.1", + "globals": "14.0.0", + "obsidian": "1.12.3", + "semver": "^7.7.4", + "typescript": "5.4.5", + "typescript-eslint": "^8.35.1" + }, + "bin": { + "eslint-plugin-obsidian": "dist/lib/index.js" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@eslint/js": "^9.30.1", + "@eslint/json": "0.14.0", + "eslint": ">=9.0.0", + "obsidian": "1.8.7", + "typescript-eslint": "^8.35.1" + } + }, + "node_modules/eslint-plugin-obsidianmd/node_modules/@types/node": { + "version": "20.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", + "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/eslint-plugin-obsidianmd/node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/eslint-plugin-obsidianmd/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.3.tgz", + "integrity": "sha512-DomWuTQPFYZwF/7c9W2fkKkStqZmBd3uugfqBYLdkZ3Hii23WzZuOLUskGxB8qkSKqftxEeGL1TB2kMhrce0jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-security": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-2.1.1.tgz", + "integrity": "sha512-7cspIGj7WTfR3EhaILzAPcfCo5R9FbeWvbgsPYWivSurTBKW88VQxtP3c4aWMG9Hz/GfJlJVdXEJ3c8LqS+u2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-regex": "^2.1.1" + } + }, "node_modules/eslint-plugin-svelte": { "version": "3.16.0", "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.16.0.tgz", @@ -8166,6 +8708,23 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fast-xml-builder": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.5.tgz", @@ -9673,6 +10232,24 @@ "node": ">=8" } }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -9740,6 +10317,40 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-migrate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/json-schema-migrate/-/json-schema-migrate-2.0.0.tgz", + "integrity": "sha512-r38SVTtojDRp4eD6WsCqiE0eNDt4v1WalBXb9cyZYw9ai5cGtBwzRNWjHzJl38w6TxFkXAIA7h+fyX3tnrAFhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + } + }, + "node_modules/json-schema-migrate/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/json-schema-migrate/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -9767,6 +10378,43 @@ "json5": "lib/cli.js" } }, + "node_modules/jsonc-eslint-parser": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.2.tgz", + "integrity": "sha512-1e4qoRgnn448pRuMvKGsFFymUCquZV0mpGgOyIKNgD3JVDTsVJyRBGH/Fm0tBb8WsWGgmB1mDe6/yJMQM37DUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.5.0", + "eslint-visitor-keys": "^3.0.0", + "espree": "^9.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, + "node_modules/jsonc-eslint-parser/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -9777,6 +10425,22 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -10271,6 +10935,26 @@ "dev": true, "license": "MIT" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loose-envify/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "11.2.7", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", @@ -10494,11 +11178,17 @@ "node": ">=18.0.0" } }, + "node_modules/module-replacements": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/module-replacements/-/module-replacements-2.11.0.tgz", + "integrity": "sha512-j5sNQm3VCpQQ7nTqGeOZtoJtV3uKERgCBm9QRhmGRiXiqkf7iRFOkfxdJRZWLkqYY8PNf4cDQF/WfXUYLENrRA==", + "dev": true, + "license": "MIT" + }, "node_modules/moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "dev": true, "license": "MIT", "engines": { "node": "*" @@ -10591,6 +11281,35 @@ "node": ">= 0.4.0" } }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/node-fetch": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", @@ -10652,6 +11371,16 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -10696,6 +11425,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/object.fromentries": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", @@ -10753,7 +11498,6 @@ "version": "1.12.3", "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.12.3.tgz", "integrity": "sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw==", - "dev": true, "license": "MIT", "dependencies": { "@types/codemirror": "5.60.8", @@ -11744,6 +12488,18 @@ "node": ">=0.4.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/proxy-agent": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", @@ -11885,6 +12641,13 @@ ], "license": "MIT" }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -11982,6 +12745,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "dev": true, + "license": "MIT", + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -12013,6 +12786,16 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -12263,6 +13046,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", + "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "regexp-tree": "~0.1.1" + } + }, "node_modules/safe-regex-test": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", @@ -12817,6 +13610,45 @@ "node": ">=8" } }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", @@ -12955,7 +13787,6 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", - "dev": true, "license": "MIT" }, "node_modules/sublevel-pouchdb": { @@ -13188,6 +14019,37 @@ } } }, + "node_modules/synckit": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.3.tgz", + "integrity": "sha512-JJoOEKTfL1urb1mDoEblhD9NhEbWmq9jHEMEnxoC4ujUaZ4itA8vKgwkFAyNClgxplLi9tsUKX+EduK0p/l7sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/tar-stream": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", @@ -13367,6 +14229,22 @@ "node": ">=8.0" } }, + "node_modules/toml-eslint-parser": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/toml-eslint-parser/-/toml-eslint-parser-0.9.3.tgz", + "integrity": "sha512-moYoCvkNUAPCxSW9jmHmRElhm4tVJpHL8ItC/+uYD0EpPSFXbck7yREz9tNdJVTSpHVod8+HoipcpbQ0oE6gsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, "node_modules/totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", @@ -13970,6 +14848,19 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "license": "0BSD" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", @@ -14095,6 +14986,274 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.3.tgz", + "integrity": "sha512-KgusgyDgG4LI8Ih/sWaCtZ06tckLAS5CvT5A4D1Q7bYVoAAyzwiZvE4BmwDHkhRVkvhRBepKeASoFzQetha7Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.59.3", + "@typescript-eslint/parser": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/utils": "8.59.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.3.tgz", + "integrity": "sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/type-utils": "8.59.3", + "@typescript-eslint/utils": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.59.3", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.3.tgz", + "integrity": "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/project-service": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.3.tgz", + "integrity": "sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.59.3", + "@typescript-eslint/types": "^8.59.3", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.3.tgz", + "integrity": "sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.3.tgz", + "integrity": "sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/type-utils": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.3.tgz", + "integrity": "sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/utils": "8.59.3", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.3.tgz", + "integrity": "sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.3.tgz", + "integrity": "sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.59.3", + "@typescript-eslint/tsconfig-utils": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.3.tgz", + "integrity": "sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.3.tgz", + "integrity": "sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.3", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/typescript-eslint/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", @@ -15014,7 +16173,6 @@ "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", - "dev": true, "license": "MIT" }, "node_modules/wait-port": { @@ -15667,7 +16825,6 @@ "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "dev": true, "license": "ISC", - "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -15678,6 +16835,23 @@ "url": "https://github.com/sponsors/eemeli" } }, + "node_modules/yaml-eslint-parser": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/yaml-eslint-parser/-/yaml-eslint-parser-1.3.2.tgz", + "integrity": "sha512-odxVsHAkZYYglR30aPYRY4nUGJnoJ2y1ww2HDvZALo0BDETv9kWbi16J52eHs+PWRNmF4ub6nZqfVOeesOvntg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.0.0", + "yaml": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index 479780a..3c79d93 100644 --- a/package.json +++ b/package.json @@ -61,8 +61,6 @@ "license": "MIT", "devDependencies": { "@chialab/esbuild-plugin-worker": "^0.19.0", - "@eslint/compat": "^2.0.2", - "@eslint/eslintrc": "^3.3.4", "@eslint/js": "^9.39.3", "@sveltejs/vite-plugin-svelte": "^6.2.4", "@tsconfig/svelte": "^5.0.8", @@ -83,18 +81,15 @@ "@vitest/browser": "^4.1.1", "@vitest/browser-playwright": "^4.1.1", "@vitest/coverage-v8": "^4.1.1", - "builtin-modules": "5.0.0", - "dotenv": "^17.3.1", "dotenv-cli": "^11.0.0", "esbuild": "0.25.0", "esbuild-plugin-inline-worker": "^0.1.1", "esbuild-svelte": "^0.9.4", "eslint": "^9.39.3", - "eslint-plugin-import": "^2.32.0", + "eslint-plugin-obsidianmd": "^0.3.0", "eslint-plugin-svelte": "^3.15.0", "events": "^3.3.0", - "glob": "^13.0.6", - "obsidian": "^1.12.3", + "globals": "^14.0.0", "playwright": "^1.58.2", "postcss": "^8.5.6", "postcss-load-config": "^6.0.1", @@ -115,6 +110,7 @@ "svelte-check": "^4.4.3", "svelte-preprocess": "^6.0.3", "terser": "^5.39.0", + "tinyglobby": "^0.2.15", "transform-pouch": "^2.0.0", "tslib": "^2.8.1", "tsx": "^4.21.0", @@ -134,6 +130,7 @@ "@smithy/querystring-builder": "^4.2.9", "@trystero-p2p/nostr": "^0.23.0", "commander": "^14.0.3", + "obsidian": "^1.12.3", "diff-match-patch": "^1.0.5", "fflate": "^0.8.2", "idb": "^8.0.3", diff --git a/src/modules/features/SetupWizard/dialogs/ScanQRCode.svelte b/src/modules/features/SetupWizard/dialogs/ScanQRCode.svelte index 58ad125..57c0621 100644 --- a/src/modules/features/SetupWizard/dialogs/ScanQRCode.svelte +++ b/src/modules/features/SetupWizard/dialogs/ScanQRCode.svelte @@ -4,10 +4,10 @@ import Decision from "@/lib/src/UI/components/Decision.svelte"; import Instruction from "@/lib/src/UI/components/Instruction.svelte"; import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte"; - const TYPE_CLOSE = "close"; + const TYPE_CLOSE = "close"; type ResultType = typeof TYPE_CLOSE; type Props = { - setResult: (result: ResultType) => void; + setResult: (_result: ResultType) => void; }; const { setResult }: Props = $props(); diff --git a/versions.json b/versions.json index 3366a32..ae232e6 100644 --- a/versions.json +++ b/versions.json @@ -1,4 +1,5 @@ { + "0.25.60": "1.2.3", "1.0.1": "0.9.12", "1.0.0": "0.9.7" } diff --git a/vitest.config.p2p.ts b/vitest.config.p2p.ts index a968e9f..56f3244 100644 --- a/vitest.config.p2p.ts +++ b/vitest.config.p2p.ts @@ -2,7 +2,8 @@ import { defineConfig, mergeConfig } from "vitest/config"; import { playwright } from "@vitest/browser-playwright"; import viteConfig from "./vitest.config.common"; import path from "path"; -import dotenv from "dotenv"; +import { existsSync, readFileSync } from "node:fs"; +import { parseEnv } from "node:util"; import { grantClipboardPermissions, writeHandoffFile, readHandoffFile } from "./test/lib/commands"; // P2P test environment variables @@ -22,8 +23,9 @@ import { grantClipboardPermissions, writeHandoffFile, readHandoffFile } from "./ // General test options (also read from env): // ENABLE_DEBUGGER - Set to "true" to attach a debugger and pause before tests // ENABLE_UI - Set to "true" to open a visible browser window during tests -const defEnv = dotenv.config({ path: ".env" }).parsed; -const testEnv = dotenv.config({ path: ".test.env" }).parsed; +const loadEnvFile = (path: string) => (existsSync(path) ? parseEnv(readFileSync(path, "utf-8")) : undefined); +const defEnv = loadEnvFile(".env"); +const testEnv = loadEnvFile(".test.env"); // Merge: dotenv files < process.env (so shell-injected vars like P2P_TEST_* take precedence) const p2pEnv: Record = {}; if (process.env.P2P_TEST_ROOM_ID) p2pEnv.P2P_TEST_ROOM_ID = process.env.P2P_TEST_ROOM_ID; diff --git a/vitest.config.ts b/vitest.config.ts index 8818ee8..d5e74d4 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,10 +2,13 @@ import { defineConfig, mergeConfig } from "vitest/config"; import { playwright } from "@vitest/browser-playwright"; import viteConfig from "./vitest.config.common"; import path from "path"; -import dotenv from "dotenv"; +import { existsSync, readFileSync } from "node:fs"; +import { parseEnv } from "node:util"; import { grantClipboardPermissions, openWebPeer, closeWebPeer, acceptWebPeer } from "./test/lib/commands"; -const defEnv = dotenv.config({ path: ".env" }).parsed; -const testEnv = dotenv.config({ path: ".test.env" }).parsed; + +const loadEnvFile = (path: string) => (existsSync(path) ? parseEnv(readFileSync(path, "utf-8")) : undefined); +const defEnv = loadEnvFile(".env"); +const testEnv = loadEnvFile(".test.env"); const env = Object.assign({}, defEnv, testEnv); const debuggerEnabled = env?.ENABLE_DEBUGGER === "true"; const enableUI = env?.ENABLE_UI === "true"; From 67996f6d0afa8956048c2a0824ba77472970b6ec Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 13 May 2026 16:53:47 +1000 Subject: [PATCH 10/73] cli: fix stale stat.size in NodeVaultAdapter causing corrupted file errors chokidar stats are captured at poll time and may not reflect the file's final byte length by the time vault.read() is called. The downstream integrity check compares stat.size to content length; a mismatch causes other LiveSync clients to reject the file as corrupted. Fix by updating file.stat.size from the actual content in read() and readBinary(). Co-authored-by: Joysimple --- src/apps/cli/adapters/NodeVaultAdapter.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/apps/cli/adapters/NodeVaultAdapter.ts b/src/apps/cli/adapters/NodeVaultAdapter.ts index 947ad01..89e6911 100644 --- a/src/apps/cli/adapters/NodeVaultAdapter.ts +++ b/src/apps/cli/adapters/NodeVaultAdapter.ts @@ -15,7 +15,12 @@ export class NodeVaultAdapter implements IVaultAdapter { } async read(file: NodeFile): Promise { - return await fs.readFile(this.resolvePath(file.path), "utf-8"); + const content = await fs.readFile(this.resolvePath(file.path), "utf-8"); + // Correct stale stat.size — chokidar stats may be from a poll before the final write. + // The downstream document integrity check compares stat.size to content length, so + // they must agree or other clients reject the file as corrupted. + file.stat.size = Buffer.byteLength(content, "utf-8"); + return content; } async cachedRead(file: NodeFile): Promise { @@ -25,6 +30,8 @@ export class NodeVaultAdapter implements IVaultAdapter { async readBinary(file: NodeFile): Promise { 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; return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) as ArrayBuffer; } From 3b311248cb979f089b8f29d4403f1f818e8142a3 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 08:02:50 +0100 Subject: [PATCH 11/73] Update for review once --- manifest.json | 2 +- src/lib | 2 +- src/serviceFeatures/onLayoutReady/enablei18n.ts | 13 ++++++++++++- src/serviceFeatures/useP2PReplicatorUI.ts | 3 ++- .../FileSystemAdapters/ObsidianVaultAdapter.ts | 12 +++++++++++- versions.json | 2 +- 6 files changed, 28 insertions(+), 6 deletions(-) diff --git a/manifest.json b/manifest.json index f5ae865..6f3ee49 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "id": "obsidian-livesync", "name": "Self-hosted LiveSync", "version": "0.25.60", - "minAppVersion": "1.2.3", + "minAppVersion": "1.7.2", "description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "author": "vorotamoroz", "authorUrl": "https://github.com/vrtmrz", diff --git a/src/lib b/src/lib index e2fd8c3..568ed49 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit e2fd8c37d7d86c99b4534e6e8e8d403653cb0b1f +Subproject commit 568ed49d67cece9d49ebd6256bc0f7b9cf8c308c diff --git a/src/serviceFeatures/onLayoutReady/enablei18n.ts b/src/serviceFeatures/onLayoutReady/enablei18n.ts index 4d79915..ba74491 100644 --- a/src/serviceFeatures/onLayoutReady/enablei18n.ts +++ b/src/serviceFeatures/onLayoutReady/enablei18n.ts @@ -3,11 +3,22 @@ import { createServiceFeature } from "@lib/interfaces/ServiceModule"; import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "@lib/common/rosetta"; import { $msg, setLang } from "@lib/common/i18n"; +function tryGetLanguage() { + try { + // Note: 1.8.7+ is required. but it is 18, Feb., 2025. we want to fallback on earlier versions, so we catch the error here. + // eslint-disable-next-line obsidianmd/no-unsupported-api + return getLanguage(); + } catch (e) { + console.error("Failed to get Obsidian language, defaulting to 'def'", e); + return "en"; + } +} + export const enableI18nFeature = createServiceFeature(async ({ services: { setting, API } }) => { let isChanged = false; const settings = setting.currentSettings(); if (settings.displayLanguage == "") { - const obsidianLanguage = getLanguage(); + const obsidianLanguage = tryGetLanguage(); if ( SUPPORTED_I18N_LANGS.indexOf(obsidianLanguage) !== -1 && // Check if the language is supported obsidianLanguage != settings.displayLanguage // Check if the language is different from the current setting diff --git a/src/serviceFeatures/useP2PReplicatorUI.ts b/src/serviceFeatures/useP2PReplicatorUI.ts index bfe042d..a2ea2d1 100644 --- a/src/serviceFeatures/useP2PReplicatorUI.ts +++ b/src/serviceFeatures/useP2PReplicatorUI.ts @@ -5,6 +5,7 @@ import { type UseP2PReplicatorResult } from "@/lib/src/replication/trystero/UseP import { P2PLogCollector } from "@/lib/src/replication/trystero/P2PLogCollector"; import { P2PReplicatorPaneView, VIEW_TYPE_P2P } from "@/features/P2PSync/P2PReplicator/P2PReplicatorPaneView"; import type { LiveSyncCore } from "@/main"; +import type { WorkspaceLeaf } from "@/deps"; /** * ServiceFeature: P2P Replicator lifecycle management. @@ -43,7 +44,7 @@ export function useP2PReplicatorUI( // Register view, commands and ribbon if a view factory is provided const viewType = VIEW_TYPE_P2P; - const factory = (leaf: any) => { + const factory = (leaf: WorkspaceLeaf) => { return new P2PReplicatorPaneView(leaf, core, { replicator: getReplicator(), p2pLogCollector, diff --git a/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts b/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts index a7d996c..78d3559 100644 --- a/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts +++ b/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts @@ -7,7 +7,7 @@ import type { TFile, App, TFolder } from "obsidian"; * Vault adapter implementation for Obsidian */ export class ObsidianVaultAdapter implements IVaultAdapter { - constructor(private app: App) {} + constructor(private app: App) { } async read(file: TFile): Promise { return await this.app.vault.read(file); @@ -38,10 +38,20 @@ export class ObsidianVaultAdapter implements IVaultAdapter { } async delete(file: TFile | TFolder, force = false): Promise { + // if ("trashFile" in this.app.fileManager) { + // // eslint-disable-next-line obsidianmd/no-unsupported-api + // return await this.app.fileManager.trashFile(file); + // } + //TODO: need fix return await this.app.vault.delete(file, force); } async trash(file: TFile | TFolder, force = false): Promise { + // if ("trashFile" in this.app.fileManager) { + // // eslint-disable-next-line obsidianmd/no-unsupported-api + // return await this.app.fileManager.trashFile(file); + // } + //TODO: need fix return await this.app.vault.trash(file, force); } diff --git a/versions.json b/versions.json index ae232e6..c5ab81e 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "0.25.60": "1.2.3", + "0.25.60": "1.7.2", "1.0.1": "0.9.12", "1.0.0": "0.9.7" } From 770d4af4a099c95a1230c9aa29d5e3cebbe699c7 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 10:15:45 +0100 Subject: [PATCH 12/73] Update eslint config to ignore file, fix some type error on LiveSyncBaseCore --- eslint.config.mjs | 4 ++++ src/LiveSyncBaseCore.ts | 18 ++++++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 0efa595..35b0d25 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -33,6 +33,10 @@ export default defineConfig([ ".prettierrc.*.mjs", ".prettierrc.mjs", "*.config.mjs", + "src/apps/**/*", + "src/lib/src/services/implements/browser/**", + "src/lib/src/services/implements/headless/**", + "src/lib/src/API", ]), ...sveltePlugin.configs["flat/base"], ...obsidianmd.configs.recommended, diff --git a/src/LiveSyncBaseCore.ts b/src/LiveSyncBaseCore.ts index f9f0427..a3945fd 100644 --- a/src/LiveSyncBaseCore.ts +++ b/src/LiveSyncBaseCore.ts @@ -1,4 +1,5 @@ import { LOG_LEVEL_INFO } from "octagonal-wheels/common/logger"; +import type PouchDB from "pouchdb-core"; import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase"; import type { HasSettings, ObsidianLiveSyncSettings, EntryDoc } from "./lib/src/common/types"; import { __$checkInstanceBinding } from "./lib/src/dev/checks"; @@ -34,12 +35,11 @@ export class LiveSyncBaseCore< TCommands extends IMinimumLiveSyncCommands = IMinimumLiveSyncCommands, > implements - LiveSyncLocalDBEnv, - LiveSyncReplicatorEnv, - LiveSyncJournalReplicatorEnv, - LiveSyncCouchDBReplicatorEnv, - HasSettings -{ + LiveSyncLocalDBEnv, + LiveSyncReplicatorEnv, + LiveSyncJournalReplicatorEnv, + LiveSyncCouchDBReplicatorEnv, + HasSettings { addOns = [] as TCommands[]; /** @@ -123,7 +123,7 @@ export class LiveSyncBaseCore< for (const module of this.modules) { if (module.constructor === constructor) return module as T; } - throw new Error(`Module ${constructor} not found or not loaded.`); + throw new Error(`Module ${constructor.name} not found or not loaded.`); } /** @@ -160,8 +160,10 @@ export class LiveSyncBaseCore< module.onBindFunction(this, this.services); __$checkInstanceBinding(module); // Check if all functions are properly bound, and log warnings if not. } else { + // module should not be never. + const moduleName = (module as unknown)?.constructor?.name ?? "unknown"; this.services.API.addLog( - `Module ${(module as any)?.constructor?.name ?? "unknown"} does not have onBindFunction, skipping binding.`, + `Module ${moduleName} does not have onBindFunction, skipping binding.`, LOG_LEVEL_INFO ); } From df79d81475e52682fb5596fcd08a695994f2769e Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 10:14:47 +0000 Subject: [PATCH 13/73] fixed: fixed unexpected error during startup --- src/modules/main/ModuleLiveSyncMain.ts | 8 +++++--- updates.md | 5 +++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/modules/main/ModuleLiveSyncMain.ts b/src/modules/main/ModuleLiveSyncMain.ts index d392c3e..b1765b2 100644 --- a/src/modules/main/ModuleLiveSyncMain.ts +++ b/src/modules/main/ModuleLiveSyncMain.ts @@ -61,10 +61,12 @@ export class ModuleLiveSyncMain extends AbstractModule { eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => { fireAndForget(async () => { try { - await this.core.services.control.applySettings(); - const lang = this.core.services.setting.currentSettings()?.displayLanguage ?? undefined; + const lang = this.core.services.setting.currentSettings()?.displayLanguage; if (lang !== undefined) { - setLang(this.core.services.setting.currentSettings()?.displayLanguage); + setLang(lang); + } + if (this.core.services.database.isDatabaseReady()) { + await this.core.services.control.applySettings(); } eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB); } catch (e) { diff --git a/updates.md b/updates.md index 0d65ee5..7308bad 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,11 @@ Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025) The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). Because 0.25 got a lot of updates, thankfully, compatibility is kept and we do not need breaking changes! In other words, when get enough stabled. The next version will be v1.0.0. Even though it my hope. +## Unreleased + +### Fixed +- No longer baffling errors occur when setting-update is triggered during the early stage of initialisation. + ## 0.25.60 29th April, 2026 From 6c30f2b8637026290f42883feb8201e8422e5052 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 03:55:11 +0100 Subject: [PATCH 14/73] (chore): removing DOM Operation --- src/apps/webapp/bootstrap.ts | 2 +- src/lib | 2 +- .../DocumentHistory/DocumentHistoryModal.ts | 119 ++++++++++++------ .../ConflictResolveModal.ts | 52 +++++--- .../features/SettingDialogue/PaneChangeLog.ts | 7 +- .../features/SettingDialogue/PaneSetup.ts | 9 +- .../SettingDialogue/utilFixCouchDBSetting.ts | 9 +- 7 files changed, 131 insertions(+), 69 deletions(-) diff --git a/src/apps/webapp/bootstrap.ts b/src/apps/webapp/bootstrap.ts index 2450285..b3fa072 100644 --- a/src/apps/webapp/bootstrap.ts +++ b/src/apps/webapp/bootstrap.ts @@ -41,7 +41,7 @@ async function renderHistoryList(): Promise { const [items, lastUsedId] = await Promise.all([historyStore.getVaultHistory(), historyStore.getLastUsedVaultId()]); - listEl.innerHTML = ""; + listEl.replaceChildren(); emptyEl.classList.toggle("is-hidden", items.length > 0); for (const item of items) { diff --git a/src/lib b/src/lib index 6a2dc67..6c53e74 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 6a2dc6777f1eb2beb7a058b8d2dde662662df9d7 +Subproject commit 6c53e748eb3dff92514e1cd28359007c8fcb3173 diff --git a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts index 7e7560a..40a5254 100644 --- a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts +++ b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts @@ -1,6 +1,6 @@ import { TFile, Modal, App, DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "../../../deps.ts"; import { getPathFromTFile, isValidPath } from "../../../common/utils.ts"; -import { decodeBinary, escapeStringToHTML, readString } from "../../../lib/src/string_and_binary/convert.ts"; +import { decodeBinary, readString } from "../../../lib/src/string_and_binary/convert.ts"; import ObsidianLiveSyncPlugin from "../../../main.ts"; import { type DocumentID, @@ -145,22 +145,66 @@ export class DocumentHistoryModal extends Modal { return v; } + prepareContentView(usePreformatted = true) { + this.contentView.empty(); + this.contentView.toggleClass("op-pre", usePreformatted); + } + + appendTextDiff(diff: [number, string][]) { + for (const [operation, text] of diff) { + if (operation == DIFF_DELETE) { + this.contentView.createSpan({ text, cls: "history-deleted" }); + } else if (operation == DIFF_EQUAL) { + this.contentView.createSpan({ text, cls: "history-normal" }); + } else if (operation == DIFF_INSERT) { + this.contentView.createSpan({ text, cls: "history-added" }); + } + } + } + + appendImageDiff(baseSrc: string, overlaySrc?: string) { + const wrap = this.contentView.createDiv({ cls: "ls-imgdiff-wrap" }); + const overlay = wrap.createDiv({ cls: "overlay" }); + overlay.createEl("img", { cls: "img-base" }, (img) => { + img.src = baseSrc; + }); + if (overlaySrc) { + overlay.createEl("img", { cls: "img-overlay" }, (img) => { + img.src = overlaySrc; + }); + } + } + + appendDeletedNotice(usePreformatted = true) { + const notice = "(At this revision, the file has been deleted)"; + if (usePreformatted) { + this.contentView.appendText(`${notice}\n`); + } else { + this.contentView.createDiv({ text: notice }); + } + } + async showExactRev(rev: string) { const db = this.core.localDatabase; const w = await db.getDBEntry(this.file, { rev: rev }, false, false, true); this.currentText = ""; this.currentDeleted = false; + this.prepareContentView(); if (w === false) { this.currentDeleted = true; - this.info.innerHTML = ""; - this.contentView.innerHTML = `Could not read this revision
(${rev})`; + this.info.empty(); + this.contentView.appendText("Could not read this revision"); + this.contentView.createEl("br"); + this.contentView.appendText(`(${rev})`); } else { this.currentDoc = w; - this.info.innerHTML = `Modified:${new Date(w.mtime).toLocaleString()}`; - let result = undefined; + this.info.setText(`Modified:${new Date(w.mtime).toLocaleString()}`); const w1data = readDocument(w); this.currentDeleted = !!w.deleted; - // this.currentText = w1data; + if (typeof w1data == "string") { + this.currentText = w1data; + } + let rendered = false; if (this.showDiff) { const prevRevIdx = this.revs_info.length - 1 - ((this.range.value as any) / 1 - 1); if (prevRevIdx >= 0 && prevRevIdx < this.revs_info.length) { @@ -168,58 +212,55 @@ export class DocumentHistoryModal extends Modal { const w2 = await db.getDBEntry(this.file, { rev: oldRev }, false, false, true); if (w2 != false) { if (typeof w1data == "string") { - result = ""; - const dmp = new diff_match_patch(); - const w2data = readDocument(w2) as string; - const diff = dmp.diff_main(w2data, w1data); - dmp.diff_cleanupSemantic(diff); - for (const v of diff) { - const x1 = v[0]; - const x2 = v[1]; - if (x1 == DIFF_DELETE) { - result += "" + escapeStringToHTML(x2) + ""; - } else if (x1 == DIFF_EQUAL) { - result += "" + escapeStringToHTML(x2) + ""; - } else if (x1 == DIFF_INSERT) { - result += "" + escapeStringToHTML(x2) + ""; + const w2data = readDocument(w2); + if (typeof w2data == "string") { + const dmp = new diff_match_patch(); + const diff = dmp.diff_main(w2data, w1data); + dmp.diff_cleanupSemantic(diff); + if (this.currentDeleted) { + this.appendDeletedNotice(); } + this.appendTextDiff(diff); + rendered = true; } - result = result.replace(/\n/g, "
"); } else if (isImage(this.file)) { const src = this.generateBlobURL("base", w1data); const overlay = this.generateBlobURL( "overlay", readDocument(w2) as Uint8Array ); - result = `
-
- - -
-
`; - this.contentView.removeClass("op-pre"); + this.prepareContentView(false); + if (this.currentDeleted) { + this.appendDeletedNotice(false); + } + this.appendImageDiff(src, overlay); + rendered = true; } } } } - if (result == undefined) { + if (!rendered) { if (typeof w1data != "string") { if (isImage(this.file)) { const src = this.generateBlobURL("base", w1data); - result = `
-
- -
-
`; - this.contentView.removeClass("op-pre"); + this.prepareContentView(false); + if (this.currentDeleted) { + this.appendDeletedNotice(false); + } + this.appendImageDiff(src); + } else { + if (this.currentDeleted) { + this.appendDeletedNotice(); + } + this.contentView.appendText("Binary file"); } } else { - result = escapeStringToHTML(w1data); + if (this.currentDeleted) { + this.appendDeletedNotice(); + } + this.contentView.appendText(w1data); } } - if (result == undefined) result = typeof w1data == "string" ? escapeStringToHTML(w1data) : "Binary file"; - this.contentView.innerHTML = - (this.currentDeleted ? "(At this revision, the file has been deleted)\n" : "") + result; } // Reset diff navigation after content changes this.resetDiffNavigation(); diff --git a/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts b/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts index ad308e5..eec5332 100644 --- a/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts +++ b/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts @@ -1,7 +1,6 @@ import { App, Modal } from "../../../deps.ts"; import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT } from "diff-match-patch"; import { CANCELLED, LEAVE_TO_SUBSEQUENT, type diff_result } from "../../../lib/src/common/types.ts"; -import { escapeStringToHTML } from "../../../lib/src/string_and_binary/convert.ts"; import { delay } from "../../../lib/src/common/utils.ts"; import { eventHub } from "../../../common/events.ts"; import { globalSlipBoard } from "../../../lib/src/bureau/bureau.ts"; @@ -44,6 +43,25 @@ export class ConflictResolveModal extends Modal { // sendValue("close-resolve-conflict:" + this.filename, false); } + appendDiffFragment(container: HTMLDivElement, text: string, cls: string) { + const lines = text.split("\n"); + lines.forEach((line, index) => { + const span = container.createSpan({ cls }); + span.textContent = line; + if (index < lines.length - 1) { + container.createSpan({ cls: "ls-mark-cr" }); + container.createEl("br"); + } + }); + } + + appendVersionInfo(container: HTMLDivElement, cls: string, name: string, date: string) { + const line = container.createSpan({ cls }); + line.createSpan({ text: name, cls: "conflict-dev-name" }); + line.appendText(`: ${date}`); + container.createEl("br"); + } + override onOpen() { const { contentEl } = this; // Send cancel signal for the previous merge dialogue @@ -64,25 +82,21 @@ export class ConflictResolveModal extends Modal { const div = contentEl.createDiv(""); div.addClass("op-scrollable"); div.addClass("ls-dialog"); - let diff = ""; + let diffLength = 0; for (const v of this.result.diff) { const x1 = v[0]; const x2 = v[1]; + diffLength += x2.length; + if (diffLength > 100 * 1024) { + continue; + } if (x1 == DIFF_DELETE) { - diff += - "" + - escapeStringToHTML(x2).replace(/\n/g, "\n") + - ""; + this.appendDiffFragment(div, x2, "deleted"); + div.createEl("span", { text: x2, cls: "deleted normal conflict-dev-name" }); } else if (x1 == DIFF_EQUAL) { - diff += - "" + - escapeStringToHTML(x2).replace(/\n/g, "\n") + - ""; + this.appendDiffFragment(div, x2, "normal"); } else if (x1 == DIFF_INSERT) { - diff += - "" + - escapeStringToHTML(x2).replace(/\n/g, "\n") + - ""; + this.appendDiffFragment(div, x2, "added"); } } @@ -92,8 +106,8 @@ export class ConflictResolveModal extends Modal { new Date(this.result.left.mtime).toLocaleString() + (this.result.left.deleted ? " (Deleted)" : ""); const date2 = new Date(this.result.right.mtime).toLocaleString() + (this.result.right.deleted ? " (Deleted)" : ""); - div2.innerHTML = `${this.localName}: ${date1}
-${this.remoteName}: ${date2}
`; + this.appendVersionInfo(div2, "deleted", this.localName, date1); + this.appendVersionInfo(div2, "added", this.remoteName, date2); contentEl.createEl("button", { text: `Use ${this.localName}` }, (e) => e.addEventListener("click", () => this.sendResponse(this.result.right.rev)) ).style.marginRight = "4px"; @@ -108,11 +122,9 @@ export class ConflictResolveModal extends Modal { contentEl.createEl("button", { text: !this.pluginPickMode ? "Not now" : "Cancel" }, (e) => e.addEventListener("click", () => this.sendResponse(CANCELLED)) ).style.marginRight = "4px"; - diff = diff.replace(/\n/g, "
"); - if (diff.length > 100 * 1024) { + if (diffLength > 100 * 1024) { + div.empty(); div.innerText = "(Too large diff to display)"; - } else { - div.innerHTML = diff; } } diff --git a/src/modules/features/SettingDialogue/PaneChangeLog.ts b/src/modules/features/SettingDialogue/PaneChangeLog.ts index be0e0c3..3d8ceb9 100644 --- a/src/modules/features/SettingDialogue/PaneChangeLog.ts +++ b/src/modules/features/SettingDialogue/PaneChangeLog.ts @@ -43,10 +43,13 @@ export function paneChangeLog(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElem // tmpDiv.addClass("sls-header-button"); tmpDiv.addClass("op-warn-info"); - tmpDiv.innerHTML = `

${$msg("obsidianLiveSyncSettingTab.msgNewVersionNote")}

`; + tmpDiv.createEl("p", { text: $msg("obsidianLiveSyncSettingTab.msgNewVersionNote") }); + const readEverythingButton = tmpDiv.createEl("button", { + text: $msg("obsidianLiveSyncSettingTab.optionOkReadEverything"), + }); if (lastVersion > (this.editingSettings?.lastReadUpdates || 0)) { const informationButtonDiv = informationDivEl.appendChild(tmpDiv); - informationButtonDiv.querySelector("button")?.addEventListener("click", () => { + readEverythingButton.addEventListener("click", () => { fireAndForget(async () => { this.editingSettings.lastReadUpdates = lastVersion; await this.saveAllDirtySettings(); diff --git a/src/modules/features/SettingDialogue/PaneSetup.ts b/src/modules/features/SettingDialogue/PaneSetup.ts index de996bc..40cd88b 100644 --- a/src/modules/features/SettingDialogue/PaneSetup.ts +++ b/src/modules/features/SettingDialogue/PaneSetup.ts @@ -125,8 +125,13 @@ export function paneSetup( paneEl, "div", "", - (el) => - (el.innerHTML = `${$msg("obsidianLiveSyncSettingTab.linkOpenInBrowser")}`) + (el) => { + el.createEl("a", { text: $msg("obsidianLiveSyncSettingTab.linkOpenInBrowser") }, (anchor) => { + anchor.href = `https://github.com/${repo}/blob/main${topPath}`; + anchor.target = "_blank"; + anchor.rel = "noopener"; + }); + } ); const troubleShootEl = this.createEl(paneEl, "div", { text: "", diff --git a/src/modules/features/SettingDialogue/utilFixCouchDBSetting.ts b/src/modules/features/SettingDialogue/utilFixCouchDBSetting.ts index 041c923..d061643 100644 --- a/src/modules/features/SettingDialogue/utilFixCouchDBSetting.ts +++ b/src/modules/features/SettingDialogue/utilFixCouchDBSetting.ts @@ -13,7 +13,7 @@ export const checkConfig = async ( Logger($msg("obsidianLiveSyncSettingTab.logCheckingDbConfig"), LOG_LEVEL_INFO); let isSuccessful = true; const emptyDiv = createDiv(); - emptyDiv.innerHTML = ""; + emptyDiv.createSpan(); checkResultDiv?.replaceChildren(...[emptyDiv]); const addResult = (msg: string, classes?: string[]) => { const tmpDiv = createDiv(); @@ -21,7 +21,7 @@ export const checkConfig = async ( if (classes) { tmpDiv.addClasses(classes); } - tmpDiv.innerHTML = `${msg}`; + tmpDiv.textContent = msg; checkResultDiv?.appendChild(tmpDiv); }; try { @@ -47,9 +47,10 @@ export const checkConfig = async ( if (!checkResultDiv) return; const tmpDiv = createDiv(); tmpDiv.addClass("ob-btn-config-fix"); - tmpDiv.innerHTML = ``; + tmpDiv.createEl("label", { text: title }); + const fixButton = tmpDiv.createEl("button", { text: $msg("obsidianLiveSyncSettingTab.btnFix") }); const x = checkResultDiv.appendChild(tmpDiv); - x.querySelector("button")?.addEventListener("click", () => { + fixButton.addEventListener("click", () => { fireAndForget(async () => { Logger($msg("obsidianLiveSyncSettingTab.logCouchDbConfigSet", { title, key, value })); const res = await requestToCouchDBWithCredentials( From e8f8b680efeac9fd83fe16fb8c3e7cfa7ca045f7 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 03:58:08 +0100 Subject: [PATCH 15/73] prettify --- .../testdeno/test-e2e-two-vaults-couchdb.ts | 3 ++- .../DocumentHistory/DocumentHistoryModal.ts | 3 +-- .../features/SettingDialogue/PaneSetup.ts | 19 +++++++------------ 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts b/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts index 6f5244b..0c0151a 100644 --- a/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts +++ b/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts @@ -29,7 +29,8 @@ export async function runScenario(remoteType: RemoteType, encrypt: boolean): Pro const dbPrefix = remoteType === "COUCHDB" ? requireEnv("COUCHDB_DBNAME", "dbname") : ""; const dbname = remoteType === "COUCHDB" ? `${dbPrefix}-${dbSuffix}` : ""; - const minioEndpoint = remoteType === "MINIO" ? requireEnv("MINIO_ENDPOINT", "minioEndpoint").replace(/\/$/, "") : ""; + const minioEndpoint = + remoteType === "MINIO" ? requireEnv("MINIO_ENDPOINT", "minioEndpoint").replace(/\/$/, "") : ""; const minioAccessKey = remoteType === "MINIO" ? requireEnv("MINIO_ACCESS_KEY", "accessKey") : ""; const minioSecretKey = remoteType === "MINIO" ? requireEnv("MINIO_SECRET_KEY", "secretKey") : ""; const minioBucketBase = remoteType === "MINIO" ? requireEnv("MINIO_BUCKET_NAME", "bucketName") : ""; diff --git a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts index 40a5254..b9cf49a 100644 --- a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts +++ b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts @@ -286,8 +286,7 @@ export class DocumentHistoryModal extends Modal { if (direction === "next") { this.currentDiffIndex = (this.currentDiffIndex + 1) % diffElements.length; } else { - this.currentDiffIndex = - this.currentDiffIndex <= 0 ? diffElements.length - 1 : this.currentDiffIndex - 1; + this.currentDiffIndex = this.currentDiffIndex <= 0 ? diffElements.length - 1 : this.currentDiffIndex - 1; } const target = diffElements[this.currentDiffIndex]; diff --git a/src/modules/features/SettingDialogue/PaneSetup.ts b/src/modules/features/SettingDialogue/PaneSetup.ts index 40cd88b..4e365c9 100644 --- a/src/modules/features/SettingDialogue/PaneSetup.ts +++ b/src/modules/features/SettingDialogue/PaneSetup.ts @@ -121,18 +121,13 @@ export function paneSetup( const repo = "vrtmrz/obsidian-livesync"; const topPath = $msg("obsidianLiveSyncSettingTab.linkTroubleshooting"); const rawRepoURI = `https://raw.githubusercontent.com/${repo}/main`; - this.createEl( - paneEl, - "div", - "", - (el) => { - el.createEl("a", { text: $msg("obsidianLiveSyncSettingTab.linkOpenInBrowser") }, (anchor) => { - anchor.href = `https://github.com/${repo}/blob/main${topPath}`; - anchor.target = "_blank"; - anchor.rel = "noopener"; - }); - } - ); + this.createEl(paneEl, "div", "", (el) => { + el.createEl("a", { text: $msg("obsidianLiveSyncSettingTab.linkOpenInBrowser") }, (anchor) => { + anchor.href = `https://github.com/${repo}/blob/main${topPath}`; + anchor.target = "_blank"; + anchor.rel = "noopener"; + }); + }); const troubleShootEl = this.createEl(paneEl, "div", { text: "", cls: "sls-troubleshoot-preview", From 25a6fde212c87423f6f58598e83419008bb089d7 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 04:40:32 +0100 Subject: [PATCH 16/73] chore: Package modernise, update linter --- esbuild.config.mjs | 1 - eslint.config.mjs | 136 +- manifest.json | 2 +- package-lock.json | 1335 ++++++++++++++++- package.json | 11 +- .../SetupWizard/dialogs/ScanQRCode.svelte | 4 +- versions.json | 1 + vitest.config.p2p.ts | 8 +- vitest.config.ts | 9 +- 9 files changed, 1340 insertions(+), 167 deletions(-) diff --git a/esbuild.config.mjs b/esbuild.config.mjs index fe62f52..4350fab 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -2,7 +2,6 @@ import esbuild from "esbuild"; import process from "process"; -import builtins from "builtin-modules"; import sveltePlugin from "esbuild-svelte"; import { sveltePreprocess } from "svelte-preprocess"; import fs from "node:fs"; diff --git a/eslint.config.mjs b/eslint.config.mjs index 41cb90b..0efa595 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,103 +1,79 @@ -import typescriptEslint from "@typescript-eslint/eslint-plugin"; -import svelte from "eslint-plugin-svelte"; -import _import from "eslint-plugin-import"; -import { fixupPluginRules } from "@eslint/compat"; import tsParser from "@typescript-eslint/parser"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import js from "@eslint/js"; -import { FlatCompat } from "@eslint/eslintrc"; +import obsidianmd from "eslint-plugin-obsidianmd"; +import globals from "globals"; +import { defineConfig, globalIgnores } from "eslint/config"; +import * as sveltePlugin from "eslint-plugin-svelte"; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - allConfig: js.configs.all, -}); - -export default [ +export default defineConfig([ + globalIgnores([ + "**/node_modules/*", + "**/jest.config.js", + "src/lib/coverage", + "src/lib/browsertest", + "**/test.ts", + "**/tests.ts", + "**/**test.ts", + "**/**.test.ts", + "**/*.unit.spec.ts", + "**/esbuild.*.mjs", + "**/terser.*.mjs", + "**/node_modules", + "**/build", + "**/.eslintrc.js.bak", + "src/lib/src/patches/pouchdb-utils", + "**/esbuild.config.mjs", + "**/rollup.config.js", + "modules/octagonal-wheels/rollup.config.js", + "modules/octagonal-wheels/dist/**/*", + "src/lib/test", + "src/lib/_tools", + "src/lib/src/cli", + "**/main.js", + "src/apps/**/*", + ".prettierrc.*.mjs", + ".prettierrc.mjs", + "*.config.mjs", + ]), + ...sveltePlugin.configs["flat/base"], + ...obsidianmd.configs.recommended, { - ignores: [ - "**/node_modules/*", - "**/jest.config.js", - "src/lib/coverage", - "src/lib/browsertest", - "**/test.ts", - "**/tests.ts", - "**/**test.ts", - "**/**.test.ts", - "**/esbuild.*.mjs", - "**/terser.*.mjs", - "**/node_modules", - "**/build", - "**/.eslintrc.js.bak", - "src/lib/src/patches/pouchdb-utils", - "**/esbuild.config.mjs", - "**/rollup.config.js", - "modules/octagonal-wheels/rollup.config.js", - "modules/octagonal-wheels/dist/**/*", - "src/lib/test", - "src/lib/_tools", - "src/lib/src/cli", - "**/main.js", - "src/apps/**/*", - ".prettierrc.*.mjs", - ".prettierrc.mjs", - "*.config.mjs" - ], - }, - ...compat.extends( - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended" - ), - { - plugins: { - "@typescript-eslint": typescriptEslint, - svelte, - import: fixupPluginRules(_import), - }, - + files: ["**/*.ts"], languageOptions: { + globals: { ...globals.browser }, parser: tsParser, - ecmaVersion: 5, - sourceType: "module", - parserOptions: { - project: ["tsconfig.json"], + project: "./tsconfig.json", }, }, - rules: { "no-unused-vars": "off", - - "@typescript-eslint/no-unused-vars": [ - "error", - { - args: "none", - }, - ], - + "@typescript-eslint/no-unused-vars": ["error", { args: "none" }], "no-unused-labels": "off", "@typescript-eslint/ban-ts-comment": "off", "no-prototype-builtins": "off", "@typescript-eslint/no-empty-function": "off", "require-await": "error", + "obsidianmd/rule-custom-message": "off", // Temporary + "obsidianmd/ui/sentence-case": "off", // Temporary "@typescript-eslint/require-await": "warn", "@typescript-eslint/no-misused-promises": "warn", "@typescript-eslint/no-floating-promises": "warn", "no-async-promise-executor": "warn", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-unnecessary-type-assertion": "error", - - "no-constant-condition": [ - "error", - { - checkLoops: false, - }, - ], + "no-constant-condition": ["error", { checkLoops: false }], }, }, -]; - + { + files: ["**/*.svelte"], + languageOptions: { + parserOptions: { + parser: tsParser, + }, + }, + rules: { + "no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }], + "obsidianmd/no-plugin-as-component": "off", // Temporary + }, + }, +]); diff --git a/manifest.json b/manifest.json index cb9b9f1..f5ae865 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "id": "obsidian-livesync", "name": "Self-hosted LiveSync", "version": "0.25.60", - "minAppVersion": "0.9.12", + "minAppVersion": "1.2.3", "description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "author": "vorotamoroz", "authorUrl": "https://github.com/vrtmrz", diff --git a/package-lock.json b/package-lock.json index 4c2fba3..d480a28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "markdown-it": "^14.1.1", "micromatch": "^4.0.0", "minimatch": "^10.2.2", + "obsidian": "^1.12.3", "octagonal-wheels": "^0.1.45", "pouchdb-adapter-leveldb": "^9.0.0", "qrcode-generator": "^1.4.4", @@ -32,8 +33,6 @@ }, "devDependencies": { "@chialab/esbuild-plugin-worker": "^0.19.0", - "@eslint/compat": "^2.0.2", - "@eslint/eslintrc": "^3.3.4", "@eslint/js": "^9.39.3", "@sveltejs/vite-plugin-svelte": "^6.2.4", "@tsconfig/svelte": "^5.0.8", @@ -55,18 +54,15 @@ "@vitest/browser": "^4.1.1", "@vitest/browser-playwright": "^4.1.1", "@vitest/coverage-v8": "^4.1.1", - "builtin-modules": "5.0.0", - "dotenv": "^17.3.1", "dotenv-cli": "^11.0.0", "esbuild": "0.25.0", "esbuild-plugin-inline-worker": "^0.1.1", "esbuild-svelte": "^0.9.4", "eslint": "^9.39.3", - "eslint-plugin-import": "^2.32.0", + "eslint-plugin-obsidianmd": "^0.3.0", "eslint-plugin-svelte": "^3.15.0", "events": "^3.3.0", - "glob": "^13.0.6", - "obsidian": "^1.12.3", + "globals": "^14.0.0", "playwright": "^1.58.2", "postcss": "^8.5.6", "postcss-load-config": "^6.0.1", @@ -87,6 +83,7 @@ "svelte-check": "^4.4.3", "svelte-preprocess": "^6.0.3", "terser": "^5.39.0", + "tinyglobby": "^0.2.15", "transform-pouch": "^2.0.0", "tslib": "^2.8.1", "tsx": "^4.21.0", @@ -1340,7 +1337,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz", "integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==", - "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -1351,7 +1347,6 @@ "version": "6.38.6", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz", "integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==", - "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -1832,27 +1827,6 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/compat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.3.tgz", - "integrity": "sha512-SjIJhGigp8hmd1YGIBwh7Ovri7Kisl42GYFjrOyHhtfYGGoLW6teYi/5p8W50KSsawUPpuLOSmsq1bD0NGQLBw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.1.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "peerDependencies": { - "eslint": "^8.40 || 9 || 10" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, "node_modules/@eslint/config-array": { "version": "0.21.2", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", @@ -1925,19 +1899,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.0.tgz", - "integrity": "sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, "node_modules/@eslint/eslintrc": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", @@ -2006,6 +1967,35 @@ "url": "https://eslint.org/donate" } }, + "node_modules/@eslint/json": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/json/-/json-0.14.0.tgz", + "integrity": "sha512-rvR/EZtvUG3p9uqrSmcDJPYSH7atmWr0RnFWN6m917MAPx82+zQgPUmDu0whPFG6XTyM0vB/hR6c1Q63OaYtCQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "@eslint/plugin-kit": "^0.4.1", + "@humanwhocodes/momoa": "^3.3.10", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/json/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/object-schema": { "version": "2.1.7", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", @@ -2103,6 +2093,16 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@humanwhocodes/momoa": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/momoa/-/momoa-3.3.10.tgz", + "integrity": "sha512-KWiFQpSAqEIyrTXko3hFNLeQvSK8zXlJQzhhxsyVn58WFRYXST99b3Nqnu+ttOtjds2Pl2grUHGpe2NzhPynuQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/@humanwhocodes/retry": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", @@ -2379,9 +2379,61 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", +<<<<<<< HEAD "dev": true, "license": "MIT", "peer": true +======= + "license": "MIT" +>>>>>>> 5772811 (chore: Package modernise, update linter) + }, + "node_modules/@microsoft/eslint-plugin-sdl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@microsoft/eslint-plugin-sdl/-/eslint-plugin-sdl-1.1.0.tgz", + "integrity": "sha512-dxdNHOemLnBhfY3eByrujX9KyLigcNtW8sU+axzWv5nLGcsSBeKW2YYyTpfPo1hV8YPOmIGnfA4fZHyKVtWqBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-plugin-n": "17.10.3", + "eslint-plugin-react": "7.37.3", + "eslint-plugin-security": "1.4.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "eslint": "^9" + } + }, + "node_modules/@microsoft/eslint-plugin-sdl/node_modules/eslint-plugin-security": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-1.4.0.tgz", + "integrity": "sha512-xlS7P2PLMXeqfhyf3NpqbvbnW04kN8M9NtmhpR3XGyOvt/vNKS7XPXT5EDbwKW9vCjWH4PpfQvgD/+JgN0VJKA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-regex": "^1.1.0" + } + }, + "node_modules/@microsoft/eslint-plugin-sdl/node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12" + } + }, + "node_modules/@microsoft/eslint-plugin-sdl/node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ret": "~0.1.10" + } }, "node_modules/@minhducsun2002/leb128": { "version": "1.0.0", @@ -3005,6 +3057,19 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.2.tgz", + "integrity": "sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", @@ -4322,7 +4387,6 @@ "version": "5.60.8", "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", - "dev": true, "license": "MIT", "dependencies": { "@types/tern": "*" @@ -4359,11 +4423,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, "license": "MIT" }, "node_modules/@types/fs-extra": { @@ -4668,7 +4742,6 @@ "version": "0.23.9", "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "*" @@ -5771,6 +5844,27 @@ "node": ">=8" } }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.findlastindex": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", @@ -5831,6 +5925,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/arraybuffer.prototype.slice": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", @@ -6220,29 +6331,16 @@ "dev": true, "license": "MIT" }, - "node_modules/builtin-modules": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-5.0.0.tgz", - "integrity": "sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" }, "engines": { @@ -6659,9 +6757,13 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", +<<<<<<< HEAD "dev": true, "license": "MIT", "peer": true +======= + "license": "MIT" +>>>>>>> 5772811 (chore: Package modernise, update linter) }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -7219,6 +7321,16 @@ "dev": true, "license": "MIT" }, + "node_modules/empathic": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz", + "integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/encoding-down": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", @@ -7267,6 +7379,20 @@ "write-stream": "~0.4.3" } }, + "node_modules/enhanced-resolve": { + "version": "5.21.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.3.tgz", + "integrity": "sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -7292,9 +7418,9 @@ } }, "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", "dev": true, "license": "MIT", "dependencies": { @@ -7380,6 +7506,34 @@ "node": ">= 0.4" } }, + "node_modules/es-iterator-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz", + "integrity": "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -7621,6 +7775,22 @@ } } }, + "node_modules/eslint-compat-utils": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", + "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", @@ -7671,6 +7841,40 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-plugin-depend": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-depend/-/eslint-plugin-depend-1.3.1.tgz", + "integrity": "sha512-1uo2rFAr9vzNrCYdp7IBZRB54LiyVxfaIso0R6/QV3t6Dax6DTbW/EV2Hktf0f4UtmGHK8UyzJWI382pwW04jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "empathic": "^2.0.0", + "module-replacements": "^2.8.0", + "semver": "^7.6.3" + } + }, + "node_modules/eslint-plugin-es-x": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", + "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/ota-meshi", + "https://opencollective.com/eslint" + ], + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.11.0", + "eslint-compat-utils": "^0.5.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": ">=8" + } + }, "node_modules/eslint-plugin-import": { "version": "2.32.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", @@ -7756,6 +7960,354 @@ "semver": "bin/semver.js" } }, + "node_modules/eslint-plugin-json-schema-validator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-json-schema-validator/-/eslint-plugin-json-schema-validator-5.1.0.tgz", + "integrity": "sha512-ZmVyxRIjm58oqe2kTuy90PpmZPrrKvOjRPXKzq8WCgRgAkidCgm5X8domL2KSfadZ3QFAmifMgGTcVNhZ5ez2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.3.0", + "ajv": "^8.0.0", + "debug": "^4.3.1", + "eslint-compat-utils": "^0.5.0", + "json-schema-migrate": "^2.0.0", + "jsonc-eslint-parser": "^2.0.0", + "minimatch": "^8.0.0", + "synckit": "^0.9.0", + "toml-eslint-parser": "^0.9.0", + "tunnel-agent": "^0.6.0", + "yaml-eslint-parser": "^1.0.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-plugin-json-schema-validator/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint-plugin-json-schema-validator/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-json-schema-validator/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/eslint-plugin-json-schema-validator/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-json-schema-validator/node_modules/minimatch": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.7.tgz", + "integrity": "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint-plugin-n": { + "version": "17.10.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.10.3.tgz", + "integrity": "sha512-ySZBfKe49nQZWR1yFaA0v/GsH6Fgp8ah6XV0WDz6CN8WO0ek4McMzb7A2xnf4DCYV43frjCygvb9f/wx7UUxRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "enhanced-resolve": "^5.17.0", + "eslint-plugin-es-x": "^7.5.0", + "get-tsconfig": "^4.7.0", + "globals": "^15.8.0", + "ignore": "^5.2.4", + "minimatch": "^9.0.5", + "semver": "^7.5.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": ">=8.23.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-n/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-n/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint-plugin-no-unsanitized": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-4.1.5.tgz", + "integrity": "sha512-MSB4hXPVFQrI8weqzs6gzl7reP2k/qSjtCoL2vUMSDejIIq9YL1ZKvq5/ORBXab/PvfBBrWO2jWviYpL+4Ghfg==", + "dev": true, + "license": "MPL-2.0", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-plugin-obsidianmd": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-obsidianmd/-/eslint-plugin-obsidianmd-0.3.0.tgz", + "integrity": "sha512-QvGDI6B2nxJBrsZKGTg31da2A/fEJNlnwN+fRZkaoPIu1QL3fYXUdpP7ThyMdr/0iTYQxifb9lt2X9cpydQx1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint/config-helpers": "^0.4.2", + "@eslint/js": "^9.30.1", + "@eslint/json": "0.14.0", + "@microsoft/eslint-plugin-sdl": "^1.1.0", + "@types/eslint": "9.6.1", + "@types/node": "20.12.12", + "@typescript-eslint/types": "^8.33.1", + "@typescript-eslint/utils": "^8.33.1", + "eslint": ">=9.0.0", + "eslint-plugin-depend": "1.3.1", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-json-schema-validator": "5.1.0", + "eslint-plugin-no-unsanitized": "^4.1.5", + "eslint-plugin-security": "2.1.1", + "globals": "14.0.0", + "obsidian": "1.12.3", + "semver": "^7.7.4", + "typescript": "5.4.5", + "typescript-eslint": "^8.35.1" + }, + "bin": { + "eslint-plugin-obsidian": "dist/lib/index.js" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@eslint/js": "^9.30.1", + "@eslint/json": "0.14.0", + "eslint": ">=9.0.0", + "obsidian": "1.8.7", + "typescript-eslint": "^8.35.1" + } + }, + "node_modules/eslint-plugin-obsidianmd/node_modules/@types/node": { + "version": "20.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", + "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/eslint-plugin-obsidianmd/node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/eslint-plugin-obsidianmd/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.3.tgz", + "integrity": "sha512-DomWuTQPFYZwF/7c9W2fkKkStqZmBd3uugfqBYLdkZ3Hii23WzZuOLUskGxB8qkSKqftxEeGL1TB2kMhrce0jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-security": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-2.1.1.tgz", + "integrity": "sha512-7cspIGj7WTfR3EhaILzAPcfCo5R9FbeWvbgsPYWivSurTBKW88VQxtP3c4aWMG9Hz/GfJlJVdXEJ3c8LqS+u2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-regex": "^2.1.1" + } + }, "node_modules/eslint-plugin-svelte": { "version": "3.16.0", "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.16.0.tgz", @@ -8177,6 +8729,23 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fast-xml-builder": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.5.tgz", @@ -9682,6 +10251,24 @@ "node": ">=8" } }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -9748,6 +10335,40 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-migrate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/json-schema-migrate/-/json-schema-migrate-2.0.0.tgz", + "integrity": "sha512-r38SVTtojDRp4eD6WsCqiE0eNDt4v1WalBXb9cyZYw9ai5cGtBwzRNWjHzJl38w6TxFkXAIA7h+fyX3tnrAFhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + } + }, + "node_modules/json-schema-migrate/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/json-schema-migrate/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -9775,6 +10396,43 @@ "json5": "lib/cli.js" } }, + "node_modules/jsonc-eslint-parser": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.2.tgz", + "integrity": "sha512-1e4qoRgnn448pRuMvKGsFFymUCquZV0mpGgOyIKNgD3JVDTsVJyRBGH/Fm0tBb8WsWGgmB1mDe6/yJMQM37DUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.5.0", + "eslint-visitor-keys": "^3.0.0", + "espree": "^9.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, + "node_modules/jsonc-eslint-parser/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -9785,6 +10443,22 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -10279,6 +10953,26 @@ "dev": true, "license": "MIT" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loose-envify/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "11.2.7", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", @@ -10501,11 +11195,17 @@ "node": ">=18.0.0" } }, + "node_modules/module-replacements": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/module-replacements/-/module-replacements-2.11.0.tgz", + "integrity": "sha512-j5sNQm3VCpQQ7nTqGeOZtoJtV3uKERgCBm9QRhmGRiXiqkf7iRFOkfxdJRZWLkqYY8PNf4cDQF/WfXUYLENrRA==", + "dev": true, + "license": "MIT" + }, "node_modules/moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "dev": true, "license": "MIT", "engines": { "node": "*" @@ -10598,6 +11298,35 @@ "node": ">= 0.4.0" } }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/node-fetch": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", @@ -10659,6 +11388,16 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -10703,6 +11442,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/object.fromentries": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", @@ -10760,7 +11515,6 @@ "version": "1.12.3", "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.12.3.tgz", "integrity": "sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw==", - "dev": true, "license": "MIT", "dependencies": { "@types/codemirror": "5.60.8", @@ -11747,6 +12501,18 @@ "node": ">=0.4.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/proxy-agent": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", @@ -11888,6 +12654,13 @@ ], "license": "MIT" }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -11984,6 +12757,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "dev": true, + "license": "MIT", + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -12015,6 +12798,16 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -12265,6 +13058,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", + "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "regexp-tree": "~0.1.1" + } + }, "node_modules/safe-regex-test": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", @@ -12819,6 +13622,45 @@ "node": ">=8" } }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", @@ -12957,9 +13799,13 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", +<<<<<<< HEAD "dev": true, "license": "MIT", "peer": true +======= + "license": "MIT" +>>>>>>> 5772811 (chore: Package modernise, update linter) }, "node_modules/sublevel-pouchdb": { "version": "9.0.0", @@ -13190,6 +14036,37 @@ } } }, + "node_modules/synckit": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.3.tgz", + "integrity": "sha512-JJoOEKTfL1urb1mDoEblhD9NhEbWmq9jHEMEnxoC4ujUaZ4itA8vKgwkFAyNClgxplLi9tsUKX+EduK0p/l7sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/tar-stream": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", @@ -13367,6 +14244,22 @@ "node": ">=8.0" } }, + "node_modules/toml-eslint-parser": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/toml-eslint-parser/-/toml-eslint-parser-0.9.3.tgz", + "integrity": "sha512-moYoCvkNUAPCxSW9jmHmRElhm4tVJpHL8ItC/+uYD0EpPSFXbck7yREz9tNdJVTSpHVod8+HoipcpbQ0oE6gsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, "node_modules/totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", @@ -13969,6 +14862,19 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "license": "0BSD" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", @@ -14093,6 +14999,274 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.3.tgz", + "integrity": "sha512-KgusgyDgG4LI8Ih/sWaCtZ06tckLAS5CvT5A4D1Q7bYVoAAyzwiZvE4BmwDHkhRVkvhRBepKeASoFzQetha7Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.59.3", + "@typescript-eslint/parser": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/utils": "8.59.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.3.tgz", + "integrity": "sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/type-utils": "8.59.3", + "@typescript-eslint/utils": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.59.3", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.3.tgz", + "integrity": "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/project-service": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.3.tgz", + "integrity": "sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.59.3", + "@typescript-eslint/types": "^8.59.3", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.3.tgz", + "integrity": "sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.3.tgz", + "integrity": "sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/type-utils": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.3.tgz", + "integrity": "sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/utils": "8.59.3", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.3.tgz", + "integrity": "sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.3.tgz", + "integrity": "sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.59.3", + "@typescript-eslint/tsconfig-utils": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.3.tgz", + "integrity": "sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.3.tgz", + "integrity": "sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.3", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/typescript-eslint/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", @@ -15009,9 +16183,13 @@ "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", +<<<<<<< HEAD "dev": true, "license": "MIT", "peer": true +======= + "license": "MIT" +>>>>>>> 5772811 (chore: Package modernise, update linter) }, "node_modules/wait-port": { "version": "1.1.0", @@ -15673,6 +16851,23 @@ "url": "https://github.com/sponsors/eemeli" } }, + "node_modules/yaml-eslint-parser": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/yaml-eslint-parser/-/yaml-eslint-parser-1.3.2.tgz", + "integrity": "sha512-odxVsHAkZYYglR30aPYRY4nUGJnoJ2y1ww2HDvZALo0BDETv9kWbi16J52eHs+PWRNmF4ub6nZqfVOeesOvntg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.0.0", + "yaml": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index bc96572..a95177f 100644 --- a/package.json +++ b/package.json @@ -61,8 +61,6 @@ "license": "MIT", "devDependencies": { "@chialab/esbuild-plugin-worker": "^0.19.0", - "@eslint/compat": "^2.0.2", - "@eslint/eslintrc": "^3.3.4", "@eslint/js": "^9.39.3", "@sveltejs/vite-plugin-svelte": "^6.2.4", "@tsconfig/svelte": "^5.0.8", @@ -84,18 +82,15 @@ "@vitest/browser": "^4.1.1", "@vitest/browser-playwright": "^4.1.1", "@vitest/coverage-v8": "^4.1.1", - "builtin-modules": "5.0.0", - "dotenv": "^17.3.1", "dotenv-cli": "^11.0.0", "esbuild": "0.25.0", "esbuild-plugin-inline-worker": "^0.1.1", "esbuild-svelte": "^0.9.4", "eslint": "^9.39.3", - "eslint-plugin-import": "^2.32.0", + "eslint-plugin-obsidianmd": "^0.3.0", "eslint-plugin-svelte": "^3.15.0", "events": "^3.3.0", - "glob": "^13.0.6", - "obsidian": "^1.12.3", + "globals": "^14.0.0", "playwright": "^1.58.2", "postcss": "^8.5.6", "postcss-load-config": "^6.0.1", @@ -116,6 +111,7 @@ "svelte-check": "^4.4.3", "svelte-preprocess": "^6.0.3", "terser": "^5.39.0", + "tinyglobby": "^0.2.15", "transform-pouch": "^2.0.0", "tslib": "^2.8.1", "tsx": "^4.21.0", @@ -136,6 +132,7 @@ "@trystero-p2p/nostr": "^0.23.0", "chokidar": "^4.0.0", "commander": "^14.0.3", + "obsidian": "^1.12.3", "diff-match-patch": "^1.0.5", "fflate": "^0.8.2", "idb": "^8.0.3", diff --git a/src/modules/features/SetupWizard/dialogs/ScanQRCode.svelte b/src/modules/features/SetupWizard/dialogs/ScanQRCode.svelte index 58ad125..57c0621 100644 --- a/src/modules/features/SetupWizard/dialogs/ScanQRCode.svelte +++ b/src/modules/features/SetupWizard/dialogs/ScanQRCode.svelte @@ -4,10 +4,10 @@ import Decision from "@/lib/src/UI/components/Decision.svelte"; import Instruction from "@/lib/src/UI/components/Instruction.svelte"; import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte"; - const TYPE_CLOSE = "close"; + const TYPE_CLOSE = "close"; type ResultType = typeof TYPE_CLOSE; type Props = { - setResult: (result: ResultType) => void; + setResult: (_result: ResultType) => void; }; const { setResult }: Props = $props(); diff --git a/versions.json b/versions.json index 3366a32..ae232e6 100644 --- a/versions.json +++ b/versions.json @@ -1,4 +1,5 @@ { + "0.25.60": "1.2.3", "1.0.1": "0.9.12", "1.0.0": "0.9.7" } diff --git a/vitest.config.p2p.ts b/vitest.config.p2p.ts index a968e9f..56f3244 100644 --- a/vitest.config.p2p.ts +++ b/vitest.config.p2p.ts @@ -2,7 +2,8 @@ import { defineConfig, mergeConfig } from "vitest/config"; import { playwright } from "@vitest/browser-playwright"; import viteConfig from "./vitest.config.common"; import path from "path"; -import dotenv from "dotenv"; +import { existsSync, readFileSync } from "node:fs"; +import { parseEnv } from "node:util"; import { grantClipboardPermissions, writeHandoffFile, readHandoffFile } from "./test/lib/commands"; // P2P test environment variables @@ -22,8 +23,9 @@ import { grantClipboardPermissions, writeHandoffFile, readHandoffFile } from "./ // General test options (also read from env): // ENABLE_DEBUGGER - Set to "true" to attach a debugger and pause before tests // ENABLE_UI - Set to "true" to open a visible browser window during tests -const defEnv = dotenv.config({ path: ".env" }).parsed; -const testEnv = dotenv.config({ path: ".test.env" }).parsed; +const loadEnvFile = (path: string) => (existsSync(path) ? parseEnv(readFileSync(path, "utf-8")) : undefined); +const defEnv = loadEnvFile(".env"); +const testEnv = loadEnvFile(".test.env"); // Merge: dotenv files < process.env (so shell-injected vars like P2P_TEST_* take precedence) const p2pEnv: Record = {}; if (process.env.P2P_TEST_ROOM_ID) p2pEnv.P2P_TEST_ROOM_ID = process.env.P2P_TEST_ROOM_ID; diff --git a/vitest.config.ts b/vitest.config.ts index 8818ee8..d5e74d4 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,10 +2,13 @@ import { defineConfig, mergeConfig } from "vitest/config"; import { playwright } from "@vitest/browser-playwright"; import viteConfig from "./vitest.config.common"; import path from "path"; -import dotenv from "dotenv"; +import { existsSync, readFileSync } from "node:fs"; +import { parseEnv } from "node:util"; import { grantClipboardPermissions, openWebPeer, closeWebPeer, acceptWebPeer } from "./test/lib/commands"; -const defEnv = dotenv.config({ path: ".env" }).parsed; -const testEnv = dotenv.config({ path: ".test.env" }).parsed; + +const loadEnvFile = (path: string) => (existsSync(path) ? parseEnv(readFileSync(path, "utf-8")) : undefined); +const defEnv = loadEnvFile(".env"); +const testEnv = loadEnvFile(".test.env"); const env = Object.assign({}, defEnv, testEnv); const debuggerEnabled = env?.ENABLE_DEBUGGER === "true"; const enableUI = env?.ENABLE_UI === "true"; From d2eb6ecbafe8555c45f28078097fbfd2562ee1ec Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 08:02:50 +0100 Subject: [PATCH 17/73] Update for review once --- manifest.json | 2 +- src/serviceFeatures/onLayoutReady/enablei18n.ts | 13 ++++++++++++- src/serviceFeatures/useP2PReplicatorUI.ts | 3 ++- .../FileSystemAdapters/ObsidianVaultAdapter.ts | 12 +++++++++++- versions.json | 2 +- 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/manifest.json b/manifest.json index f5ae865..6f3ee49 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "id": "obsidian-livesync", "name": "Self-hosted LiveSync", "version": "0.25.60", - "minAppVersion": "1.2.3", + "minAppVersion": "1.7.2", "description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "author": "vorotamoroz", "authorUrl": "https://github.com/vrtmrz", diff --git a/src/serviceFeatures/onLayoutReady/enablei18n.ts b/src/serviceFeatures/onLayoutReady/enablei18n.ts index 4d79915..ba74491 100644 --- a/src/serviceFeatures/onLayoutReady/enablei18n.ts +++ b/src/serviceFeatures/onLayoutReady/enablei18n.ts @@ -3,11 +3,22 @@ import { createServiceFeature } from "@lib/interfaces/ServiceModule"; import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "@lib/common/rosetta"; import { $msg, setLang } from "@lib/common/i18n"; +function tryGetLanguage() { + try { + // Note: 1.8.7+ is required. but it is 18, Feb., 2025. we want to fallback on earlier versions, so we catch the error here. + // eslint-disable-next-line obsidianmd/no-unsupported-api + return getLanguage(); + } catch (e) { + console.error("Failed to get Obsidian language, defaulting to 'def'", e); + return "en"; + } +} + export const enableI18nFeature = createServiceFeature(async ({ services: { setting, API } }) => { let isChanged = false; const settings = setting.currentSettings(); if (settings.displayLanguage == "") { - const obsidianLanguage = getLanguage(); + const obsidianLanguage = tryGetLanguage(); if ( SUPPORTED_I18N_LANGS.indexOf(obsidianLanguage) !== -1 && // Check if the language is supported obsidianLanguage != settings.displayLanguage // Check if the language is different from the current setting diff --git a/src/serviceFeatures/useP2PReplicatorUI.ts b/src/serviceFeatures/useP2PReplicatorUI.ts index bfe042d..a2ea2d1 100644 --- a/src/serviceFeatures/useP2PReplicatorUI.ts +++ b/src/serviceFeatures/useP2PReplicatorUI.ts @@ -5,6 +5,7 @@ import { type UseP2PReplicatorResult } from "@/lib/src/replication/trystero/UseP import { P2PLogCollector } from "@/lib/src/replication/trystero/P2PLogCollector"; import { P2PReplicatorPaneView, VIEW_TYPE_P2P } from "@/features/P2PSync/P2PReplicator/P2PReplicatorPaneView"; import type { LiveSyncCore } from "@/main"; +import type { WorkspaceLeaf } from "@/deps"; /** * ServiceFeature: P2P Replicator lifecycle management. @@ -43,7 +44,7 @@ export function useP2PReplicatorUI( // Register view, commands and ribbon if a view factory is provided const viewType = VIEW_TYPE_P2P; - const factory = (leaf: any) => { + const factory = (leaf: WorkspaceLeaf) => { return new P2PReplicatorPaneView(leaf, core, { replicator: getReplicator(), p2pLogCollector, diff --git a/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts b/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts index a7d996c..78d3559 100644 --- a/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts +++ b/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts @@ -7,7 +7,7 @@ import type { TFile, App, TFolder } from "obsidian"; * Vault adapter implementation for Obsidian */ export class ObsidianVaultAdapter implements IVaultAdapter { - constructor(private app: App) {} + constructor(private app: App) { } async read(file: TFile): Promise { return await this.app.vault.read(file); @@ -38,10 +38,20 @@ export class ObsidianVaultAdapter implements IVaultAdapter { } async delete(file: TFile | TFolder, force = false): Promise { + // if ("trashFile" in this.app.fileManager) { + // // eslint-disable-next-line obsidianmd/no-unsupported-api + // return await this.app.fileManager.trashFile(file); + // } + //TODO: need fix return await this.app.vault.delete(file, force); } async trash(file: TFile | TFolder, force = false): Promise { + // if ("trashFile" in this.app.fileManager) { + // // eslint-disable-next-line obsidianmd/no-unsupported-api + // return await this.app.fileManager.trashFile(file); + // } + //TODO: need fix return await this.app.vault.trash(file, force); } diff --git a/versions.json b/versions.json index ae232e6..c5ab81e 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "0.25.60": "1.2.3", + "0.25.60": "1.7.2", "1.0.1": "0.9.12", "1.0.0": "0.9.7" } From d05c76da3622a5abc2601b39f7f70b179b559e6b Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 10:15:45 +0100 Subject: [PATCH 18/73] Update eslint config to ignore file, fix some type error on LiveSyncBaseCore --- eslint.config.mjs | 4 ++++ src/LiveSyncBaseCore.ts | 18 ++++++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 0efa595..35b0d25 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -33,6 +33,10 @@ export default defineConfig([ ".prettierrc.*.mjs", ".prettierrc.mjs", "*.config.mjs", + "src/apps/**/*", + "src/lib/src/services/implements/browser/**", + "src/lib/src/services/implements/headless/**", + "src/lib/src/API", ]), ...sveltePlugin.configs["flat/base"], ...obsidianmd.configs.recommended, diff --git a/src/LiveSyncBaseCore.ts b/src/LiveSyncBaseCore.ts index f9f0427..a3945fd 100644 --- a/src/LiveSyncBaseCore.ts +++ b/src/LiveSyncBaseCore.ts @@ -1,4 +1,5 @@ import { LOG_LEVEL_INFO } from "octagonal-wheels/common/logger"; +import type PouchDB from "pouchdb-core"; import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase"; import type { HasSettings, ObsidianLiveSyncSettings, EntryDoc } from "./lib/src/common/types"; import { __$checkInstanceBinding } from "./lib/src/dev/checks"; @@ -34,12 +35,11 @@ export class LiveSyncBaseCore< TCommands extends IMinimumLiveSyncCommands = IMinimumLiveSyncCommands, > implements - LiveSyncLocalDBEnv, - LiveSyncReplicatorEnv, - LiveSyncJournalReplicatorEnv, - LiveSyncCouchDBReplicatorEnv, - HasSettings -{ + LiveSyncLocalDBEnv, + LiveSyncReplicatorEnv, + LiveSyncJournalReplicatorEnv, + LiveSyncCouchDBReplicatorEnv, + HasSettings { addOns = [] as TCommands[]; /** @@ -123,7 +123,7 @@ export class LiveSyncBaseCore< for (const module of this.modules) { if (module.constructor === constructor) return module as T; } - throw new Error(`Module ${constructor} not found or not loaded.`); + throw new Error(`Module ${constructor.name} not found or not loaded.`); } /** @@ -160,8 +160,10 @@ export class LiveSyncBaseCore< module.onBindFunction(this, this.services); __$checkInstanceBinding(module); // Check if all functions are properly bound, and log warnings if not. } else { + // module should not be never. + const moduleName = (module as unknown)?.constructor?.name ?? "unknown"; this.services.API.addLog( - `Module ${(module as any)?.constructor?.name ?? "unknown"} does not have onBindFunction, skipping binding.`, + `Module ${moduleName} does not have onBindFunction, skipping binding.`, LOG_LEVEL_INFO ); } From 538130aa913376d46dfa66103d581df33e4d7ce1 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 11:36:01 +0100 Subject: [PATCH 19/73] Fix package-lock --- package-lock.json | 1244 +-------------------------------------------- 1 file changed, 20 insertions(+), 1224 deletions(-) diff --git a/package-lock.json b/package-lock.json index 856e294..f15ed50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,11 +60,9 @@ "esbuild-svelte": "^0.9.4", "eslint": "^9.39.3", "eslint-plugin-obsidianmd": "^0.3.0", - "eslint-plugin-obsidianmd": "^0.3.0", "eslint-plugin-svelte": "^3.15.0", "events": "^3.3.0", "globals": "^14.0.0", - "globals": "^14.0.0", "playwright": "^1.58.2", "postcss": "^8.5.6", "postcss-load-config": "^6.0.1", @@ -86,7 +84,6 @@ "svelte-preprocess": "^6.0.3", "terser": "^5.39.0", "tinyglobby": "^0.2.15", - "tinyglobby": "^0.2.15", "transform-pouch": "^2.0.0", "tslib": "^2.8.1", "tsx": "^4.21.0", @@ -987,6 +984,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1999,35 +1997,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/json": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@eslint/json/-/json-0.14.0.tgz", - "integrity": "sha512-rvR/EZtvUG3p9uqrSmcDJPYSH7atmWr0RnFWN6m917MAPx82+zQgPUmDu0whPFG6XTyM0vB/hR6c1Q63OaYtCQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "@eslint/plugin-kit": "^0.4.1", - "@humanwhocodes/momoa": "^3.3.10", - "natural-compare": "^1.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/json/node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@eslint/object-schema": { "version": "2.1.7", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", @@ -2135,16 +2104,6 @@ "node": ">=18" } }, - "node_modules/@humanwhocodes/momoa": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/momoa/-/momoa-3.3.10.tgz", - "integrity": "sha512-KWiFQpSAqEIyrTXko3hFNLeQvSK8zXlJQzhhxsyVn58WFRYXST99b3Nqnu+ttOtjds2Pl2grUHGpe2NzhPynuQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18" - } - }, "node_modules/@humanwhocodes/retry": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", @@ -2421,13 +2380,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", -<<<<<<< HEAD - "dev": true, - "license": "MIT", - "peer": true -======= "license": "MIT" ->>>>>>> 5772811 (chore: Package modernise, update linter) }, "node_modules/@microsoft/eslint-plugin-sdl": { "version": "1.1.0", @@ -3112,19 +3065,6 @@ "url": "https://opencollective.com/unts" } }, - "node_modules/@pkgr/core": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.2.tgz", - "integrity": "sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, "node_modules/@polka/url": { "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", @@ -4347,6 +4287,7 @@ "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "deepmerge": "^4.3.1", @@ -4489,17 +4430,6 @@ "@types/json-schema": "*" } }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -4896,6 +4826,7 @@ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", @@ -5100,6 +5031,7 @@ "integrity": "sha512-gjjrFC4+kPVK/fN9URDJWrssU5Gqh8Az8pKG/NSfQ2V+ky8b/y1BgBg0Ug13+hOGp5pzInonmGRPn7vOgSLgzA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@blazediff/core": "1.9.1", "@vitest/mocker": "4.1.1", @@ -5123,6 +5055,7 @@ "integrity": "sha512-dtVSBZZha2k/7P7EAXXrEAoxuIKl8Yv9f2Dk4GN/DGfmhf4DQvkvu+57okR2wq/gan1xppKjL/aBxK/kbYrbGw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/browser": "4.1.1", "@vitest/mocker": "4.1.1", @@ -5564,6 +5497,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5931,27 +5865,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.findlast": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", - "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/array.prototype.findlastindex": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", @@ -6029,23 +5942,6 @@ "node": ">= 0.4" } }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", - "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", - "es-errors": "^1.3.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/arraybuffer.prototype.slice": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", @@ -6381,6 +6277,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -6436,18 +6333,12 @@ "license": "MIT" }, "node_modules/call-bind": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", - "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "version": "1.0.9", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "get-intrinsic": "^1.3.0", "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", @@ -6867,13 +6758,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", -<<<<<<< HEAD - "dev": true, - "license": "MIT", - "peer": true -======= "license": "MIT" ->>>>>>> 5772811 (chore: Package modernise, update linter) }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -7441,16 +7326,6 @@ "node": ">=14" } }, - "node_modules/empathic": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz", - "integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, "node_modules/encoding-down": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", @@ -7513,20 +7388,6 @@ "node": ">=10.13.0" } }, - "node_modules/enhanced-resolve": { - "version": "5.21.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.3.tgz", - "integrity": "sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -7552,9 +7413,6 @@ } }, "node_modules/es-abstract": { - "version": "1.24.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", - "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", "version": "1.24.2", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", @@ -7671,34 +7529,6 @@ "node": ">= 0.4" } }, - "node_modules/es-iterator-helpers": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz", - "integrity": "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.2", - "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.1.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.3.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "iterator.prototype": "^1.1.5", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -7773,6 +7603,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -7886,6 +7717,7 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7956,22 +7788,6 @@ "eslint": ">=6.0.0" } }, - "node_modules/eslint-compat-utils": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", - "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "eslint": ">=6.0.0" - } - }, "node_modules/eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", @@ -8056,40 +7872,6 @@ "eslint": ">=8" } }, - "node_modules/eslint-plugin-depend": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-depend/-/eslint-plugin-depend-1.3.1.tgz", - "integrity": "sha512-1uo2rFAr9vzNrCYdp7IBZRB54LiyVxfaIso0R6/QV3t6Dax6DTbW/EV2Hktf0f4UtmGHK8UyzJWI382pwW04jw==", - "dev": true, - "license": "MIT", - "dependencies": { - "empathic": "^2.0.0", - "module-replacements": "^2.8.0", - "semver": "^7.6.3" - } - }, - "node_modules/eslint-plugin-es-x": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", - "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/ota-meshi", - "https://opencollective.com/eslint" - ], - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.1.2", - "@eslint-community/regexpp": "^4.11.0", - "eslint-compat-utils": "^0.5.1" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": ">=8" - } - }, "node_modules/eslint-plugin-import": { "version": "2.32.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", @@ -8523,354 +8305,6 @@ "safe-regex": "^2.1.1" } }, - "node_modules/eslint-plugin-json-schema-validator": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-json-schema-validator/-/eslint-plugin-json-schema-validator-5.1.0.tgz", - "integrity": "sha512-ZmVyxRIjm58oqe2kTuy90PpmZPrrKvOjRPXKzq8WCgRgAkidCgm5X8domL2KSfadZ3QFAmifMgGTcVNhZ5ez2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.3.0", - "ajv": "^8.0.0", - "debug": "^4.3.1", - "eslint-compat-utils": "^0.5.0", - "json-schema-migrate": "^2.0.0", - "jsonc-eslint-parser": "^2.0.0", - "minimatch": "^8.0.0", - "synckit": "^0.9.0", - "toml-eslint-parser": "^0.9.0", - "tunnel-agent": "^0.6.0", - "yaml-eslint-parser": "^1.0.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - }, - "peerDependencies": { - "eslint": ">=6.0.0" - } - }, - "node_modules/eslint-plugin-json-schema-validator/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint-plugin-json-schema-validator/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint-plugin-json-schema-validator/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/eslint-plugin-json-schema-validator/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint-plugin-json-schema-validator/node_modules/minimatch": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.7.tgz", - "integrity": "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/eslint-plugin-n": { - "version": "17.10.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.10.3.tgz", - "integrity": "sha512-ySZBfKe49nQZWR1yFaA0v/GsH6Fgp8ah6XV0WDz6CN8WO0ek4McMzb7A2xnf4DCYV43frjCygvb9f/wx7UUxRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "enhanced-resolve": "^5.17.0", - "eslint-plugin-es-x": "^7.5.0", - "get-tsconfig": "^4.7.0", - "globals": "^15.8.0", - "ignore": "^5.2.4", - "minimatch": "^9.0.5", - "semver": "^7.5.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": ">=8.23.0" - } - }, - "node_modules/eslint-plugin-n/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint-plugin-n/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/eslint-plugin-n/node_modules/globals": { - "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-plugin-n/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/eslint-plugin-no-unsanitized": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-4.1.5.tgz", - "integrity": "sha512-MSB4hXPVFQrI8weqzs6gzl7reP2k/qSjtCoL2vUMSDejIIq9YL1ZKvq5/ORBXab/PvfBBrWO2jWviYpL+4Ghfg==", - "dev": true, - "license": "MPL-2.0", - "peerDependencies": { - "eslint": "^9 || ^10" - } - }, - "node_modules/eslint-plugin-obsidianmd": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-obsidianmd/-/eslint-plugin-obsidianmd-0.3.0.tgz", - "integrity": "sha512-QvGDI6B2nxJBrsZKGTg31da2A/fEJNlnwN+fRZkaoPIu1QL3fYXUdpP7ThyMdr/0iTYQxifb9lt2X9cpydQx1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint/config-helpers": "^0.4.2", - "@eslint/js": "^9.30.1", - "@eslint/json": "0.14.0", - "@microsoft/eslint-plugin-sdl": "^1.1.0", - "@types/eslint": "9.6.1", - "@types/node": "20.12.12", - "@typescript-eslint/types": "^8.33.1", - "@typescript-eslint/utils": "^8.33.1", - "eslint": ">=9.0.0", - "eslint-plugin-depend": "1.3.1", - "eslint-plugin-import": "^2.31.0", - "eslint-plugin-json-schema-validator": "5.1.0", - "eslint-plugin-no-unsanitized": "^4.1.5", - "eslint-plugin-security": "2.1.1", - "globals": "14.0.0", - "obsidian": "1.12.3", - "semver": "^7.7.4", - "typescript": "5.4.5", - "typescript-eslint": "^8.35.1" - }, - "bin": { - "eslint-plugin-obsidian": "dist/lib/index.js" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@eslint/js": "^9.30.1", - "@eslint/json": "0.14.0", - "eslint": ">=9.0.0", - "obsidian": "1.8.7", - "typescript-eslint": "^8.35.1" - } - }, - "node_modules/eslint-plugin-obsidianmd/node_modules/@types/node": { - "version": "20.12.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", - "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/eslint-plugin-obsidianmd/node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/eslint-plugin-obsidianmd/node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint-plugin-react": { - "version": "7.37.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.3.tgz", - "integrity": "sha512-DomWuTQPFYZwF/7c9W2fkKkStqZmBd3uugfqBYLdkZ3Hii23WzZuOLUskGxB8qkSKqftxEeGL1TB2kMhrce0jA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.8", - "array.prototype.findlast": "^1.2.5", - "array.prototype.flatmap": "^1.3.3", - "array.prototype.tosorted": "^1.1.4", - "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.2.1", - "estraverse": "^5.3.0", - "hasown": "^2.0.2", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.8", - "object.fromentries": "^2.0.8", - "object.values": "^1.2.1", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.5", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.12", - "string.prototype.repeat": "^1.0.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" - } - }, - "node_modules/eslint-plugin-react/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint-plugin-react/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint-plugin-react/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.6", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", - "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "is-core-module": "^2.16.1", - "node-exports-info": "^1.6.0", - "object-keys": "^1.1.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/eslint-plugin-react/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-security": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-2.1.1.tgz", - "integrity": "sha512-7cspIGj7WTfR3EhaILzAPcfCo5R9FbeWvbgsPYWivSurTBKW88VQxtP3c4aWMG9Hz/GfJlJVdXEJ3c8LqS+u2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "safe-regex": "^2.1.1" - } - }, "node_modules/eslint-plugin-svelte": { "version": "3.16.0", "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.16.0.tgz", @@ -9309,23 +8743,6 @@ ], "license": "BSD-3-Clause" }, - "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/fast-xml-builder": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.5.tgz", @@ -10849,24 +10266,6 @@ "node": ">= 0.4" } }, - "node_modules/iterator.prototype": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", - "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "get-proto": "^1.0.0", - "has-symbols": "^1.1.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -10889,6 +10288,7 @@ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -10967,40 +10367,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-schema-migrate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/json-schema-migrate/-/json-schema-migrate-2.0.0.tgz", - "integrity": "sha512-r38SVTtojDRp4eD6WsCqiE0eNDt4v1WalBXb9cyZYw9ai5cGtBwzRNWjHzJl38w6TxFkXAIA7h+fyX3tnrAFhQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - } - }, - "node_modules/json-schema-migrate/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/json-schema-migrate/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -11065,43 +10431,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/jsonc-eslint-parser": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.2.tgz", - "integrity": "sha512-1e4qoRgnn448pRuMvKGsFFymUCquZV0mpGgOyIKNgD3JVDTsVJyRBGH/Fm0tBb8WsWGgmB1mDe6/yJMQM37DUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.5.0", - "eslint-visitor-keys": "^3.0.0", - "espree": "^9.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - } - }, - "node_modules/jsonc-eslint-parser/node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -11128,22 +10457,6 @@ "node": ">=4.0" } }, - "node_modules/jsx-ast-utils": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", - "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - }, - "engines": { - "node": ">=4.0" - } - }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -11658,26 +10971,6 @@ "dev": true, "license": "MIT" }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/loose-envify/node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lru-cache": { "version": "11.2.7", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", @@ -11907,13 +11200,6 @@ "dev": true, "license": "MIT" }, - "node_modules/module-replacements": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/module-replacements/-/module-replacements-2.11.0.tgz", - "integrity": "sha512-j5sNQm3VCpQQ7nTqGeOZtoJtV3uKERgCBm9QRhmGRiXiqkf7iRFOkfxdJRZWLkqYY8PNf4cDQF/WfXUYLENrRA==", - "dev": true, - "license": "MIT" - }, "node_modules/moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", @@ -12039,35 +11325,6 @@ "semver": "bin/semver.js" } }, - "node_modules/node-exports-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", - "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "array.prototype.flatmap": "^1.3.3", - "es-errors": "^1.3.0", - "object.entries": "^1.1.9", - "semver": "^6.3.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/node-exports-info/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/node-fetch": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", @@ -12139,16 +11396,6 @@ "node": ">=0.10.0" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -12209,22 +11456,6 @@ "node": ">= 0.4" } }, - "node_modules/object.entries": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", - "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/object.fromentries": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", @@ -12730,6 +11961,7 @@ "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright-core": "1.58.2" }, @@ -12796,6 +12028,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -12821,6 +12054,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "lilconfig": "^3.1.1" }, @@ -13280,18 +12514,6 @@ "react-is": "^16.13.1" } }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/proxy-agent": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", @@ -13440,13 +12662,6 @@ "dev": true, "license": "MIT" }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, - "license": "MIT" - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -13553,16 +12768,6 @@ "regexp-tree": "bin/regexp-tree" } }, - "node_modules/regexp-tree": { - "version": "0.1.27", - "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", - "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", - "dev": true, - "license": "MIT", - "bin": { - "regexp-tree": "bin/regexp-tree" - } - }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -13604,16 +12809,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -13874,16 +13069,6 @@ "regexp-tree": "~0.1.1" } }, - "node_modules/safe-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", - "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "regexp-tree": "~0.1.1" - } - }, "node_modules/safe-regex-test": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", @@ -14477,45 +13662,6 @@ "es-abstract": "^1.17.5" } }, - "node_modules/string.prototype.matchall": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", - "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "regexp.prototype.flags": "^1.5.3", - "set-function-name": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.repeat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", - "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, "node_modules/string.prototype.trim": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", @@ -14654,13 +13800,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", -<<<<<<< HEAD - "dev": true, - "license": "MIT", - "peer": true -======= "license": "MIT" ->>>>>>> 5772811 (chore: Package modernise, update linter) }, "node_modules/sublevel-pouchdb": { "version": "9.0.0", @@ -14729,6 +13869,7 @@ "integrity": "sha512-0a/huwc8e2es+7KFi70esqsReRfRbrT8h1cJSY/+z1lF0yKM6TT+//HYu28Yxstr50H7ifaqZRDGd0KuKDxP7w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -14922,37 +14063,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/synckit": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.3.tgz", - "integrity": "sha512-JJoOEKTfL1urb1mDoEblhD9NhEbWmq9jHEMEnxoC4ujUaZ4itA8vKgwkFAyNClgxplLi9tsUKX+EduK0p/l7sg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, - "node_modules/tapable": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", - "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/tar-stream": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", @@ -15101,6 +14211,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -15146,22 +14257,6 @@ "url": "https://github.com/sponsors/ota-meshi" } }, - "node_modules/toml-eslint-parser": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/toml-eslint-parser/-/toml-eslint-parser-0.9.3.tgz", - "integrity": "sha512-moYoCvkNUAPCxSW9jmHmRElhm4tVJpHL8ItC/+uYD0EpPSFXbck7yREz9tNdJVTSpHVod8+HoipcpbQ0oE6gsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.0.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - } - }, "node_modules/totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", @@ -15250,6 +14345,7 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -15777,19 +14873,6 @@ "node": "*" } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", @@ -15906,6 +14989,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16182,274 +15266,6 @@ "node": ">= 4" } }, - "node_modules/typescript-eslint": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.3.tgz", - "integrity": "sha512-KgusgyDgG4LI8Ih/sWaCtZ06tckLAS5CvT5A4D1Q7bYVoAAyzwiZvE4BmwDHkhRVkvhRBepKeASoFzQetha7Fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.59.3", - "@typescript-eslint/parser": "8.59.3", - "@typescript-eslint/typescript-estree": "8.59.3", - "@typescript-eslint/utils": "8.59.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.3.tgz", - "integrity": "sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.59.3", - "@typescript-eslint/type-utils": "8.59.3", - "@typescript-eslint/utils": "8.59.3", - "@typescript-eslint/visitor-keys": "8.59.3", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.59.3", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.3.tgz", - "integrity": "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/scope-manager": "8.59.3", - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/typescript-estree": "8.59.3", - "@typescript-eslint/visitor-keys": "8.59.3", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/project-service": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.3.tgz", - "integrity": "sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.59.3", - "@typescript-eslint/types": "^8.59.3", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.3.tgz", - "integrity": "sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/visitor-keys": "8.59.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.3.tgz", - "integrity": "sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/type-utils": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.3.tgz", - "integrity": "sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/typescript-estree": "8.59.3", - "@typescript-eslint/utils": "8.59.3", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.3.tgz", - "integrity": "sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.3.tgz", - "integrity": "sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.59.3", - "@typescript-eslint/tsconfig-utils": "8.59.3", - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/visitor-keys": "8.59.3", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.3.tgz", - "integrity": "sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.59.3", - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/typescript-estree": "8.59.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.3.tgz", - "integrity": "sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.59.3", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/typescript-eslint/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/typescript-eslint/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", @@ -16591,6 +15407,7 @@ "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -17227,6 +16044,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -17260,6 +16078,7 @@ "integrity": "sha512-yF+o4POL41rpAzj5KVILUxm1GCjKnELvaqmU9TLLUbMfDzuN0UpUR9uaDs+mCtjPe+uYPksXDRLQGGPvj1cTmA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.1.1", "@vitest/mocker": "4.1.1", @@ -17366,13 +16185,7 @@ "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", -<<<<<<< HEAD - "dev": true, - "license": "MIT", - "peer": true -======= "license": "MIT" ->>>>>>> 5772811 (chore: Package modernise, update linter) }, "node_modules/wait-port": { "version": "1.1.0", @@ -18051,23 +16864,6 @@ "url": "https://github.com/sponsors/ota-meshi" } }, - "node_modules/yaml-eslint-parser": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/yaml-eslint-parser/-/yaml-eslint-parser-1.3.2.tgz", - "integrity": "sha512-odxVsHAkZYYglR30aPYRY4nUGJnoJ2y1ww2HDvZALo0BDETv9kWbi16J52eHs+PWRNmF4ub6nZqfVOeesOvntg==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.0.0", - "yaml": "^2.0.0" - }, - "engines": { - "node": "^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - } - }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", From 106367fa41c55d17ff152ff44d2bcd60d0bbb48e Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 11:09:04 +0000 Subject: [PATCH 20/73] Adding a rough DI --- src/lib | 2 +- src/main.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib b/src/lib index 6c53e74..e5ccf1b 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 6c53e748eb3dff92514e1cd28359007c8fcb3173 +Subproject commit e5ccf1befe5dfa48aac8dcfa63dc1539cf770d5f diff --git a/src/main.ts b/src/main.ts index a07ce40..63242b0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,6 @@ -import { Notice, Plugin, type App, type PluginManifest } from "./deps"; - +import { getLanguage, Notice, Plugin, type App, type PluginManifest } from "./deps"; +import { setGetLanguage } from "./lib/src/common/coreEnvFunctions.ts"; +setGetLanguage(getLanguage); import { LiveSyncCommands } from "./features/LiveSyncCommands.ts"; import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts"; import { ConfigSync } from "./features/ConfigSync/CmdConfigSync.ts"; From 37715d4c9f055f34c28fc8df7b950f02f0dcc638 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 11:12:40 +0000 Subject: [PATCH 21/73] chore: ran prettier --- src/LiveSyncBaseCore.ts | 11 +++--- .../cli/commands/daemonCommand.unit.spec.ts | 12 +++---- src/apps/cli/commands/runCommand.ts | 34 ++++++++++++------- src/apps/cli/commands/types.ts | 11 +++++- src/apps/cli/main.ts | 5 +-- .../managers/CLIStorageEventManagerAdapter.ts | 6 +++- ...CLIStorageEventManagerAdapter.unit.spec.ts | 5 +-- .../cli/serviceModules/CLIServiceModules.ts | 22 +++++++----- src/apps/cli/serviceModules/IgnoreRules.ts | 6 ++-- .../serviceModules/IgnoreRules.unit.spec.ts | 5 +-- src/apps/cli/vite.config.ts | 3 +- .../ObsidianVaultAdapter.ts | 2 +- 12 files changed, 73 insertions(+), 49 deletions(-) diff --git a/src/LiveSyncBaseCore.ts b/src/LiveSyncBaseCore.ts index a3945fd..9a49451 100644 --- a/src/LiveSyncBaseCore.ts +++ b/src/LiveSyncBaseCore.ts @@ -35,11 +35,12 @@ export class LiveSyncBaseCore< TCommands extends IMinimumLiveSyncCommands = IMinimumLiveSyncCommands, > implements - LiveSyncLocalDBEnv, - LiveSyncReplicatorEnv, - LiveSyncJournalReplicatorEnv, - LiveSyncCouchDBReplicatorEnv, - HasSettings { + LiveSyncLocalDBEnv, + LiveSyncReplicatorEnv, + LiveSyncJournalReplicatorEnv, + LiveSyncCouchDBReplicatorEnv, + HasSettings +{ addOns = [] as TCommands[]; /** diff --git a/src/apps/cli/commands/daemonCommand.unit.spec.ts b/src/apps/cli/commands/daemonCommand.unit.spec.ts index 1adb967..2e2a341 100644 --- a/src/apps/cli/commands/daemonCommand.unit.spec.ts +++ b/src/apps/cli/commands/daemonCommand.unit.spec.ts @@ -257,12 +257,12 @@ describe("daemon command", () => { // failure 1: 30000*2=60000, failure 2: 30000*4=120000, // failure 3: 30000*8=240000, failure 4: 30000*16=480000→capped, 5→cap, 6→cap const expectedIntervals = [ - baseMs * 2, // after failure 1: 60000 - baseMs * 4, // after failure 2: 120000 - baseMs * 8, // after failure 3: 240000 - 300_000, // after failure 4 (would be 480000, capped) - 300_000, // after failure 5 (cap) - 300_000, // after failure 6 (cap) + baseMs * 2, // after failure 1: 60000 + baseMs * 4, // after failure 2: 120000 + baseMs * 8, // after failure 3: 240000 + 300_000, // after failure 4 (would be 480000, capped) + 300_000, // after failure 5 (cap) + 300_000, // after failure 6 (cap) ]; for (const expected of expectedIntervals) { diff --git a/src/apps/cli/commands/runCommand.ts b/src/apps/cli/commands/runCommand.ts index c90fa94..9f7df02 100644 --- a/src/apps/cli/commands/runCommand.ts +++ b/src/apps/cli/commands/runCommand.ts @@ -43,10 +43,13 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext // 3. Re-enable sync. const restoreSyncSettings = async () => { - await core.services.setting.applyPartial({ - ...context.originalSyncSettings, - suspendFileWatching: false, - }, true); + await core.services.setting.applyPartial( + { + ...context.originalSyncSettings, + suspendFileWatching: false, + }, + true + ); // applySettings fires the full lifecycle: onSuspending → onResumed. // ModuleReplicatorCouchDB starts continuous replication on onResumed // via fireAndForget. @@ -54,10 +57,13 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext // Lifecycle events (onSuspending) may re-enable suspension flags. // Clear them explicitly after the lifecycle completes. applyPartial // with true is a direct store write — it does not re-trigger lifecycle. - await core.services.setting.applyPartial({ - suspendFileWatching: false, - suspendParseReplicationResult: false, - }, true); + await core.services.setting.applyPartial( + { + suspendFileWatching: false, + suspendParseReplicationResult: false, + }, + true + ); }; if (options.interval) { log(`Polling mode: syncing every ${options.interval}s`); @@ -80,7 +86,9 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext currentIntervalMs = Math.min(baseIntervalMs * Math.pow(2, consecutiveFailures), maxIntervalMs); console.error(`[Daemon] Poll error (${consecutiveFailures} consecutive):`, err); if (consecutiveFailures >= 5) { - console.error(`[Daemon] Warning: ${consecutiveFailures} consecutive failures, backing off to ${Math.round(currentIntervalMs / 1000)}s`); + console.error( + `[Daemon] Warning: ${consecutiveFailures} consecutive failures, backing off to ${Math.round(currentIntervalMs / 1000)}s` + ); } } pollTimer = setTimeout(poll, currentIntervalMs); @@ -99,9 +107,11 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext log("LiveSync active"); const currentSettings = core.services.setting.currentSettings(); if (!currentSettings.liveSync && !currentSettings.syncOnStart) { - console.error("[Daemon] Warning: liveSync and syncOnStart are both disabled in settings. " + - "No sync will occur. Set liveSync=true in your settings file for continuous sync, " + - "or use --interval for polling mode."); + console.error( + "[Daemon] Warning: liveSync and syncOnStart are both disabled in settings. " + + "No sync will occur. Set liveSync=true in your settings file for continuous sync, " + + "or use --interval for polling mode." + ); } } diff --git a/src/apps/cli/commands/types.ts b/src/apps/cli/commands/types.ts index ca01152..d6ccee5 100644 --- a/src/apps/cli/commands/types.ts +++ b/src/apps/cli/commands/types.ts @@ -37,7 +37,16 @@ export interface CLICommandContext { databasePath: string; core: LiveSyncBaseCore; settingsPath: string; - originalSyncSettings: Pick; + originalSyncSettings: Pick< + ObsidianLiveSyncSettings, + | "liveSync" + | "syncOnStart" + | "periodicReplication" + | "syncOnSave" + | "syncOnEditorSave" + | "syncOnFileOpen" + | "syncAfterMerge" + >; } export const VALID_COMMANDS = new Set([ diff --git a/src/apps/cli/main.ts b/src/apps/cli/main.ts index 535d137..7067c70 100644 --- a/src/apps/cli/main.ts +++ b/src/apps/cli/main.ts @@ -280,16 +280,13 @@ export async function main() { // chokidar's ignored option is populated when beginWatch() fires during onLoad(). const watchEnabled = options.command === "daemon"; const vaultPath = - options.command === "mirror" && options.commandArgs[0] - ? path.resolve(options.commandArgs[0]) - : databasePath; + options.command === "mirror" && options.commandArgs[0] ? path.resolve(options.commandArgs[0]) : databasePath; let ignoreRules: IgnoreRules | undefined; if (options.command === "daemon" || options.command === "mirror") { ignoreRules = new IgnoreRules(vaultPath); await ignoreRules.load(); } - // Create service context and hub const context = new NodeServiceContext(databasePath); const serviceHubInstance = new NodeServiceHub(databasePath, context); diff --git a/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts b/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts index 9abc5fd..c2f11e1 100644 --- a/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts +++ b/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts @@ -97,7 +97,11 @@ class CLIConverterAdapter implements IStorageEventConverterAdapter { class CLIWatchAdapter implements IStorageEventWatchAdapter { private _watcher: FSWatcher | undefined; - constructor(private basePath: string, private ignoreRules?: IgnoreRules, private watchEnabled: boolean = false) {} + constructor( + private basePath: string, + private ignoreRules?: IgnoreRules, + private watchEnabled: boolean = false + ) {} private _toNodeFile(filePath: string, stats: Stats | undefined): NodeFile { return { diff --git a/src/apps/cli/managers/CLIStorageEventManagerAdapter.unit.spec.ts b/src/apps/cli/managers/CLIStorageEventManagerAdapter.unit.spec.ts index edfb222..5af69a9 100644 --- a/src/apps/cli/managers/CLIStorageEventManagerAdapter.unit.spec.ts +++ b/src/apps/cli/managers/CLIStorageEventManagerAdapter.unit.spec.ts @@ -60,10 +60,7 @@ describe("CLIStorageEventManagerAdapter", () => { await adapter.watch.beginWatch(handlers); expect(chokidar.watch).toHaveBeenCalledTimes(1); - expect(chokidar.watch).toHaveBeenCalledWith( - "/base", - expect.objectContaining({ ignoreInitial: true }) - ); + expect(chokidar.watch).toHaveBeenCalledWith("/base", expect.objectContaining({ ignoreInitial: true })); }); it("add event produces NodeFile with correct relative path via onCreate", async () => { diff --git a/src/apps/cli/serviceModules/CLIServiceModules.ts b/src/apps/cli/serviceModules/CLIServiceModules.ts index 6c4cce5..50d185a 100644 --- a/src/apps/cli/serviceModules/CLIServiceModules.ts +++ b/src/apps/cli/serviceModules/CLIServiceModules.ts @@ -25,7 +25,7 @@ export function initialiseServiceModulesCLI( core: LiveSyncBaseCore, services: InjectableServiceHub, ignoreRules?: IgnoreRules, - watchEnabled: boolean = false, + watchEnabled: boolean = false ): ServiceModules { const storageAccessManager = new StorageAccessManager(); @@ -39,13 +39,19 @@ export function initialiseServiceModulesCLI( }); // CLI-specific storage event manager - const storageEventManager = new StorageEventManagerCLI(basePath, core, { - fileProcessing: services.fileProcessing, - setting: services.setting, - vaultService: services.vault, - storageAccessManager: storageAccessManager, - APIService: services.API, - }, ignoreRules, watchEnabled); + const storageEventManager = new StorageEventManagerCLI( + basePath, + core, + { + fileProcessing: services.fileProcessing, + setting: services.setting, + vaultService: services.vault, + storageAccessManager: storageAccessManager, + APIService: services.API, + }, + ignoreRules, + watchEnabled + ); // Close the file watcher during graceful shutdown so the process can exit cleanly. services.appLifecycle.onUnload.addHandler(async () => { diff --git a/src/apps/cli/serviceModules/IgnoreRules.ts b/src/apps/cli/serviceModules/IgnoreRules.ts index 9764fd2..0b7fc41 100644 --- a/src/apps/cli/serviceModules/IgnoreRules.ts +++ b/src/apps/cli/serviceModules/IgnoreRules.ts @@ -55,7 +55,9 @@ export class IgnoreRules { continue; } if (trimmed.startsWith("import:")) { - console.error(`[IgnoreRules] Warning: unrecognised directive '${trimmed}' — only 'import: .gitignore' is supported`); + console.error( + `[IgnoreRules] Warning: unrecognised directive '${trimmed}' — only 'import: .gitignore' is supported` + ); continue; } this._addPattern(trimmed); @@ -105,7 +107,7 @@ export class IgnoreRules { if (raw.startsWith("!")) { throw new Error( `[IgnoreRules] Negation pattern '${raw}' is not supported. ` + - `Remove it from .livesync/ignore or use a separate include/exclude file.` + `Remove it from .livesync/ignore or use a separate include/exclude file.` ); } this.patterns.push(this._normalisePattern(raw)); diff --git a/src/apps/cli/serviceModules/IgnoreRules.unit.spec.ts b/src/apps/cli/serviceModules/IgnoreRules.unit.spec.ts index 59bfb12..4f6a606 100644 --- a/src/apps/cli/serviceModules/IgnoreRules.unit.spec.ts +++ b/src/apps/cli/serviceModules/IgnoreRules.unit.spec.ts @@ -122,10 +122,7 @@ describe("IgnoreRules", () => { describe("load() with comments and blank lines", () => { it("skips # comment lines and blank lines", async () => { const vaultPath = await createVault(); - await writeIgnoreFile( - vaultPath, - "# This is a comment\n\n \n*.tmp\n# another comment\nbuild/\n" - ); + await writeIgnoreFile(vaultPath, "# This is a comment\n\n \n*.tmp\n# another comment\nbuild/\n"); const rules = new IgnoreRules(vaultPath); await rules.load(); expect(rules.shouldIgnore("scratch.tmp")).toBe(true); diff --git a/src/apps/cli/vite.config.ts b/src/apps/cli/vite.config.ts index 74c4ba6..11104cd 100644 --- a/src/apps/cli/vite.config.ts +++ b/src/apps/cli/vite.config.ts @@ -47,7 +47,8 @@ function injectBanner(): import("vite").Plugin { // Insert after the shebang line if present, otherwise at the top. if (chunk.code.startsWith("#!")) { const newline = chunk.code.indexOf("\n"); - chunk.code = chunk.code.slice(0, newline + 1) + fileReaderPolyfillBanner + chunk.code.slice(newline + 1); + chunk.code = + chunk.code.slice(0, newline + 1) + fileReaderPolyfillBanner + chunk.code.slice(newline + 1); } else { chunk.code = fileReaderPolyfillBanner + chunk.code; } diff --git a/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts b/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts index 78d3559..bb89774 100644 --- a/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts +++ b/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts @@ -7,7 +7,7 @@ import type { TFile, App, TFolder } from "obsidian"; * Vault adapter implementation for Obsidian */ export class ObsidianVaultAdapter implements IVaultAdapter { - constructor(private app: App) { } + constructor(private app: App) {} async read(file: TFile): Promise { return await this.app.vault.read(file); From e2c54aaf43f5b2e46bd1d90090a27f95955a08e0 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 13:31:11 +0100 Subject: [PATCH 22/73] Update lib to fix P2P problems --- src/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib b/src/lib index 6a2dc67..30f5ed1 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 6a2dc6777f1eb2beb7a058b8d2dde662662df9d7 +Subproject commit 30f5ed1256f620c8c3b2cc69392e1482fcfb24dc From c45aca4794cc13c280eac655ca33e0ffcb9623a5 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 13:45:09 +0100 Subject: [PATCH 23/73] fixed: fixed subrepo pointer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I thought I’d rebased it, but it turns out everything had been merged. --- src/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib b/src/lib index 30f5ed1..adcfe42 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 30f5ed1256f620c8c3b2cc69392e1482fcfb24dc +Subproject commit adcfe4252200a6c43d922bab6e415a0f98de5920 From 0549e901b2fc40198d99def63fe06f79ded183aa Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 03:55:11 +0100 Subject: [PATCH 24/73] (chore): removing DOM Operation --- src/apps/webapp/bootstrap.ts | 2 +- src/lib | 2 +- .../DocumentHistory/DocumentHistoryModal.ts | 119 ++++++++++++------ .../ConflictResolveModal.ts | 52 +++++--- .../features/SettingDialogue/PaneChangeLog.ts | 7 +- .../features/SettingDialogue/PaneSetup.ts | 9 +- .../SettingDialogue/utilFixCouchDBSetting.ts | 9 +- 7 files changed, 131 insertions(+), 69 deletions(-) diff --git a/src/apps/webapp/bootstrap.ts b/src/apps/webapp/bootstrap.ts index 2450285..b3fa072 100644 --- a/src/apps/webapp/bootstrap.ts +++ b/src/apps/webapp/bootstrap.ts @@ -41,7 +41,7 @@ async function renderHistoryList(): Promise { const [items, lastUsedId] = await Promise.all([historyStore.getVaultHistory(), historyStore.getLastUsedVaultId()]); - listEl.innerHTML = ""; + listEl.replaceChildren(); emptyEl.classList.toggle("is-hidden", items.length > 0); for (const item of items) { diff --git a/src/lib b/src/lib index adcfe42..c74a8b6 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit adcfe4252200a6c43d922bab6e415a0f98de5920 +Subproject commit c74a8b60ff8c14f6edd2aa20fee2d7fe1cd0793f diff --git a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts index 7e7560a..40a5254 100644 --- a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts +++ b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts @@ -1,6 +1,6 @@ import { TFile, Modal, App, DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "../../../deps.ts"; import { getPathFromTFile, isValidPath } from "../../../common/utils.ts"; -import { decodeBinary, escapeStringToHTML, readString } from "../../../lib/src/string_and_binary/convert.ts"; +import { decodeBinary, readString } from "../../../lib/src/string_and_binary/convert.ts"; import ObsidianLiveSyncPlugin from "../../../main.ts"; import { type DocumentID, @@ -145,22 +145,66 @@ export class DocumentHistoryModal extends Modal { return v; } + prepareContentView(usePreformatted = true) { + this.contentView.empty(); + this.contentView.toggleClass("op-pre", usePreformatted); + } + + appendTextDiff(diff: [number, string][]) { + for (const [operation, text] of diff) { + if (operation == DIFF_DELETE) { + this.contentView.createSpan({ text, cls: "history-deleted" }); + } else if (operation == DIFF_EQUAL) { + this.contentView.createSpan({ text, cls: "history-normal" }); + } else if (operation == DIFF_INSERT) { + this.contentView.createSpan({ text, cls: "history-added" }); + } + } + } + + appendImageDiff(baseSrc: string, overlaySrc?: string) { + const wrap = this.contentView.createDiv({ cls: "ls-imgdiff-wrap" }); + const overlay = wrap.createDiv({ cls: "overlay" }); + overlay.createEl("img", { cls: "img-base" }, (img) => { + img.src = baseSrc; + }); + if (overlaySrc) { + overlay.createEl("img", { cls: "img-overlay" }, (img) => { + img.src = overlaySrc; + }); + } + } + + appendDeletedNotice(usePreformatted = true) { + const notice = "(At this revision, the file has been deleted)"; + if (usePreformatted) { + this.contentView.appendText(`${notice}\n`); + } else { + this.contentView.createDiv({ text: notice }); + } + } + async showExactRev(rev: string) { const db = this.core.localDatabase; const w = await db.getDBEntry(this.file, { rev: rev }, false, false, true); this.currentText = ""; this.currentDeleted = false; + this.prepareContentView(); if (w === false) { this.currentDeleted = true; - this.info.innerHTML = ""; - this.contentView.innerHTML = `Could not read this revision
(${rev})`; + this.info.empty(); + this.contentView.appendText("Could not read this revision"); + this.contentView.createEl("br"); + this.contentView.appendText(`(${rev})`); } else { this.currentDoc = w; - this.info.innerHTML = `Modified:${new Date(w.mtime).toLocaleString()}`; - let result = undefined; + this.info.setText(`Modified:${new Date(w.mtime).toLocaleString()}`); const w1data = readDocument(w); this.currentDeleted = !!w.deleted; - // this.currentText = w1data; + if (typeof w1data == "string") { + this.currentText = w1data; + } + let rendered = false; if (this.showDiff) { const prevRevIdx = this.revs_info.length - 1 - ((this.range.value as any) / 1 - 1); if (prevRevIdx >= 0 && prevRevIdx < this.revs_info.length) { @@ -168,58 +212,55 @@ export class DocumentHistoryModal extends Modal { const w2 = await db.getDBEntry(this.file, { rev: oldRev }, false, false, true); if (w2 != false) { if (typeof w1data == "string") { - result = ""; - const dmp = new diff_match_patch(); - const w2data = readDocument(w2) as string; - const diff = dmp.diff_main(w2data, w1data); - dmp.diff_cleanupSemantic(diff); - for (const v of diff) { - const x1 = v[0]; - const x2 = v[1]; - if (x1 == DIFF_DELETE) { - result += "" + escapeStringToHTML(x2) + ""; - } else if (x1 == DIFF_EQUAL) { - result += "" + escapeStringToHTML(x2) + ""; - } else if (x1 == DIFF_INSERT) { - result += "" + escapeStringToHTML(x2) + ""; + const w2data = readDocument(w2); + if (typeof w2data == "string") { + const dmp = new diff_match_patch(); + const diff = dmp.diff_main(w2data, w1data); + dmp.diff_cleanupSemantic(diff); + if (this.currentDeleted) { + this.appendDeletedNotice(); } + this.appendTextDiff(diff); + rendered = true; } - result = result.replace(/\n/g, "
"); } else if (isImage(this.file)) { const src = this.generateBlobURL("base", w1data); const overlay = this.generateBlobURL( "overlay", readDocument(w2) as Uint8Array ); - result = `
-
- - -
-
`; - this.contentView.removeClass("op-pre"); + this.prepareContentView(false); + if (this.currentDeleted) { + this.appendDeletedNotice(false); + } + this.appendImageDiff(src, overlay); + rendered = true; } } } } - if (result == undefined) { + if (!rendered) { if (typeof w1data != "string") { if (isImage(this.file)) { const src = this.generateBlobURL("base", w1data); - result = `
-
- -
-
`; - this.contentView.removeClass("op-pre"); + this.prepareContentView(false); + if (this.currentDeleted) { + this.appendDeletedNotice(false); + } + this.appendImageDiff(src); + } else { + if (this.currentDeleted) { + this.appendDeletedNotice(); + } + this.contentView.appendText("Binary file"); } } else { - result = escapeStringToHTML(w1data); + if (this.currentDeleted) { + this.appendDeletedNotice(); + } + this.contentView.appendText(w1data); } } - if (result == undefined) result = typeof w1data == "string" ? escapeStringToHTML(w1data) : "Binary file"; - this.contentView.innerHTML = - (this.currentDeleted ? "(At this revision, the file has been deleted)\n" : "") + result; } // Reset diff navigation after content changes this.resetDiffNavigation(); diff --git a/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts b/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts index ad308e5..eec5332 100644 --- a/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts +++ b/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts @@ -1,7 +1,6 @@ import { App, Modal } from "../../../deps.ts"; import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT } from "diff-match-patch"; import { CANCELLED, LEAVE_TO_SUBSEQUENT, type diff_result } from "../../../lib/src/common/types.ts"; -import { escapeStringToHTML } from "../../../lib/src/string_and_binary/convert.ts"; import { delay } from "../../../lib/src/common/utils.ts"; import { eventHub } from "../../../common/events.ts"; import { globalSlipBoard } from "../../../lib/src/bureau/bureau.ts"; @@ -44,6 +43,25 @@ export class ConflictResolveModal extends Modal { // sendValue("close-resolve-conflict:" + this.filename, false); } + appendDiffFragment(container: HTMLDivElement, text: string, cls: string) { + const lines = text.split("\n"); + lines.forEach((line, index) => { + const span = container.createSpan({ cls }); + span.textContent = line; + if (index < lines.length - 1) { + container.createSpan({ cls: "ls-mark-cr" }); + container.createEl("br"); + } + }); + } + + appendVersionInfo(container: HTMLDivElement, cls: string, name: string, date: string) { + const line = container.createSpan({ cls }); + line.createSpan({ text: name, cls: "conflict-dev-name" }); + line.appendText(`: ${date}`); + container.createEl("br"); + } + override onOpen() { const { contentEl } = this; // Send cancel signal for the previous merge dialogue @@ -64,25 +82,21 @@ export class ConflictResolveModal extends Modal { const div = contentEl.createDiv(""); div.addClass("op-scrollable"); div.addClass("ls-dialog"); - let diff = ""; + let diffLength = 0; for (const v of this.result.diff) { const x1 = v[0]; const x2 = v[1]; + diffLength += x2.length; + if (diffLength > 100 * 1024) { + continue; + } if (x1 == DIFF_DELETE) { - diff += - "" + - escapeStringToHTML(x2).replace(/\n/g, "\n") + - ""; + this.appendDiffFragment(div, x2, "deleted"); + div.createEl("span", { text: x2, cls: "deleted normal conflict-dev-name" }); } else if (x1 == DIFF_EQUAL) { - diff += - "" + - escapeStringToHTML(x2).replace(/\n/g, "\n") + - ""; + this.appendDiffFragment(div, x2, "normal"); } else if (x1 == DIFF_INSERT) { - diff += - "" + - escapeStringToHTML(x2).replace(/\n/g, "\n") + - ""; + this.appendDiffFragment(div, x2, "added"); } } @@ -92,8 +106,8 @@ export class ConflictResolveModal extends Modal { new Date(this.result.left.mtime).toLocaleString() + (this.result.left.deleted ? " (Deleted)" : ""); const date2 = new Date(this.result.right.mtime).toLocaleString() + (this.result.right.deleted ? " (Deleted)" : ""); - div2.innerHTML = `${this.localName}: ${date1}
-${this.remoteName}: ${date2}
`; + this.appendVersionInfo(div2, "deleted", this.localName, date1); + this.appendVersionInfo(div2, "added", this.remoteName, date2); contentEl.createEl("button", { text: `Use ${this.localName}` }, (e) => e.addEventListener("click", () => this.sendResponse(this.result.right.rev)) ).style.marginRight = "4px"; @@ -108,11 +122,9 @@ export class ConflictResolveModal extends Modal { contentEl.createEl("button", { text: !this.pluginPickMode ? "Not now" : "Cancel" }, (e) => e.addEventListener("click", () => this.sendResponse(CANCELLED)) ).style.marginRight = "4px"; - diff = diff.replace(/\n/g, "
"); - if (diff.length > 100 * 1024) { + if (diffLength > 100 * 1024) { + div.empty(); div.innerText = "(Too large diff to display)"; - } else { - div.innerHTML = diff; } } diff --git a/src/modules/features/SettingDialogue/PaneChangeLog.ts b/src/modules/features/SettingDialogue/PaneChangeLog.ts index be0e0c3..3d8ceb9 100644 --- a/src/modules/features/SettingDialogue/PaneChangeLog.ts +++ b/src/modules/features/SettingDialogue/PaneChangeLog.ts @@ -43,10 +43,13 @@ export function paneChangeLog(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElem // tmpDiv.addClass("sls-header-button"); tmpDiv.addClass("op-warn-info"); - tmpDiv.innerHTML = `

${$msg("obsidianLiveSyncSettingTab.msgNewVersionNote")}

`; + tmpDiv.createEl("p", { text: $msg("obsidianLiveSyncSettingTab.msgNewVersionNote") }); + const readEverythingButton = tmpDiv.createEl("button", { + text: $msg("obsidianLiveSyncSettingTab.optionOkReadEverything"), + }); if (lastVersion > (this.editingSettings?.lastReadUpdates || 0)) { const informationButtonDiv = informationDivEl.appendChild(tmpDiv); - informationButtonDiv.querySelector("button")?.addEventListener("click", () => { + readEverythingButton.addEventListener("click", () => { fireAndForget(async () => { this.editingSettings.lastReadUpdates = lastVersion; await this.saveAllDirtySettings(); diff --git a/src/modules/features/SettingDialogue/PaneSetup.ts b/src/modules/features/SettingDialogue/PaneSetup.ts index de996bc..40cd88b 100644 --- a/src/modules/features/SettingDialogue/PaneSetup.ts +++ b/src/modules/features/SettingDialogue/PaneSetup.ts @@ -125,8 +125,13 @@ export function paneSetup( paneEl, "div", "", - (el) => - (el.innerHTML = `${$msg("obsidianLiveSyncSettingTab.linkOpenInBrowser")}`) + (el) => { + el.createEl("a", { text: $msg("obsidianLiveSyncSettingTab.linkOpenInBrowser") }, (anchor) => { + anchor.href = `https://github.com/${repo}/blob/main${topPath}`; + anchor.target = "_blank"; + anchor.rel = "noopener"; + }); + } ); const troubleShootEl = this.createEl(paneEl, "div", { text: "", diff --git a/src/modules/features/SettingDialogue/utilFixCouchDBSetting.ts b/src/modules/features/SettingDialogue/utilFixCouchDBSetting.ts index 041c923..d061643 100644 --- a/src/modules/features/SettingDialogue/utilFixCouchDBSetting.ts +++ b/src/modules/features/SettingDialogue/utilFixCouchDBSetting.ts @@ -13,7 +13,7 @@ export const checkConfig = async ( Logger($msg("obsidianLiveSyncSettingTab.logCheckingDbConfig"), LOG_LEVEL_INFO); let isSuccessful = true; const emptyDiv = createDiv(); - emptyDiv.innerHTML = ""; + emptyDiv.createSpan(); checkResultDiv?.replaceChildren(...[emptyDiv]); const addResult = (msg: string, classes?: string[]) => { const tmpDiv = createDiv(); @@ -21,7 +21,7 @@ export const checkConfig = async ( if (classes) { tmpDiv.addClasses(classes); } - tmpDiv.innerHTML = `${msg}`; + tmpDiv.textContent = msg; checkResultDiv?.appendChild(tmpDiv); }; try { @@ -47,9 +47,10 @@ export const checkConfig = async ( if (!checkResultDiv) return; const tmpDiv = createDiv(); tmpDiv.addClass("ob-btn-config-fix"); - tmpDiv.innerHTML = ``; + tmpDiv.createEl("label", { text: title }); + const fixButton = tmpDiv.createEl("button", { text: $msg("obsidianLiveSyncSettingTab.btnFix") }); const x = checkResultDiv.appendChild(tmpDiv); - x.querySelector("button")?.addEventListener("click", () => { + fixButton.addEventListener("click", () => { fireAndForget(async () => { Logger($msg("obsidianLiveSyncSettingTab.logCouchDbConfigSet", { title, key, value })); const res = await requestToCouchDBWithCredentials( From a130e3700e3afc81f4fc22d4e3d49e865d0cc0d9 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 03:58:08 +0100 Subject: [PATCH 25/73] prettify --- .../testdeno/test-e2e-two-vaults-couchdb.ts | 3 ++- .../DocumentHistory/DocumentHistoryModal.ts | 3 +-- .../features/SettingDialogue/PaneSetup.ts | 19 +++++++------------ 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts b/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts index 6f5244b..0c0151a 100644 --- a/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts +++ b/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts @@ -29,7 +29,8 @@ export async function runScenario(remoteType: RemoteType, encrypt: boolean): Pro const dbPrefix = remoteType === "COUCHDB" ? requireEnv("COUCHDB_DBNAME", "dbname") : ""; const dbname = remoteType === "COUCHDB" ? `${dbPrefix}-${dbSuffix}` : ""; - const minioEndpoint = remoteType === "MINIO" ? requireEnv("MINIO_ENDPOINT", "minioEndpoint").replace(/\/$/, "") : ""; + const minioEndpoint = + remoteType === "MINIO" ? requireEnv("MINIO_ENDPOINT", "minioEndpoint").replace(/\/$/, "") : ""; const minioAccessKey = remoteType === "MINIO" ? requireEnv("MINIO_ACCESS_KEY", "accessKey") : ""; const minioSecretKey = remoteType === "MINIO" ? requireEnv("MINIO_SECRET_KEY", "secretKey") : ""; const minioBucketBase = remoteType === "MINIO" ? requireEnv("MINIO_BUCKET_NAME", "bucketName") : ""; diff --git a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts index 40a5254..b9cf49a 100644 --- a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts +++ b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts @@ -286,8 +286,7 @@ export class DocumentHistoryModal extends Modal { if (direction === "next") { this.currentDiffIndex = (this.currentDiffIndex + 1) % diffElements.length; } else { - this.currentDiffIndex = - this.currentDiffIndex <= 0 ? diffElements.length - 1 : this.currentDiffIndex - 1; + this.currentDiffIndex = this.currentDiffIndex <= 0 ? diffElements.length - 1 : this.currentDiffIndex - 1; } const target = diffElements[this.currentDiffIndex]; diff --git a/src/modules/features/SettingDialogue/PaneSetup.ts b/src/modules/features/SettingDialogue/PaneSetup.ts index 40cd88b..4e365c9 100644 --- a/src/modules/features/SettingDialogue/PaneSetup.ts +++ b/src/modules/features/SettingDialogue/PaneSetup.ts @@ -121,18 +121,13 @@ export function paneSetup( const repo = "vrtmrz/obsidian-livesync"; const topPath = $msg("obsidianLiveSyncSettingTab.linkTroubleshooting"); const rawRepoURI = `https://raw.githubusercontent.com/${repo}/main`; - this.createEl( - paneEl, - "div", - "", - (el) => { - el.createEl("a", { text: $msg("obsidianLiveSyncSettingTab.linkOpenInBrowser") }, (anchor) => { - anchor.href = `https://github.com/${repo}/blob/main${topPath}`; - anchor.target = "_blank"; - anchor.rel = "noopener"; - }); - } - ); + this.createEl(paneEl, "div", "", (el) => { + el.createEl("a", { text: $msg("obsidianLiveSyncSettingTab.linkOpenInBrowser") }, (anchor) => { + anchor.href = `https://github.com/${repo}/blob/main${topPath}`; + anchor.target = "_blank"; + anchor.rel = "noopener"; + }); + }); const troubleShootEl = this.createEl(paneEl, "div", { text: "", cls: "sls-troubleshoot-preview", From cc7af036186b6befbf3764a3254c7d22db8141e4 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 04:40:32 +0100 Subject: [PATCH 26/73] chore: Package modernise, update linter --- esbuild.config.mjs | 1 - eslint.config.mjs | 136 +- manifest.json | 2 +- package-lock.json | 1335 ++++++++++++++++- package.json | 11 +- .../SetupWizard/dialogs/ScanQRCode.svelte | 4 +- versions.json | 1 + vitest.config.p2p.ts | 8 +- vitest.config.ts | 9 +- 9 files changed, 1340 insertions(+), 167 deletions(-) diff --git a/esbuild.config.mjs b/esbuild.config.mjs index fe62f52..4350fab 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -2,7 +2,6 @@ import esbuild from "esbuild"; import process from "process"; -import builtins from "builtin-modules"; import sveltePlugin from "esbuild-svelte"; import { sveltePreprocess } from "svelte-preprocess"; import fs from "node:fs"; diff --git a/eslint.config.mjs b/eslint.config.mjs index 41cb90b..0efa595 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,103 +1,79 @@ -import typescriptEslint from "@typescript-eslint/eslint-plugin"; -import svelte from "eslint-plugin-svelte"; -import _import from "eslint-plugin-import"; -import { fixupPluginRules } from "@eslint/compat"; import tsParser from "@typescript-eslint/parser"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import js from "@eslint/js"; -import { FlatCompat } from "@eslint/eslintrc"; +import obsidianmd from "eslint-plugin-obsidianmd"; +import globals from "globals"; +import { defineConfig, globalIgnores } from "eslint/config"; +import * as sveltePlugin from "eslint-plugin-svelte"; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - allConfig: js.configs.all, -}); - -export default [ +export default defineConfig([ + globalIgnores([ + "**/node_modules/*", + "**/jest.config.js", + "src/lib/coverage", + "src/lib/browsertest", + "**/test.ts", + "**/tests.ts", + "**/**test.ts", + "**/**.test.ts", + "**/*.unit.spec.ts", + "**/esbuild.*.mjs", + "**/terser.*.mjs", + "**/node_modules", + "**/build", + "**/.eslintrc.js.bak", + "src/lib/src/patches/pouchdb-utils", + "**/esbuild.config.mjs", + "**/rollup.config.js", + "modules/octagonal-wheels/rollup.config.js", + "modules/octagonal-wheels/dist/**/*", + "src/lib/test", + "src/lib/_tools", + "src/lib/src/cli", + "**/main.js", + "src/apps/**/*", + ".prettierrc.*.mjs", + ".prettierrc.mjs", + "*.config.mjs", + ]), + ...sveltePlugin.configs["flat/base"], + ...obsidianmd.configs.recommended, { - ignores: [ - "**/node_modules/*", - "**/jest.config.js", - "src/lib/coverage", - "src/lib/browsertest", - "**/test.ts", - "**/tests.ts", - "**/**test.ts", - "**/**.test.ts", - "**/esbuild.*.mjs", - "**/terser.*.mjs", - "**/node_modules", - "**/build", - "**/.eslintrc.js.bak", - "src/lib/src/patches/pouchdb-utils", - "**/esbuild.config.mjs", - "**/rollup.config.js", - "modules/octagonal-wheels/rollup.config.js", - "modules/octagonal-wheels/dist/**/*", - "src/lib/test", - "src/lib/_tools", - "src/lib/src/cli", - "**/main.js", - "src/apps/**/*", - ".prettierrc.*.mjs", - ".prettierrc.mjs", - "*.config.mjs" - ], - }, - ...compat.extends( - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended" - ), - { - plugins: { - "@typescript-eslint": typescriptEslint, - svelte, - import: fixupPluginRules(_import), - }, - + files: ["**/*.ts"], languageOptions: { + globals: { ...globals.browser }, parser: tsParser, - ecmaVersion: 5, - sourceType: "module", - parserOptions: { - project: ["tsconfig.json"], + project: "./tsconfig.json", }, }, - rules: { "no-unused-vars": "off", - - "@typescript-eslint/no-unused-vars": [ - "error", - { - args: "none", - }, - ], - + "@typescript-eslint/no-unused-vars": ["error", { args: "none" }], "no-unused-labels": "off", "@typescript-eslint/ban-ts-comment": "off", "no-prototype-builtins": "off", "@typescript-eslint/no-empty-function": "off", "require-await": "error", + "obsidianmd/rule-custom-message": "off", // Temporary + "obsidianmd/ui/sentence-case": "off", // Temporary "@typescript-eslint/require-await": "warn", "@typescript-eslint/no-misused-promises": "warn", "@typescript-eslint/no-floating-promises": "warn", "no-async-promise-executor": "warn", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-unnecessary-type-assertion": "error", - - "no-constant-condition": [ - "error", - { - checkLoops: false, - }, - ], + "no-constant-condition": ["error", { checkLoops: false }], }, }, -]; - + { + files: ["**/*.svelte"], + languageOptions: { + parserOptions: { + parser: tsParser, + }, + }, + rules: { + "no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }], + "obsidianmd/no-plugin-as-component": "off", // Temporary + }, + }, +]); diff --git a/manifest.json b/manifest.json index cb9b9f1..f5ae865 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "id": "obsidian-livesync", "name": "Self-hosted LiveSync", "version": "0.25.60", - "minAppVersion": "0.9.12", + "minAppVersion": "1.2.3", "description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "author": "vorotamoroz", "authorUrl": "https://github.com/vrtmrz", diff --git a/package-lock.json b/package-lock.json index 4c2fba3..d480a28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "markdown-it": "^14.1.1", "micromatch": "^4.0.0", "minimatch": "^10.2.2", + "obsidian": "^1.12.3", "octagonal-wheels": "^0.1.45", "pouchdb-adapter-leveldb": "^9.0.0", "qrcode-generator": "^1.4.4", @@ -32,8 +33,6 @@ }, "devDependencies": { "@chialab/esbuild-plugin-worker": "^0.19.0", - "@eslint/compat": "^2.0.2", - "@eslint/eslintrc": "^3.3.4", "@eslint/js": "^9.39.3", "@sveltejs/vite-plugin-svelte": "^6.2.4", "@tsconfig/svelte": "^5.0.8", @@ -55,18 +54,15 @@ "@vitest/browser": "^4.1.1", "@vitest/browser-playwright": "^4.1.1", "@vitest/coverage-v8": "^4.1.1", - "builtin-modules": "5.0.0", - "dotenv": "^17.3.1", "dotenv-cli": "^11.0.0", "esbuild": "0.25.0", "esbuild-plugin-inline-worker": "^0.1.1", "esbuild-svelte": "^0.9.4", "eslint": "^9.39.3", - "eslint-plugin-import": "^2.32.0", + "eslint-plugin-obsidianmd": "^0.3.0", "eslint-plugin-svelte": "^3.15.0", "events": "^3.3.0", - "glob": "^13.0.6", - "obsidian": "^1.12.3", + "globals": "^14.0.0", "playwright": "^1.58.2", "postcss": "^8.5.6", "postcss-load-config": "^6.0.1", @@ -87,6 +83,7 @@ "svelte-check": "^4.4.3", "svelte-preprocess": "^6.0.3", "terser": "^5.39.0", + "tinyglobby": "^0.2.15", "transform-pouch": "^2.0.0", "tslib": "^2.8.1", "tsx": "^4.21.0", @@ -1340,7 +1337,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz", "integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==", - "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -1351,7 +1347,6 @@ "version": "6.38.6", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz", "integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==", - "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -1832,27 +1827,6 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/compat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.3.tgz", - "integrity": "sha512-SjIJhGigp8hmd1YGIBwh7Ovri7Kisl42GYFjrOyHhtfYGGoLW6teYi/5p8W50KSsawUPpuLOSmsq1bD0NGQLBw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.1.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "peerDependencies": { - "eslint": "^8.40 || 9 || 10" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, "node_modules/@eslint/config-array": { "version": "0.21.2", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", @@ -1925,19 +1899,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.0.tgz", - "integrity": "sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, "node_modules/@eslint/eslintrc": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", @@ -2006,6 +1967,35 @@ "url": "https://eslint.org/donate" } }, + "node_modules/@eslint/json": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/json/-/json-0.14.0.tgz", + "integrity": "sha512-rvR/EZtvUG3p9uqrSmcDJPYSH7atmWr0RnFWN6m917MAPx82+zQgPUmDu0whPFG6XTyM0vB/hR6c1Q63OaYtCQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "@eslint/plugin-kit": "^0.4.1", + "@humanwhocodes/momoa": "^3.3.10", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/json/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/object-schema": { "version": "2.1.7", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", @@ -2103,6 +2093,16 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@humanwhocodes/momoa": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/momoa/-/momoa-3.3.10.tgz", + "integrity": "sha512-KWiFQpSAqEIyrTXko3hFNLeQvSK8zXlJQzhhxsyVn58WFRYXST99b3Nqnu+ttOtjds2Pl2grUHGpe2NzhPynuQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/@humanwhocodes/retry": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", @@ -2379,9 +2379,61 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", +<<<<<<< HEAD "dev": true, "license": "MIT", "peer": true +======= + "license": "MIT" +>>>>>>> 5772811 (chore: Package modernise, update linter) + }, + "node_modules/@microsoft/eslint-plugin-sdl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@microsoft/eslint-plugin-sdl/-/eslint-plugin-sdl-1.1.0.tgz", + "integrity": "sha512-dxdNHOemLnBhfY3eByrujX9KyLigcNtW8sU+axzWv5nLGcsSBeKW2YYyTpfPo1hV8YPOmIGnfA4fZHyKVtWqBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-plugin-n": "17.10.3", + "eslint-plugin-react": "7.37.3", + "eslint-plugin-security": "1.4.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "eslint": "^9" + } + }, + "node_modules/@microsoft/eslint-plugin-sdl/node_modules/eslint-plugin-security": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-1.4.0.tgz", + "integrity": "sha512-xlS7P2PLMXeqfhyf3NpqbvbnW04kN8M9NtmhpR3XGyOvt/vNKS7XPXT5EDbwKW9vCjWH4PpfQvgD/+JgN0VJKA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-regex": "^1.1.0" + } + }, + "node_modules/@microsoft/eslint-plugin-sdl/node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12" + } + }, + "node_modules/@microsoft/eslint-plugin-sdl/node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ret": "~0.1.10" + } }, "node_modules/@minhducsun2002/leb128": { "version": "1.0.0", @@ -3005,6 +3057,19 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.2.tgz", + "integrity": "sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", @@ -4322,7 +4387,6 @@ "version": "5.60.8", "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", - "dev": true, "license": "MIT", "dependencies": { "@types/tern": "*" @@ -4359,11 +4423,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, "license": "MIT" }, "node_modules/@types/fs-extra": { @@ -4668,7 +4742,6 @@ "version": "0.23.9", "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "*" @@ -5771,6 +5844,27 @@ "node": ">=8" } }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.findlastindex": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", @@ -5831,6 +5925,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/arraybuffer.prototype.slice": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", @@ -6220,29 +6331,16 @@ "dev": true, "license": "MIT" }, - "node_modules/builtin-modules": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-5.0.0.tgz", - "integrity": "sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" }, "engines": { @@ -6659,9 +6757,13 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", +<<<<<<< HEAD "dev": true, "license": "MIT", "peer": true +======= + "license": "MIT" +>>>>>>> 5772811 (chore: Package modernise, update linter) }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -7219,6 +7321,16 @@ "dev": true, "license": "MIT" }, + "node_modules/empathic": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz", + "integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/encoding-down": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", @@ -7267,6 +7379,20 @@ "write-stream": "~0.4.3" } }, + "node_modules/enhanced-resolve": { + "version": "5.21.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.3.tgz", + "integrity": "sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -7292,9 +7418,9 @@ } }, "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", "dev": true, "license": "MIT", "dependencies": { @@ -7380,6 +7506,34 @@ "node": ">= 0.4" } }, + "node_modules/es-iterator-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz", + "integrity": "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -7621,6 +7775,22 @@ } } }, + "node_modules/eslint-compat-utils": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", + "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", @@ -7671,6 +7841,40 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-plugin-depend": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-depend/-/eslint-plugin-depend-1.3.1.tgz", + "integrity": "sha512-1uo2rFAr9vzNrCYdp7IBZRB54LiyVxfaIso0R6/QV3t6Dax6DTbW/EV2Hktf0f4UtmGHK8UyzJWI382pwW04jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "empathic": "^2.0.0", + "module-replacements": "^2.8.0", + "semver": "^7.6.3" + } + }, + "node_modules/eslint-plugin-es-x": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", + "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/ota-meshi", + "https://opencollective.com/eslint" + ], + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.11.0", + "eslint-compat-utils": "^0.5.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": ">=8" + } + }, "node_modules/eslint-plugin-import": { "version": "2.32.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", @@ -7756,6 +7960,354 @@ "semver": "bin/semver.js" } }, + "node_modules/eslint-plugin-json-schema-validator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-json-schema-validator/-/eslint-plugin-json-schema-validator-5.1.0.tgz", + "integrity": "sha512-ZmVyxRIjm58oqe2kTuy90PpmZPrrKvOjRPXKzq8WCgRgAkidCgm5X8domL2KSfadZ3QFAmifMgGTcVNhZ5ez2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.3.0", + "ajv": "^8.0.0", + "debug": "^4.3.1", + "eslint-compat-utils": "^0.5.0", + "json-schema-migrate": "^2.0.0", + "jsonc-eslint-parser": "^2.0.0", + "minimatch": "^8.0.0", + "synckit": "^0.9.0", + "toml-eslint-parser": "^0.9.0", + "tunnel-agent": "^0.6.0", + "yaml-eslint-parser": "^1.0.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-plugin-json-schema-validator/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint-plugin-json-schema-validator/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-json-schema-validator/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/eslint-plugin-json-schema-validator/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-json-schema-validator/node_modules/minimatch": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.7.tgz", + "integrity": "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint-plugin-n": { + "version": "17.10.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.10.3.tgz", + "integrity": "sha512-ySZBfKe49nQZWR1yFaA0v/GsH6Fgp8ah6XV0WDz6CN8WO0ek4McMzb7A2xnf4DCYV43frjCygvb9f/wx7UUxRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "enhanced-resolve": "^5.17.0", + "eslint-plugin-es-x": "^7.5.0", + "get-tsconfig": "^4.7.0", + "globals": "^15.8.0", + "ignore": "^5.2.4", + "minimatch": "^9.0.5", + "semver": "^7.5.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": ">=8.23.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-n/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-n/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint-plugin-no-unsanitized": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-4.1.5.tgz", + "integrity": "sha512-MSB4hXPVFQrI8weqzs6gzl7reP2k/qSjtCoL2vUMSDejIIq9YL1ZKvq5/ORBXab/PvfBBrWO2jWviYpL+4Ghfg==", + "dev": true, + "license": "MPL-2.0", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-plugin-obsidianmd": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-obsidianmd/-/eslint-plugin-obsidianmd-0.3.0.tgz", + "integrity": "sha512-QvGDI6B2nxJBrsZKGTg31da2A/fEJNlnwN+fRZkaoPIu1QL3fYXUdpP7ThyMdr/0iTYQxifb9lt2X9cpydQx1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint/config-helpers": "^0.4.2", + "@eslint/js": "^9.30.1", + "@eslint/json": "0.14.0", + "@microsoft/eslint-plugin-sdl": "^1.1.0", + "@types/eslint": "9.6.1", + "@types/node": "20.12.12", + "@typescript-eslint/types": "^8.33.1", + "@typescript-eslint/utils": "^8.33.1", + "eslint": ">=9.0.0", + "eslint-plugin-depend": "1.3.1", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-json-schema-validator": "5.1.0", + "eslint-plugin-no-unsanitized": "^4.1.5", + "eslint-plugin-security": "2.1.1", + "globals": "14.0.0", + "obsidian": "1.12.3", + "semver": "^7.7.4", + "typescript": "5.4.5", + "typescript-eslint": "^8.35.1" + }, + "bin": { + "eslint-plugin-obsidian": "dist/lib/index.js" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@eslint/js": "^9.30.1", + "@eslint/json": "0.14.0", + "eslint": ">=9.0.0", + "obsidian": "1.8.7", + "typescript-eslint": "^8.35.1" + } + }, + "node_modules/eslint-plugin-obsidianmd/node_modules/@types/node": { + "version": "20.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", + "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/eslint-plugin-obsidianmd/node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/eslint-plugin-obsidianmd/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.3.tgz", + "integrity": "sha512-DomWuTQPFYZwF/7c9W2fkKkStqZmBd3uugfqBYLdkZ3Hii23WzZuOLUskGxB8qkSKqftxEeGL1TB2kMhrce0jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-security": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-2.1.1.tgz", + "integrity": "sha512-7cspIGj7WTfR3EhaILzAPcfCo5R9FbeWvbgsPYWivSurTBKW88VQxtP3c4aWMG9Hz/GfJlJVdXEJ3c8LqS+u2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-regex": "^2.1.1" + } + }, "node_modules/eslint-plugin-svelte": { "version": "3.16.0", "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.16.0.tgz", @@ -8177,6 +8729,23 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fast-xml-builder": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.5.tgz", @@ -9682,6 +10251,24 @@ "node": ">=8" } }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -9748,6 +10335,40 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-migrate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/json-schema-migrate/-/json-schema-migrate-2.0.0.tgz", + "integrity": "sha512-r38SVTtojDRp4eD6WsCqiE0eNDt4v1WalBXb9cyZYw9ai5cGtBwzRNWjHzJl38w6TxFkXAIA7h+fyX3tnrAFhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + } + }, + "node_modules/json-schema-migrate/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/json-schema-migrate/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -9775,6 +10396,43 @@ "json5": "lib/cli.js" } }, + "node_modules/jsonc-eslint-parser": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.2.tgz", + "integrity": "sha512-1e4qoRgnn448pRuMvKGsFFymUCquZV0mpGgOyIKNgD3JVDTsVJyRBGH/Fm0tBb8WsWGgmB1mDe6/yJMQM37DUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.5.0", + "eslint-visitor-keys": "^3.0.0", + "espree": "^9.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, + "node_modules/jsonc-eslint-parser/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -9785,6 +10443,22 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -10279,6 +10953,26 @@ "dev": true, "license": "MIT" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loose-envify/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "11.2.7", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", @@ -10501,11 +11195,17 @@ "node": ">=18.0.0" } }, + "node_modules/module-replacements": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/module-replacements/-/module-replacements-2.11.0.tgz", + "integrity": "sha512-j5sNQm3VCpQQ7nTqGeOZtoJtV3uKERgCBm9QRhmGRiXiqkf7iRFOkfxdJRZWLkqYY8PNf4cDQF/WfXUYLENrRA==", + "dev": true, + "license": "MIT" + }, "node_modules/moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "dev": true, "license": "MIT", "engines": { "node": "*" @@ -10598,6 +11298,35 @@ "node": ">= 0.4.0" } }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/node-fetch": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", @@ -10659,6 +11388,16 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -10703,6 +11442,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/object.fromentries": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", @@ -10760,7 +11515,6 @@ "version": "1.12.3", "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.12.3.tgz", "integrity": "sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw==", - "dev": true, "license": "MIT", "dependencies": { "@types/codemirror": "5.60.8", @@ -11747,6 +12501,18 @@ "node": ">=0.4.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/proxy-agent": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", @@ -11888,6 +12654,13 @@ ], "license": "MIT" }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -11984,6 +12757,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "dev": true, + "license": "MIT", + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -12015,6 +12798,16 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -12265,6 +13058,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", + "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "regexp-tree": "~0.1.1" + } + }, "node_modules/safe-regex-test": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", @@ -12819,6 +13622,45 @@ "node": ">=8" } }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", @@ -12957,9 +13799,13 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", +<<<<<<< HEAD "dev": true, "license": "MIT", "peer": true +======= + "license": "MIT" +>>>>>>> 5772811 (chore: Package modernise, update linter) }, "node_modules/sublevel-pouchdb": { "version": "9.0.0", @@ -13190,6 +14036,37 @@ } } }, + "node_modules/synckit": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.3.tgz", + "integrity": "sha512-JJoOEKTfL1urb1mDoEblhD9NhEbWmq9jHEMEnxoC4ujUaZ4itA8vKgwkFAyNClgxplLi9tsUKX+EduK0p/l7sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/tar-stream": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", @@ -13367,6 +14244,22 @@ "node": ">=8.0" } }, + "node_modules/toml-eslint-parser": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/toml-eslint-parser/-/toml-eslint-parser-0.9.3.tgz", + "integrity": "sha512-moYoCvkNUAPCxSW9jmHmRElhm4tVJpHL8ItC/+uYD0EpPSFXbck7yREz9tNdJVTSpHVod8+HoipcpbQ0oE6gsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, "node_modules/totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", @@ -13969,6 +14862,19 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "license": "0BSD" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", @@ -14093,6 +14999,274 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.3.tgz", + "integrity": "sha512-KgusgyDgG4LI8Ih/sWaCtZ06tckLAS5CvT5A4D1Q7bYVoAAyzwiZvE4BmwDHkhRVkvhRBepKeASoFzQetha7Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.59.3", + "@typescript-eslint/parser": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/utils": "8.59.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.3.tgz", + "integrity": "sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/type-utils": "8.59.3", + "@typescript-eslint/utils": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.59.3", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.3.tgz", + "integrity": "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/project-service": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.3.tgz", + "integrity": "sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.59.3", + "@typescript-eslint/types": "^8.59.3", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.3.tgz", + "integrity": "sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.3.tgz", + "integrity": "sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/type-utils": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.3.tgz", + "integrity": "sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/utils": "8.59.3", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.3.tgz", + "integrity": "sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.3.tgz", + "integrity": "sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.59.3", + "@typescript-eslint/tsconfig-utils": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.3.tgz", + "integrity": "sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.3.tgz", + "integrity": "sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.3", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/typescript-eslint/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", @@ -15009,9 +16183,13 @@ "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", +<<<<<<< HEAD "dev": true, "license": "MIT", "peer": true +======= + "license": "MIT" +>>>>>>> 5772811 (chore: Package modernise, update linter) }, "node_modules/wait-port": { "version": "1.1.0", @@ -15673,6 +16851,23 @@ "url": "https://github.com/sponsors/eemeli" } }, + "node_modules/yaml-eslint-parser": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/yaml-eslint-parser/-/yaml-eslint-parser-1.3.2.tgz", + "integrity": "sha512-odxVsHAkZYYglR30aPYRY4nUGJnoJ2y1ww2HDvZALo0BDETv9kWbi16J52eHs+PWRNmF4ub6nZqfVOeesOvntg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.0.0", + "yaml": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index bc96572..a95177f 100644 --- a/package.json +++ b/package.json @@ -61,8 +61,6 @@ "license": "MIT", "devDependencies": { "@chialab/esbuild-plugin-worker": "^0.19.0", - "@eslint/compat": "^2.0.2", - "@eslint/eslintrc": "^3.3.4", "@eslint/js": "^9.39.3", "@sveltejs/vite-plugin-svelte": "^6.2.4", "@tsconfig/svelte": "^5.0.8", @@ -84,18 +82,15 @@ "@vitest/browser": "^4.1.1", "@vitest/browser-playwright": "^4.1.1", "@vitest/coverage-v8": "^4.1.1", - "builtin-modules": "5.0.0", - "dotenv": "^17.3.1", "dotenv-cli": "^11.0.0", "esbuild": "0.25.0", "esbuild-plugin-inline-worker": "^0.1.1", "esbuild-svelte": "^0.9.4", "eslint": "^9.39.3", - "eslint-plugin-import": "^2.32.0", + "eslint-plugin-obsidianmd": "^0.3.0", "eslint-plugin-svelte": "^3.15.0", "events": "^3.3.0", - "glob": "^13.0.6", - "obsidian": "^1.12.3", + "globals": "^14.0.0", "playwright": "^1.58.2", "postcss": "^8.5.6", "postcss-load-config": "^6.0.1", @@ -116,6 +111,7 @@ "svelte-check": "^4.4.3", "svelte-preprocess": "^6.0.3", "terser": "^5.39.0", + "tinyglobby": "^0.2.15", "transform-pouch": "^2.0.0", "tslib": "^2.8.1", "tsx": "^4.21.0", @@ -136,6 +132,7 @@ "@trystero-p2p/nostr": "^0.23.0", "chokidar": "^4.0.0", "commander": "^14.0.3", + "obsidian": "^1.12.3", "diff-match-patch": "^1.0.5", "fflate": "^0.8.2", "idb": "^8.0.3", diff --git a/src/modules/features/SetupWizard/dialogs/ScanQRCode.svelte b/src/modules/features/SetupWizard/dialogs/ScanQRCode.svelte index 58ad125..57c0621 100644 --- a/src/modules/features/SetupWizard/dialogs/ScanQRCode.svelte +++ b/src/modules/features/SetupWizard/dialogs/ScanQRCode.svelte @@ -4,10 +4,10 @@ import Decision from "@/lib/src/UI/components/Decision.svelte"; import Instruction from "@/lib/src/UI/components/Instruction.svelte"; import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte"; - const TYPE_CLOSE = "close"; + const TYPE_CLOSE = "close"; type ResultType = typeof TYPE_CLOSE; type Props = { - setResult: (result: ResultType) => void; + setResult: (_result: ResultType) => void; }; const { setResult }: Props = $props(); diff --git a/versions.json b/versions.json index 3366a32..ae232e6 100644 --- a/versions.json +++ b/versions.json @@ -1,4 +1,5 @@ { + "0.25.60": "1.2.3", "1.0.1": "0.9.12", "1.0.0": "0.9.7" } diff --git a/vitest.config.p2p.ts b/vitest.config.p2p.ts index a968e9f..56f3244 100644 --- a/vitest.config.p2p.ts +++ b/vitest.config.p2p.ts @@ -2,7 +2,8 @@ import { defineConfig, mergeConfig } from "vitest/config"; import { playwright } from "@vitest/browser-playwright"; import viteConfig from "./vitest.config.common"; import path from "path"; -import dotenv from "dotenv"; +import { existsSync, readFileSync } from "node:fs"; +import { parseEnv } from "node:util"; import { grantClipboardPermissions, writeHandoffFile, readHandoffFile } from "./test/lib/commands"; // P2P test environment variables @@ -22,8 +23,9 @@ import { grantClipboardPermissions, writeHandoffFile, readHandoffFile } from "./ // General test options (also read from env): // ENABLE_DEBUGGER - Set to "true" to attach a debugger and pause before tests // ENABLE_UI - Set to "true" to open a visible browser window during tests -const defEnv = dotenv.config({ path: ".env" }).parsed; -const testEnv = dotenv.config({ path: ".test.env" }).parsed; +const loadEnvFile = (path: string) => (existsSync(path) ? parseEnv(readFileSync(path, "utf-8")) : undefined); +const defEnv = loadEnvFile(".env"); +const testEnv = loadEnvFile(".test.env"); // Merge: dotenv files < process.env (so shell-injected vars like P2P_TEST_* take precedence) const p2pEnv: Record = {}; if (process.env.P2P_TEST_ROOM_ID) p2pEnv.P2P_TEST_ROOM_ID = process.env.P2P_TEST_ROOM_ID; diff --git a/vitest.config.ts b/vitest.config.ts index 8818ee8..d5e74d4 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,10 +2,13 @@ import { defineConfig, mergeConfig } from "vitest/config"; import { playwright } from "@vitest/browser-playwright"; import viteConfig from "./vitest.config.common"; import path from "path"; -import dotenv from "dotenv"; +import { existsSync, readFileSync } from "node:fs"; +import { parseEnv } from "node:util"; import { grantClipboardPermissions, openWebPeer, closeWebPeer, acceptWebPeer } from "./test/lib/commands"; -const defEnv = dotenv.config({ path: ".env" }).parsed; -const testEnv = dotenv.config({ path: ".test.env" }).parsed; + +const loadEnvFile = (path: string) => (existsSync(path) ? parseEnv(readFileSync(path, "utf-8")) : undefined); +const defEnv = loadEnvFile(".env"); +const testEnv = loadEnvFile(".test.env"); const env = Object.assign({}, defEnv, testEnv); const debuggerEnabled = env?.ENABLE_DEBUGGER === "true"; const enableUI = env?.ENABLE_UI === "true"; From 053813bffbf2f7241176eeb0fd6810fbf6753819 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 08:02:50 +0100 Subject: [PATCH 27/73] Update for review once --- manifest.json | 2 +- src/serviceFeatures/onLayoutReady/enablei18n.ts | 13 ++++++++++++- src/serviceFeatures/useP2PReplicatorUI.ts | 3 ++- .../FileSystemAdapters/ObsidianVaultAdapter.ts | 12 +++++++++++- versions.json | 2 +- 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/manifest.json b/manifest.json index f5ae865..6f3ee49 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "id": "obsidian-livesync", "name": "Self-hosted LiveSync", "version": "0.25.60", - "minAppVersion": "1.2.3", + "minAppVersion": "1.7.2", "description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "author": "vorotamoroz", "authorUrl": "https://github.com/vrtmrz", diff --git a/src/serviceFeatures/onLayoutReady/enablei18n.ts b/src/serviceFeatures/onLayoutReady/enablei18n.ts index 4d79915..ba74491 100644 --- a/src/serviceFeatures/onLayoutReady/enablei18n.ts +++ b/src/serviceFeatures/onLayoutReady/enablei18n.ts @@ -3,11 +3,22 @@ import { createServiceFeature } from "@lib/interfaces/ServiceModule"; import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "@lib/common/rosetta"; import { $msg, setLang } from "@lib/common/i18n"; +function tryGetLanguage() { + try { + // Note: 1.8.7+ is required. but it is 18, Feb., 2025. we want to fallback on earlier versions, so we catch the error here. + // eslint-disable-next-line obsidianmd/no-unsupported-api + return getLanguage(); + } catch (e) { + console.error("Failed to get Obsidian language, defaulting to 'def'", e); + return "en"; + } +} + export const enableI18nFeature = createServiceFeature(async ({ services: { setting, API } }) => { let isChanged = false; const settings = setting.currentSettings(); if (settings.displayLanguage == "") { - const obsidianLanguage = getLanguage(); + const obsidianLanguage = tryGetLanguage(); if ( SUPPORTED_I18N_LANGS.indexOf(obsidianLanguage) !== -1 && // Check if the language is supported obsidianLanguage != settings.displayLanguage // Check if the language is different from the current setting diff --git a/src/serviceFeatures/useP2PReplicatorUI.ts b/src/serviceFeatures/useP2PReplicatorUI.ts index bfe042d..a2ea2d1 100644 --- a/src/serviceFeatures/useP2PReplicatorUI.ts +++ b/src/serviceFeatures/useP2PReplicatorUI.ts @@ -5,6 +5,7 @@ import { type UseP2PReplicatorResult } from "@/lib/src/replication/trystero/UseP import { P2PLogCollector } from "@/lib/src/replication/trystero/P2PLogCollector"; import { P2PReplicatorPaneView, VIEW_TYPE_P2P } from "@/features/P2PSync/P2PReplicator/P2PReplicatorPaneView"; import type { LiveSyncCore } from "@/main"; +import type { WorkspaceLeaf } from "@/deps"; /** * ServiceFeature: P2P Replicator lifecycle management. @@ -43,7 +44,7 @@ export function useP2PReplicatorUI( // Register view, commands and ribbon if a view factory is provided const viewType = VIEW_TYPE_P2P; - const factory = (leaf: any) => { + const factory = (leaf: WorkspaceLeaf) => { return new P2PReplicatorPaneView(leaf, core, { replicator: getReplicator(), p2pLogCollector, diff --git a/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts b/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts index a7d996c..78d3559 100644 --- a/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts +++ b/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts @@ -7,7 +7,7 @@ import type { TFile, App, TFolder } from "obsidian"; * Vault adapter implementation for Obsidian */ export class ObsidianVaultAdapter implements IVaultAdapter { - constructor(private app: App) {} + constructor(private app: App) { } async read(file: TFile): Promise { return await this.app.vault.read(file); @@ -38,10 +38,20 @@ export class ObsidianVaultAdapter implements IVaultAdapter { } async delete(file: TFile | TFolder, force = false): Promise { + // if ("trashFile" in this.app.fileManager) { + // // eslint-disable-next-line obsidianmd/no-unsupported-api + // return await this.app.fileManager.trashFile(file); + // } + //TODO: need fix return await this.app.vault.delete(file, force); } async trash(file: TFile | TFolder, force = false): Promise { + // if ("trashFile" in this.app.fileManager) { + // // eslint-disable-next-line obsidianmd/no-unsupported-api + // return await this.app.fileManager.trashFile(file); + // } + //TODO: need fix return await this.app.vault.trash(file, force); } diff --git a/versions.json b/versions.json index ae232e6..c5ab81e 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "0.25.60": "1.2.3", + "0.25.60": "1.7.2", "1.0.1": "0.9.12", "1.0.0": "0.9.7" } From 8deaf123d684755adba05b8d5c90a5d0a6272c65 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 10:15:45 +0100 Subject: [PATCH 28/73] Update eslint config to ignore file, fix some type error on LiveSyncBaseCore --- eslint.config.mjs | 4 ++++ src/LiveSyncBaseCore.ts | 18 ++++++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 0efa595..35b0d25 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -33,6 +33,10 @@ export default defineConfig([ ".prettierrc.*.mjs", ".prettierrc.mjs", "*.config.mjs", + "src/apps/**/*", + "src/lib/src/services/implements/browser/**", + "src/lib/src/services/implements/headless/**", + "src/lib/src/API", ]), ...sveltePlugin.configs["flat/base"], ...obsidianmd.configs.recommended, diff --git a/src/LiveSyncBaseCore.ts b/src/LiveSyncBaseCore.ts index f9f0427..a3945fd 100644 --- a/src/LiveSyncBaseCore.ts +++ b/src/LiveSyncBaseCore.ts @@ -1,4 +1,5 @@ import { LOG_LEVEL_INFO } from "octagonal-wheels/common/logger"; +import type PouchDB from "pouchdb-core"; import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase"; import type { HasSettings, ObsidianLiveSyncSettings, EntryDoc } from "./lib/src/common/types"; import { __$checkInstanceBinding } from "./lib/src/dev/checks"; @@ -34,12 +35,11 @@ export class LiveSyncBaseCore< TCommands extends IMinimumLiveSyncCommands = IMinimumLiveSyncCommands, > implements - LiveSyncLocalDBEnv, - LiveSyncReplicatorEnv, - LiveSyncJournalReplicatorEnv, - LiveSyncCouchDBReplicatorEnv, - HasSettings -{ + LiveSyncLocalDBEnv, + LiveSyncReplicatorEnv, + LiveSyncJournalReplicatorEnv, + LiveSyncCouchDBReplicatorEnv, + HasSettings { addOns = [] as TCommands[]; /** @@ -123,7 +123,7 @@ export class LiveSyncBaseCore< for (const module of this.modules) { if (module.constructor === constructor) return module as T; } - throw new Error(`Module ${constructor} not found or not loaded.`); + throw new Error(`Module ${constructor.name} not found or not loaded.`); } /** @@ -160,8 +160,10 @@ export class LiveSyncBaseCore< module.onBindFunction(this, this.services); __$checkInstanceBinding(module); // Check if all functions are properly bound, and log warnings if not. } else { + // module should not be never. + const moduleName = (module as unknown)?.constructor?.name ?? "unknown"; this.services.API.addLog( - `Module ${(module as any)?.constructor?.name ?? "unknown"} does not have onBindFunction, skipping binding.`, + `Module ${moduleName} does not have onBindFunction, skipping binding.`, LOG_LEVEL_INFO ); } From 95f40cc9547ba6fd51aca675521de05dc9a99676 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 03:55:11 +0100 Subject: [PATCH 29/73] (chore): removing DOM Operation --- .../features/SettingDialogue/PaneSetup.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/modules/features/SettingDialogue/PaneSetup.ts b/src/modules/features/SettingDialogue/PaneSetup.ts index 4e365c9..40cd88b 100644 --- a/src/modules/features/SettingDialogue/PaneSetup.ts +++ b/src/modules/features/SettingDialogue/PaneSetup.ts @@ -121,13 +121,18 @@ export function paneSetup( const repo = "vrtmrz/obsidian-livesync"; const topPath = $msg("obsidianLiveSyncSettingTab.linkTroubleshooting"); const rawRepoURI = `https://raw.githubusercontent.com/${repo}/main`; - this.createEl(paneEl, "div", "", (el) => { - el.createEl("a", { text: $msg("obsidianLiveSyncSettingTab.linkOpenInBrowser") }, (anchor) => { - anchor.href = `https://github.com/${repo}/blob/main${topPath}`; - anchor.target = "_blank"; - anchor.rel = "noopener"; - }); - }); + this.createEl( + paneEl, + "div", + "", + (el) => { + el.createEl("a", { text: $msg("obsidianLiveSyncSettingTab.linkOpenInBrowser") }, (anchor) => { + anchor.href = `https://github.com/${repo}/blob/main${topPath}`; + anchor.target = "_blank"; + anchor.rel = "noopener"; + }); + } + ); const troubleShootEl = this.createEl(paneEl, "div", { text: "", cls: "sls-troubleshoot-preview", From b1cadf054926a0f0b792272060f8ab68fec50207 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 03:58:08 +0100 Subject: [PATCH 30/73] prettify --- .../features/SettingDialogue/PaneSetup.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/modules/features/SettingDialogue/PaneSetup.ts b/src/modules/features/SettingDialogue/PaneSetup.ts index 40cd88b..4e365c9 100644 --- a/src/modules/features/SettingDialogue/PaneSetup.ts +++ b/src/modules/features/SettingDialogue/PaneSetup.ts @@ -121,18 +121,13 @@ export function paneSetup( const repo = "vrtmrz/obsidian-livesync"; const topPath = $msg("obsidianLiveSyncSettingTab.linkTroubleshooting"); const rawRepoURI = `https://raw.githubusercontent.com/${repo}/main`; - this.createEl( - paneEl, - "div", - "", - (el) => { - el.createEl("a", { text: $msg("obsidianLiveSyncSettingTab.linkOpenInBrowser") }, (anchor) => { - anchor.href = `https://github.com/${repo}/blob/main${topPath}`; - anchor.target = "_blank"; - anchor.rel = "noopener"; - }); - } - ); + this.createEl(paneEl, "div", "", (el) => { + el.createEl("a", { text: $msg("obsidianLiveSyncSettingTab.linkOpenInBrowser") }, (anchor) => { + anchor.href = `https://github.com/${repo}/blob/main${topPath}`; + anchor.target = "_blank"; + anchor.rel = "noopener"; + }); + }); const troubleShootEl = this.createEl(paneEl, "div", { text: "", cls: "sls-troubleshoot-preview", From a6891374a149bebb69db0bf7b526d9078d7da49d Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 04:40:32 +0100 Subject: [PATCH 31/73] chore: Package modernise, update linter --- package-lock.json | 64 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index d480a28..005969d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2379,11 +2379,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", -<<<<<<< HEAD "dev": true, - "license": "MIT", - "peer": true -======= "license": "MIT" >>>>>>> 5772811 (chore: Package modernise, update linter) }, @@ -2435,6 +2431,54 @@ "ret": "~0.1.10" } }, + "node_modules/@microsoft/eslint-plugin-sdl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@microsoft/eslint-plugin-sdl/-/eslint-plugin-sdl-1.1.0.tgz", + "integrity": "sha512-dxdNHOemLnBhfY3eByrujX9KyLigcNtW8sU+axzWv5nLGcsSBeKW2YYyTpfPo1hV8YPOmIGnfA4fZHyKVtWqBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-plugin-n": "17.10.3", + "eslint-plugin-react": "7.37.3", + "eslint-plugin-security": "1.4.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "eslint": "^9" + } + }, + "node_modules/@microsoft/eslint-plugin-sdl/node_modules/eslint-plugin-security": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-1.4.0.tgz", + "integrity": "sha512-xlS7P2PLMXeqfhyf3NpqbvbnW04kN8M9NtmhpR3XGyOvt/vNKS7XPXT5EDbwKW9vCjWH4PpfQvgD/+JgN0VJKA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-regex": "^1.1.0" + } + }, + "node_modules/@microsoft/eslint-plugin-sdl/node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12" + } + }, + "node_modules/@microsoft/eslint-plugin-sdl/node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ret": "~0.1.10" + } + }, "node_modules/@minhducsun2002/leb128": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@minhducsun2002/leb128/-/leb128-1.0.0.tgz", @@ -6757,11 +6801,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", -<<<<<<< HEAD "dev": true, - "license": "MIT", - "peer": true -======= "license": "MIT" >>>>>>> 5772811 (chore: Package modernise, update linter) }, @@ -13799,11 +13839,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", -<<<<<<< HEAD "dev": true, - "license": "MIT", - "peer": true -======= "license": "MIT" >>>>>>> 5772811 (chore: Package modernise, update linter) }, @@ -16183,11 +16219,7 @@ "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", -<<<<<<< HEAD "dev": true, - "license": "MIT", - "peer": true -======= "license": "MIT" >>>>>>> 5772811 (chore: Package modernise, update linter) }, From daaad9212ef0e86b8355ae3a0efbe2ed58fa062f Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 11:36:01 +0100 Subject: [PATCH 32/73] Fix package-lock --- package-lock.json | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 005969d..3a253ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -984,6 +984,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -2379,9 +2380,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", - "dev": true, "license": "MIT" ->>>>>>> 5772811 (chore: Package modernise, update linter) }, "node_modules/@microsoft/eslint-plugin-sdl": { "version": "1.1.0", @@ -4336,6 +4335,7 @@ "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "deepmerge": "^4.3.1", @@ -4874,6 +4874,7 @@ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", @@ -5078,6 +5079,7 @@ "integrity": "sha512-gjjrFC4+kPVK/fN9URDJWrssU5Gqh8Az8pKG/NSfQ2V+ky8b/y1BgBg0Ug13+hOGp5pzInonmGRPn7vOgSLgzA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@blazediff/core": "1.9.1", "@vitest/mocker": "4.1.1", @@ -5101,6 +5103,7 @@ "integrity": "sha512-dtVSBZZha2k/7P7EAXXrEAoxuIKl8Yv9f2Dk4GN/DGfmhf4DQvkvu+57okR2wq/gan1xppKjL/aBxK/kbYrbGw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/browser": "4.1.1", "@vitest/mocker": "4.1.1", @@ -5542,6 +5545,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6321,6 +6325,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -6801,9 +6806,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", - "dev": true, "license": "MIT" ->>>>>>> 5772811 (chore: Package modernise, update linter) }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -7648,6 +7651,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -7761,6 +7765,7 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -10331,6 +10336,7 @@ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -12003,6 +12009,7 @@ "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright-core": "1.58.2" }, @@ -12069,6 +12076,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -12094,6 +12102,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "lilconfig": "^3.1.1" }, @@ -13839,9 +13848,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", - "dev": true, "license": "MIT" ->>>>>>> 5772811 (chore: Package modernise, update linter) }, "node_modules/sublevel-pouchdb": { "version": "9.0.0", @@ -13910,6 +13917,7 @@ "integrity": "sha512-0a/huwc8e2es+7KFi70esqsReRfRbrT8h1cJSY/+z1lF0yKM6TT+//HYu28Yxstr50H7ifaqZRDGd0KuKDxP7w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -14251,6 +14259,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -14384,6 +14393,7 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -15027,6 +15037,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15444,6 +15455,7 @@ "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -16080,6 +16092,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -16113,6 +16126,7 @@ "integrity": "sha512-yF+o4POL41rpAzj5KVILUxm1GCjKnELvaqmU9TLLUbMfDzuN0UpUR9uaDs+mCtjPe+uYPksXDRLQGGPvj1cTmA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.1.1", "@vitest/mocker": "4.1.1", @@ -16219,9 +16233,7 @@ "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", - "dev": true, "license": "MIT" ->>>>>>> 5772811 (chore: Package modernise, update linter) }, "node_modules/wait-port": { "version": "1.1.0", From de2397dc3f9da454d0f6734631f13ced61d310b7 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 11:09:04 +0000 Subject: [PATCH 33/73] Adding a rough DI --- src/main.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main.ts b/src/main.ts index a07ce40..63242b0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,6 @@ -import { Notice, Plugin, type App, type PluginManifest } from "./deps"; - +import { getLanguage, Notice, Plugin, type App, type PluginManifest } from "./deps"; +import { setGetLanguage } from "./lib/src/common/coreEnvFunctions.ts"; +setGetLanguage(getLanguage); import { LiveSyncCommands } from "./features/LiveSyncCommands.ts"; import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts"; import { ConfigSync } from "./features/ConfigSync/CmdConfigSync.ts"; From 6a9bba702c3f35d39f757fdee9c3840669cb69e6 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 11:12:40 +0000 Subject: [PATCH 34/73] chore: ran prettier --- src/LiveSyncBaseCore.ts | 11 +++--- .../cli/commands/daemonCommand.unit.spec.ts | 12 +++---- src/apps/cli/commands/runCommand.ts | 34 ++++++++++++------- src/apps/cli/commands/types.ts | 11 +++++- src/apps/cli/main.ts | 5 +-- .../managers/CLIStorageEventManagerAdapter.ts | 6 +++- ...CLIStorageEventManagerAdapter.unit.spec.ts | 5 +-- .../cli/serviceModules/CLIServiceModules.ts | 22 +++++++----- src/apps/cli/serviceModules/IgnoreRules.ts | 6 ++-- .../serviceModules/IgnoreRules.unit.spec.ts | 5 +-- src/apps/cli/vite.config.ts | 3 +- .../ObsidianVaultAdapter.ts | 2 +- 12 files changed, 73 insertions(+), 49 deletions(-) diff --git a/src/LiveSyncBaseCore.ts b/src/LiveSyncBaseCore.ts index a3945fd..9a49451 100644 --- a/src/LiveSyncBaseCore.ts +++ b/src/LiveSyncBaseCore.ts @@ -35,11 +35,12 @@ export class LiveSyncBaseCore< TCommands extends IMinimumLiveSyncCommands = IMinimumLiveSyncCommands, > implements - LiveSyncLocalDBEnv, - LiveSyncReplicatorEnv, - LiveSyncJournalReplicatorEnv, - LiveSyncCouchDBReplicatorEnv, - HasSettings { + LiveSyncLocalDBEnv, + LiveSyncReplicatorEnv, + LiveSyncJournalReplicatorEnv, + LiveSyncCouchDBReplicatorEnv, + HasSettings +{ addOns = [] as TCommands[]; /** diff --git a/src/apps/cli/commands/daemonCommand.unit.spec.ts b/src/apps/cli/commands/daemonCommand.unit.spec.ts index 1adb967..2e2a341 100644 --- a/src/apps/cli/commands/daemonCommand.unit.spec.ts +++ b/src/apps/cli/commands/daemonCommand.unit.spec.ts @@ -257,12 +257,12 @@ describe("daemon command", () => { // failure 1: 30000*2=60000, failure 2: 30000*4=120000, // failure 3: 30000*8=240000, failure 4: 30000*16=480000→capped, 5→cap, 6→cap const expectedIntervals = [ - baseMs * 2, // after failure 1: 60000 - baseMs * 4, // after failure 2: 120000 - baseMs * 8, // after failure 3: 240000 - 300_000, // after failure 4 (would be 480000, capped) - 300_000, // after failure 5 (cap) - 300_000, // after failure 6 (cap) + baseMs * 2, // after failure 1: 60000 + baseMs * 4, // after failure 2: 120000 + baseMs * 8, // after failure 3: 240000 + 300_000, // after failure 4 (would be 480000, capped) + 300_000, // after failure 5 (cap) + 300_000, // after failure 6 (cap) ]; for (const expected of expectedIntervals) { diff --git a/src/apps/cli/commands/runCommand.ts b/src/apps/cli/commands/runCommand.ts index c90fa94..9f7df02 100644 --- a/src/apps/cli/commands/runCommand.ts +++ b/src/apps/cli/commands/runCommand.ts @@ -43,10 +43,13 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext // 3. Re-enable sync. const restoreSyncSettings = async () => { - await core.services.setting.applyPartial({ - ...context.originalSyncSettings, - suspendFileWatching: false, - }, true); + await core.services.setting.applyPartial( + { + ...context.originalSyncSettings, + suspendFileWatching: false, + }, + true + ); // applySettings fires the full lifecycle: onSuspending → onResumed. // ModuleReplicatorCouchDB starts continuous replication on onResumed // via fireAndForget. @@ -54,10 +57,13 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext // Lifecycle events (onSuspending) may re-enable suspension flags. // Clear them explicitly after the lifecycle completes. applyPartial // with true is a direct store write — it does not re-trigger lifecycle. - await core.services.setting.applyPartial({ - suspendFileWatching: false, - suspendParseReplicationResult: false, - }, true); + await core.services.setting.applyPartial( + { + suspendFileWatching: false, + suspendParseReplicationResult: false, + }, + true + ); }; if (options.interval) { log(`Polling mode: syncing every ${options.interval}s`); @@ -80,7 +86,9 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext currentIntervalMs = Math.min(baseIntervalMs * Math.pow(2, consecutiveFailures), maxIntervalMs); console.error(`[Daemon] Poll error (${consecutiveFailures} consecutive):`, err); if (consecutiveFailures >= 5) { - console.error(`[Daemon] Warning: ${consecutiveFailures} consecutive failures, backing off to ${Math.round(currentIntervalMs / 1000)}s`); + console.error( + `[Daemon] Warning: ${consecutiveFailures} consecutive failures, backing off to ${Math.round(currentIntervalMs / 1000)}s` + ); } } pollTimer = setTimeout(poll, currentIntervalMs); @@ -99,9 +107,11 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext log("LiveSync active"); const currentSettings = core.services.setting.currentSettings(); if (!currentSettings.liveSync && !currentSettings.syncOnStart) { - console.error("[Daemon] Warning: liveSync and syncOnStart are both disabled in settings. " + - "No sync will occur. Set liveSync=true in your settings file for continuous sync, " + - "or use --interval for polling mode."); + console.error( + "[Daemon] Warning: liveSync and syncOnStart are both disabled in settings. " + + "No sync will occur. Set liveSync=true in your settings file for continuous sync, " + + "or use --interval for polling mode." + ); } } diff --git a/src/apps/cli/commands/types.ts b/src/apps/cli/commands/types.ts index ca01152..d6ccee5 100644 --- a/src/apps/cli/commands/types.ts +++ b/src/apps/cli/commands/types.ts @@ -37,7 +37,16 @@ export interface CLICommandContext { databasePath: string; core: LiveSyncBaseCore; settingsPath: string; - originalSyncSettings: Pick; + originalSyncSettings: Pick< + ObsidianLiveSyncSettings, + | "liveSync" + | "syncOnStart" + | "periodicReplication" + | "syncOnSave" + | "syncOnEditorSave" + | "syncOnFileOpen" + | "syncAfterMerge" + >; } export const VALID_COMMANDS = new Set([ diff --git a/src/apps/cli/main.ts b/src/apps/cli/main.ts index 535d137..7067c70 100644 --- a/src/apps/cli/main.ts +++ b/src/apps/cli/main.ts @@ -280,16 +280,13 @@ export async function main() { // chokidar's ignored option is populated when beginWatch() fires during onLoad(). const watchEnabled = options.command === "daemon"; const vaultPath = - options.command === "mirror" && options.commandArgs[0] - ? path.resolve(options.commandArgs[0]) - : databasePath; + options.command === "mirror" && options.commandArgs[0] ? path.resolve(options.commandArgs[0]) : databasePath; let ignoreRules: IgnoreRules | undefined; if (options.command === "daemon" || options.command === "mirror") { ignoreRules = new IgnoreRules(vaultPath); await ignoreRules.load(); } - // Create service context and hub const context = new NodeServiceContext(databasePath); const serviceHubInstance = new NodeServiceHub(databasePath, context); diff --git a/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts b/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts index 9abc5fd..c2f11e1 100644 --- a/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts +++ b/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts @@ -97,7 +97,11 @@ class CLIConverterAdapter implements IStorageEventConverterAdapter { class CLIWatchAdapter implements IStorageEventWatchAdapter { private _watcher: FSWatcher | undefined; - constructor(private basePath: string, private ignoreRules?: IgnoreRules, private watchEnabled: boolean = false) {} + constructor( + private basePath: string, + private ignoreRules?: IgnoreRules, + private watchEnabled: boolean = false + ) {} private _toNodeFile(filePath: string, stats: Stats | undefined): NodeFile { return { diff --git a/src/apps/cli/managers/CLIStorageEventManagerAdapter.unit.spec.ts b/src/apps/cli/managers/CLIStorageEventManagerAdapter.unit.spec.ts index edfb222..5af69a9 100644 --- a/src/apps/cli/managers/CLIStorageEventManagerAdapter.unit.spec.ts +++ b/src/apps/cli/managers/CLIStorageEventManagerAdapter.unit.spec.ts @@ -60,10 +60,7 @@ describe("CLIStorageEventManagerAdapter", () => { await adapter.watch.beginWatch(handlers); expect(chokidar.watch).toHaveBeenCalledTimes(1); - expect(chokidar.watch).toHaveBeenCalledWith( - "/base", - expect.objectContaining({ ignoreInitial: true }) - ); + expect(chokidar.watch).toHaveBeenCalledWith("/base", expect.objectContaining({ ignoreInitial: true })); }); it("add event produces NodeFile with correct relative path via onCreate", async () => { diff --git a/src/apps/cli/serviceModules/CLIServiceModules.ts b/src/apps/cli/serviceModules/CLIServiceModules.ts index 6c4cce5..50d185a 100644 --- a/src/apps/cli/serviceModules/CLIServiceModules.ts +++ b/src/apps/cli/serviceModules/CLIServiceModules.ts @@ -25,7 +25,7 @@ export function initialiseServiceModulesCLI( core: LiveSyncBaseCore, services: InjectableServiceHub, ignoreRules?: IgnoreRules, - watchEnabled: boolean = false, + watchEnabled: boolean = false ): ServiceModules { const storageAccessManager = new StorageAccessManager(); @@ -39,13 +39,19 @@ export function initialiseServiceModulesCLI( }); // CLI-specific storage event manager - const storageEventManager = new StorageEventManagerCLI(basePath, core, { - fileProcessing: services.fileProcessing, - setting: services.setting, - vaultService: services.vault, - storageAccessManager: storageAccessManager, - APIService: services.API, - }, ignoreRules, watchEnabled); + const storageEventManager = new StorageEventManagerCLI( + basePath, + core, + { + fileProcessing: services.fileProcessing, + setting: services.setting, + vaultService: services.vault, + storageAccessManager: storageAccessManager, + APIService: services.API, + }, + ignoreRules, + watchEnabled + ); // Close the file watcher during graceful shutdown so the process can exit cleanly. services.appLifecycle.onUnload.addHandler(async () => { diff --git a/src/apps/cli/serviceModules/IgnoreRules.ts b/src/apps/cli/serviceModules/IgnoreRules.ts index 9764fd2..0b7fc41 100644 --- a/src/apps/cli/serviceModules/IgnoreRules.ts +++ b/src/apps/cli/serviceModules/IgnoreRules.ts @@ -55,7 +55,9 @@ export class IgnoreRules { continue; } if (trimmed.startsWith("import:")) { - console.error(`[IgnoreRules] Warning: unrecognised directive '${trimmed}' — only 'import: .gitignore' is supported`); + console.error( + `[IgnoreRules] Warning: unrecognised directive '${trimmed}' — only 'import: .gitignore' is supported` + ); continue; } this._addPattern(trimmed); @@ -105,7 +107,7 @@ export class IgnoreRules { if (raw.startsWith("!")) { throw new Error( `[IgnoreRules] Negation pattern '${raw}' is not supported. ` + - `Remove it from .livesync/ignore or use a separate include/exclude file.` + `Remove it from .livesync/ignore or use a separate include/exclude file.` ); } this.patterns.push(this._normalisePattern(raw)); diff --git a/src/apps/cli/serviceModules/IgnoreRules.unit.spec.ts b/src/apps/cli/serviceModules/IgnoreRules.unit.spec.ts index 59bfb12..4f6a606 100644 --- a/src/apps/cli/serviceModules/IgnoreRules.unit.spec.ts +++ b/src/apps/cli/serviceModules/IgnoreRules.unit.spec.ts @@ -122,10 +122,7 @@ describe("IgnoreRules", () => { describe("load() with comments and blank lines", () => { it("skips # comment lines and blank lines", async () => { const vaultPath = await createVault(); - await writeIgnoreFile( - vaultPath, - "# This is a comment\n\n \n*.tmp\n# another comment\nbuild/\n" - ); + await writeIgnoreFile(vaultPath, "# This is a comment\n\n \n*.tmp\n# another comment\nbuild/\n"); const rules = new IgnoreRules(vaultPath); await rules.load(); expect(rules.shouldIgnore("scratch.tmp")).toBe(true); diff --git a/src/apps/cli/vite.config.ts b/src/apps/cli/vite.config.ts index 74c4ba6..11104cd 100644 --- a/src/apps/cli/vite.config.ts +++ b/src/apps/cli/vite.config.ts @@ -47,7 +47,8 @@ function injectBanner(): import("vite").Plugin { // Insert after the shebang line if present, otherwise at the top. if (chunk.code.startsWith("#!")) { const newline = chunk.code.indexOf("\n"); - chunk.code = chunk.code.slice(0, newline + 1) + fileReaderPolyfillBanner + chunk.code.slice(newline + 1); + chunk.code = + chunk.code.slice(0, newline + 1) + fileReaderPolyfillBanner + chunk.code.slice(newline + 1); } else { chunk.code = fileReaderPolyfillBanner + chunk.code; } diff --git a/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts b/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts index 78d3559..bb89774 100644 --- a/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts +++ b/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts @@ -7,7 +7,7 @@ import type { TFile, App, TFolder } from "obsidian"; * Vault adapter implementation for Obsidian */ export class ObsidianVaultAdapter implements IVaultAdapter { - constructor(private app: App) { } + constructor(private app: App) {} async read(file: TFile): Promise { return await this.app.vault.read(file); From 06e1f4aa4a5376b7c1172d8cacffc74339d2944b Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 14:25:49 +0100 Subject: [PATCH 35/73] Update subrepo pointer --- src/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib b/src/lib index c74a8b6..f6a6c2d 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit c74a8b60ff8c14f6edd2aa20fee2d7fe1cd0793f +Subproject commit f6a6c2dff0e2865317021d5d155f80974e3ec15d From a1859f5d2e1a86fc54de2d3796ae40238bce88f3 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 13 May 2026 14:44:38 +0100 Subject: [PATCH 36/73] bump --- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- updates.md | 33 ++++++++++++++++++++++++++++++++- versions.json | 1 + 5 files changed, 37 insertions(+), 5 deletions(-) diff --git a/manifest.json b/manifest.json index 6f3ee49..7442588 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.60", + "version": "0.25.61", "minAppVersion": "1.7.2", "description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "author": "vorotamoroz", diff --git a/package-lock.json b/package-lock.json index f15ed50..735d535 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.60", + "version": "0.25.61", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.60", + "version": "0.25.61", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index a95177f..4a09e31 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.60", + "version": "0.25.61", "description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "main": "main.js", "type": "module", diff --git a/updates.md b/updates.md index 53a435b..35b4e06 100644 --- a/updates.md +++ b/updates.md @@ -3,7 +3,15 @@ Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025) The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). Because 0.25 got a lot of updates, thankfully, compatibility is kept and we do not need breaking changes! In other words, when get enough stabled. The next version will be v1.0.0. Even though it my hope. -## Unreleased +## 0.25.61 + +13th May, 2026 + +Reviews have started on the Obsidian Community, haven't they? It was quite a struggle, what with having to fix the outdated ESLint. +I am a bit nervous, but it is far better than just plodding along aimlessly, so let us get on with it. If you spot any issues, please let me know straight away. + +From now on, I am avoiding committing directly to the main branch. This is because you lots have all been sending so much PRs. I wanted to keep things harmonious. +That said, I am still not used to rebasing, so there are some parts where the commit history is a right mess. I will work on improving that. ### Improved @@ -14,6 +22,29 @@ This P2P synchronisation is not compatible with previous versions in terms of co ### Fixed - No longer baffling errors occur when setting-update is triggered during the early stage of initialisation. +- Network error notice pop-ups are now suppressed when 'NetworkWarningStyle' is set to 'Hidden'. (Thank you so much @SeleiXi!) + +### New features + +- Diff navigation buttons have been added to the diff view, making it easier to move between differences. (Thank you so much @SeleiXi! #871) + +### Translations + +- Chinese (Simplified) translations for settings and the Setup Wizard have been added. (Thank you so much @zombiek731!) +- Common UI controls and signal words are now localised into Chinese (Simplified). (Thank you so much @zombiek731!) +- i18n runtime behaviour and locale coverage have been improved. (Thank you so much @52sanmao!) + +### CLI + +#### New features + +- Daemon synchronisation is now supported. (Thank you so much @andrewleech! #843) +- `HeadlessConfirm` has been implemented with sensible defaults, enabling unattended operation in headless environments. (Thank you so much @andrewleech!) +- The CLI onboarding experience has been improved. (Thank you so much @OriBoharon! #872) + +#### Fixed + +- Sub-millisecond CLI mtimes are now truncated to prevent mobile crash. (Thank you so much @brian-spackman! #893) ## 0.25.60 diff --git a/versions.json b/versions.json index c5ab81e..fa2aaa6 100644 --- a/versions.json +++ b/versions.json @@ -1,4 +1,5 @@ { + "0.25.61": "1.7.2", "0.25.60": "1.7.2", "1.0.1": "0.9.12", "1.0.0": "0.9.7" From 437e7c0d9c18375ac2b1c7789e5564bad18ffbaf Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 14 May 2026 09:32:34 +0000 Subject: [PATCH 37/73] Fixed an issue where a connection could not be established when attempting to connect to a brand-new remote database without going through the set-up wizard or configuration checking --- src/lib | 2 +- updates.md | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lib b/src/lib index f6a6c2d..ed4502e 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit f6a6c2dff0e2865317021d5d155f80974e3ec15d +Subproject commit ed4502e0035bfee88eca5f311d09ffc239ab9734 diff --git a/updates.md b/updates.md index 35b4e06..621065b 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,14 @@ Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025) The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). Because 0.25 got a lot of updates, thankfully, compatibility is kept and we do not need breaking changes! In other words, when get enough stabled. The next version will be v1.0.0. Even though it my hope. +## 0.25.62 + +14th May, 2026 + +### Fixed + +- Fixed an issue where a connection could not be established when attempting to connect to a brand-new remote database without going through the set-up wizard or configuration checking (#660). + ## 0.25.61 13th May, 2026 From f1fe48c1ee201ffeaa143bdb8db7d68572e92b32 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 14 May 2026 09:35:53 +0000 Subject: [PATCH 38/73] add version-bump --- version-bump.mjs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 version-bump.mjs diff --git a/version-bump.mjs b/version-bump.mjs new file mode 100644 index 0000000..0b975e4 --- /dev/null +++ b/version-bump.mjs @@ -0,0 +1,17 @@ +import { readFileSync, writeFileSync } from "fs"; + +const targetVersion = process.env.npm_package_version; + +// read minAppVersion from manifest.json and bump version to target version +const manifest = JSON.parse(readFileSync("manifest.json", "utf8")); +const { minAppVersion } = manifest; +manifest.version = targetVersion; +writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); + +// update versions.json with target version and minAppVersion from manifest.json +// but only if the target version is not already in versions.json +const versions = JSON.parse(readFileSync('versions.json', 'utf8')); +if (!Object.values(versions).includes(minAppVersion)) { + versions[targetVersion] = minAppVersion; + writeFileSync('versions.json', JSON.stringify(versions, null, '\t')); +} \ No newline at end of file From 75b44b1636133daf2d217cec6a2e7ac8e202ebda Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 14 May 2026 09:40:12 +0000 Subject: [PATCH 39/73] bump --- manifest.json | 18 +++++++++--------- package-lock.json | 40 ++++++++++++++++++++++++++++------------ package.json | 5 +++-- 3 files changed, 40 insertions(+), 23 deletions(-) diff --git a/manifest.json b/manifest.json index 7442588..dded2c4 100644 --- a/manifest.json +++ b/manifest.json @@ -1,10 +1,10 @@ { - "id": "obsidian-livesync", - "name": "Self-hosted LiveSync", - "version": "0.25.61", - "minAppVersion": "1.7.2", - "description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", - "author": "vorotamoroz", - "authorUrl": "https://github.com/vrtmrz", - "isDesktopOnly": false -} + "id": "obsidian-livesync", + "name": "Self-hosted LiveSync", + "version": "0.25.62", + "minAppVersion": "1.7.2", + "description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", + "author": "vorotamoroz", + "authorUrl": "https://github.com/vrtmrz", + "isDesktopOnly": false +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 735d535..4349282 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.61", + "version": "0.25.62", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.61", + "version": "0.25.62", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", @@ -6211,9 +6211,9 @@ } }, "node_modules/basic-ftp": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.0.tgz", - "integrity": "sha512-5K9eNNn7ywHPsYnFwjKgYH8Hf8B5emh7JKcPaVjjrMJFQQwGpwowEnZNEtHs7DfR7hCZsmaK3VA4HUK0YarT+w==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", + "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", "dev": true, "license": "MIT", "engines": { @@ -8744,9 +8744,9 @@ "license": "BSD-3-Clause" }, "node_modules/fast-xml-builder": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.5.tgz", - "integrity": "sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", + "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", "funding": [ { "type": "github", @@ -8755,7 +8755,8 @@ ], "license": "MIT", "dependencies": { - "path-expression-matcher": "^1.1.3" + "path-expression-matcher": "^1.5.0", + "xml-naming": "^0.1.0" } }, "node_modules/fast-xml-parser": { @@ -9668,9 +9669,9 @@ "license": "MIT" }, "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", "dev": true, "license": "MIT", "engines": { @@ -16798,6 +16799,21 @@ } } }, + "node_modules/xml-naming": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", + "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 4a09e31..4bbb2c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.61", + "version": "0.25.62", "description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "main": "main.js", "type": "module", @@ -54,7 +54,8 @@ "test:docker-all:start": "npm run test:docker-all:up && sleep 5 && npm run test:docker-all:init", "test:docker-all:stop": "npm run test:docker-all:down", "test:full": "npm run test:docker-all:start && vitest run --coverage && npm run test:docker-all:stop", - "test:p2p": "bash test/suitep2p/run-p2p-tests.sh" + "test:p2p": "bash test/suitep2p/run-p2p-tests.sh", + "version": "node version-bump.mjs && git add manifest.json versions.json" }, "keywords": [], "author": "vorotamoroz", From 02673a1631da0c4abdd9ccfa6aed7ac200b653db Mon Sep 17 00:00:00 2001 From: Nikolay Sokolov Date: Thu, 14 May 2026 23:17:37 -0700 Subject: [PATCH 40/73] bugfix: Add package-lock.json into docker build --- src/apps/cli/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/cli/Dockerfile b/src/apps/cli/Dockerfile index 7e85bdb..beed3b5 100644 --- a/src/apps/cli/Dockerfile +++ b/src/apps/cli/Dockerfile @@ -60,7 +60,7 @@ RUN apt-get update \ WORKDIR /build # Install workspace dependencies first (layer-cache friendly) -COPY package.json ./ +COPY package.json package-lock.json ./ RUN npm install # Copy the full source tree and build the CLI bundle From d5e2f57781d0ed7e91cf89b11a57db430c426ac8 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Fri, 15 May 2026 10:18:53 +0100 Subject: [PATCH 41/73] Fixed: fixed P2P bugs and and implement new UI --- docs/p2p_sync_updates_2026.md | 42 ++ manifest.json | 18 +- .../P2PReplicator/P2POpenReplicationModal.ts | 69 +++ .../P2POpenReplicationPane.svelte | 288 +++++++++ .../P2PSync/P2PReplicator/P2PReplicationUI.ts | 73 +++ .../P2PReplicator/P2PServerStatusCard.svelte | 201 +++++++ .../P2PReplicator/P2PServerStatusPane.svelte | 558 ++++++++++++++++++ .../P2PReplicator/P2PServerStatusPaneView.ts | 42 ++ src/lib | 2 +- src/main.ts | 8 +- src/modules/core/ModuleReplicator.ts | 1 + .../SetupWizard/dialogs/SetupRemoteP2P.svelte | 22 +- src/modules/services/ObsidianAPIService.ts | 14 + src/serviceFeatures/useP2PReplicatorUI.ts | 69 ++- updates.md | 12 + 15 files changed, 1388 insertions(+), 31 deletions(-) create mode 100644 docs/p2p_sync_updates_2026.md create mode 100644 src/features/P2PSync/P2PReplicator/P2POpenReplicationModal.ts create mode 100644 src/features/P2PSync/P2PReplicator/P2POpenReplicationPane.svelte create mode 100644 src/features/P2PSync/P2PReplicator/P2PReplicationUI.ts create mode 100644 src/features/P2PSync/P2PReplicator/P2PServerStatusCard.svelte create mode 100644 src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte create mode 100644 src/features/P2PSync/P2PReplicator/P2PServerStatusPaneView.ts diff --git a/docs/p2p_sync_updates_2026.md b/docs/p2p_sync_updates_2026.md new file mode 100644 index 0000000..5a8f9e4 --- /dev/null +++ b/docs/p2p_sync_updates_2026.md @@ -0,0 +1,42 @@ +# User Guide: Peer-to-Peer Synchronisation (2026 Edition) + +Peer-to-Peer (P2P) synchronisation has evolved significantly. This guide covers the essential setup and the new features introduced in the 2026 updates. + +## 1. Core Concept: Server-less Freedom +P2P synchronisation allows your devices to talk directly to each other using WebRTC. A central server is not required for data storage, ensuring maximum privacy and "freedom." + +## 2. Setting Up via P2P Status Pane +You no longer need to navigate through complex menus. Simply open the **P2P Server Status** (via the ribbon icon or command palette) and click the **⚙ (Cog)** icon. + +This opens the **P2P Setup** dialogue where you can configure the essentials: +- **Room ID:** A unique identifier for your synchronisation group. +- **Password:** Your encryption key. Ensure all your devices use the exact same password. +- **Device Name:** A recognisable name for the current device (e.g., `iphone-16`). + +Once you have saved the settings, return to the **P2P Status Pane** and click the **Connect** button to join the network. + +*Tip: You can also toggle **Auto Connect** in the setup dialogue to automatically join the network whenever Obsidian starts.* + +## 3. Real-time Control +The status pane in the right sidebar provides granular control over your synchronisation: + +- **Signalling Status:** Shows if you are connected to the relay (🟢 Online). +- **Live-push (Broadcast):** Toggle "Broadcast changes" to notify other peers whenever you make an edit. +- **Watch:** Enable "Watch" on specific peers to automatically pull changes when they broadcast. This creates a "LiveSync-like" experience. + +## 4. Enhanced Replication Dialogue (Bidirectional Sync) +If you want to synchronise manually, click the **🔄 (Replicate)** button next to a peer in the device list. This opens the **Replication Dialogue**. + +Inside the dialogue, you can still see the **Server Status** at the top, so you will know if you are still connected while performing manual synchronisations. + +When you trigger a synchronisation this way, the system now performs a **Bidirectional Synchronisation**: +1. **Pull:** It first fetches changes from the peer. +2. **Push:** If the pull is successful, it immediately pushes your local changes to that peer. + +This "one-click" approach ensures both devices are perfectly in synchronisation without manual back-and-forth. + +## 5. Technical Improvements in 2026 +- **Decoupled Architecture:** The UI is now strictly separated from the core logic, making the plugin more stable across different platforms (Mobile, Desktop, and Web). +- **Svelte 5 UI:** The interface has been rebuilt for better responsiveness and clearer status indicators. +- **Security:** All data remains end-to-end encrypted. Even the signalling relay never sees your actual notes. + diff --git a/manifest.json b/manifest.json index dded2c4..66379e4 100644 --- a/manifest.json +++ b/manifest.json @@ -1,10 +1,10 @@ { - "id": "obsidian-livesync", - "name": "Self-hosted LiveSync", - "version": "0.25.62", - "minAppVersion": "1.7.2", - "description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", - "author": "vorotamoroz", - "authorUrl": "https://github.com/vrtmrz", - "isDesktopOnly": false -} \ No newline at end of file + "id": "obsidian-livesync", + "name": "Self-hosted LiveSync", + "version": "0.25.62", + "minAppVersion": "1.7.2", + "description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", + "author": "vorotamoroz", + "authorUrl": "https://github.com/vrtmrz", + "isDesktopOnly": false +} diff --git a/src/features/P2PSync/P2PReplicator/P2POpenReplicationModal.ts b/src/features/P2PSync/P2PReplicator/P2POpenReplicationModal.ts new file mode 100644 index 0000000..8be0ab3 --- /dev/null +++ b/src/features/P2PSync/P2PReplicator/P2POpenReplicationModal.ts @@ -0,0 +1,69 @@ +import { App, Modal } from "@/deps.ts"; +import P2POpenReplicationPane from "./P2POpenReplicationPane.svelte"; +import { mount, unmount } from "svelte"; +import type { LiveSyncTrysteroReplicator } from "@/lib/src/replication/trystero/LiveSyncTrysteroReplicator"; + +export type P2POpenReplicationModalCallback = { + onSync: (peerId: string) => Promise; + onSyncAndClose: (peerId: string) => Promise; +}; + +export class P2POpenReplicationModal extends Modal { + liveSyncReplicator: LiveSyncTrysteroReplicator; + callback?: P2POpenReplicationModalCallback; + component?: ReturnType; + showResult: boolean; + + constructor( + app: App, + liveSyncReplicator: LiveSyncTrysteroReplicator, + callback?: P2POpenReplicationModalCallback, + showResult: boolean = false + ) { + super(app); + this.liveSyncReplicator = liveSyncReplicator; + this.callback = callback; + this.showResult = showResult; + } + + async onSync(peerId: string) { + if (this.callback?.onSync) { + await this.callback.onSync(peerId); + } + } + + async onSyncAndClose(peerId: string) { + if (this.callback?.onSyncAndClose) { + await this.callback.onSyncAndClose(peerId); + } + this.close(); + } + + override onOpen() { + const { contentEl } = this; + this.titleEl.setText("P2P Replication"); + contentEl.empty(); + + if (this.component === undefined) { + this.component = mount(P2POpenReplicationPane, { + target: contentEl, + props: { + liveSyncReplicator: this.liveSyncReplicator, + onSync: (peerId: string) => this.onSync(peerId), + onSyncAndClose: (peerId: string) => this.onSyncAndClose(peerId), + onClose: () => this.close(), + showResult: this.showResult, + }, + }); + } + } + + override onClose() { + const { contentEl } = this; + contentEl.empty(); + if (this.component !== undefined) { + void unmount(this.component); + this.component = undefined; + } + } +} diff --git a/src/features/P2PSync/P2PReplicator/P2POpenReplicationPane.svelte b/src/features/P2PSync/P2PReplicator/P2POpenReplicationPane.svelte new file mode 100644 index 0000000..8247b67 --- /dev/null +++ b/src/features/P2PSync/P2PReplicator/P2POpenReplicationPane.svelte @@ -0,0 +1,288 @@ + + +
+ + +
+

Available Devices

+ {#if serverInfo && serverInfo.knownAdvertisements.length > 0} +
+ {#each serverInfo.knownAdvertisements as peer (peer.peerId)} +
+
+
{peer.name}
+
+ {peer.platform} + + {peer.peerId.slice(0, 8)} + + + {getAcceptanceStatus(peer)} + +
+
+
+ + +
+
+ {/each} +
+ {:else if serverInfo} +

No devices available. Waiting for other devices to connect...

+ {/if} +
+ + +
+ + diff --git a/src/features/P2PSync/P2PReplicator/P2PReplicationUI.ts b/src/features/P2PSync/P2PReplicator/P2PReplicationUI.ts new file mode 100644 index 0000000..28907db --- /dev/null +++ b/src/features/P2PSync/P2PReplicator/P2PReplicationUI.ts @@ -0,0 +1,73 @@ +import { App } from "@/deps.ts"; +import { Logger } from "@lib/common/logger"; +import { LOG_LEVEL_NOTICE, LOG_LEVEL_INFO } from "@lib/common/types"; +import type { LiveSyncTrysteroReplicator } from "@/lib/src/replication/trystero/LiveSyncTrysteroReplicator"; +import { P2POpenReplicationModal } from "./P2POpenReplicationModal"; + +/** + * Creates an openReplicationUI factory for Obsidian environments. + * Returns a per-replicator closure that opens the P2P Replication modal + * and performs bidirectional sync (pull then push on success). + * + * Usage: + * const factory = createOpenReplicationUI(app); + * useP2PReplicatorFeature(core, factory); + */ +export function createOpenReplicationUI( + app: App +): (replicator: LiveSyncTrysteroReplicator) => (showResult: boolean) => Promise { + return (replicator: LiveSyncTrysteroReplicator) => + (showResult: boolean): Promise => { + const logLevel = showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO; + return new Promise((resolve) => { + const modal = new P2POpenReplicationModal( + app, + replicator, + { + onSync: async (peerId: string) => { + try { + // pull (replicateFrom) first; push only on success + const pullResult = await replicator.replicateFrom(peerId, showResult); + if (pullResult?.ok) { + const pushResult = await replicator.requestSynchroniseToPeer(peerId); + resolve(pushResult?.ok ?? true); + } else { + resolve(false); + } + } catch (e) { + Logger( + `Error in bidirectional sync with ${peerId}: ${e instanceof Error ? e.message : String(e)}`, + logLevel + ); + resolve(false); + } + }, + onSyncAndClose: async (peerId: string) => { + try { + const pullResult = await replicator.replicateFrom(peerId, showResult); + if (pullResult?.ok) { + const pushResult = await replicator.requestSynchroniseToPeer(peerId); + if (pushResult?.ok ?? true) { + await replicator.close(); + resolve(true); + } else { + resolve(false); + } + } else { + resolve(false); + } + } catch (e) { + Logger( + `Error in bidirectional sync with ${peerId}: ${e instanceof Error ? e.message : String(e)}`, + logLevel + ); + resolve(false); + } + }, + }, + showResult + ); + modal.open(); + }); + }; +} diff --git a/src/features/P2PSync/P2PReplicator/P2PServerStatusCard.svelte b/src/features/P2PSync/P2PReplicator/P2PServerStatusCard.svelte new file mode 100644 index 0000000..cf64a30 --- /dev/null +++ b/src/features/P2PSync/P2PReplicator/P2PServerStatusCard.svelte @@ -0,0 +1,201 @@ + + +
+

Signalling Status

+ +
+ Connection: + + {isConnected ? "🟢 Connected" : "🔴 Disconnected"} + +
+ +
+ {#if !isConnected} + + {:else} + + {/if} +
+ + {#if serverInfo} +
+ Peer ID: + + {serverInfo.serverPeerId.slice(0, 12)}... + +
+ +
+ Devices: + {serverInfo.knownAdvertisements.length} +
+ {/if} + + {#if showBroadcastToggle} +
+ + + +
+ {/if} +
+ + \ No newline at end of file diff --git a/src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte b/src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte new file mode 100644 index 0000000..cfcc206 --- /dev/null +++ b/src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte @@ -0,0 +1,558 @@ + + +
+
+

P2P Host

+ +
+ + + +
+
+

Known Devices

+ +
+ + {#if serverInfo && serverInfo.knownAdvertisements.length > 0} +
+ {#each serverInfo.knownAdvertisements as peer (peer.peerId)} +
+
+
+ {peer.name} : ({peer.peerId.slice(0, 8)}) + {#if isCommunicating(peer.peerId)} + 📡 + {/if} +
+
+ {peer.platform} +
+
+
+ {#if isAccepted(peer)} +
+ + {getAcceptanceStatus(peer)} + + +
+
+ WATCH + +
+ {:else} +
+ + {getAcceptanceStatus(peer)} + +
+
+ PERMANENT + + +
+
+ SESSION + + +
+ {/if} + {#if !isAccepted(peer) && (peer.isAccepted !== undefined || peer.isTemporaryAccepted !== undefined)} + + {/if} +
+
+ {/each} +
+ {:else if serverInfo} +

No devices available. Waiting for other devices to connect...

+ {:else} +

Fetching status...

+ {/if} +
+
+ + \ No newline at end of file diff --git a/src/features/P2PSync/P2PReplicator/P2PServerStatusPaneView.ts b/src/features/P2PSync/P2PReplicator/P2PServerStatusPaneView.ts new file mode 100644 index 0000000..d6cc66a --- /dev/null +++ b/src/features/P2PSync/P2PReplicator/P2PServerStatusPaneView.ts @@ -0,0 +1,42 @@ +import { WorkspaceLeaf } from "@/deps.ts"; +import { mount } from "svelte"; +import { SvelteItemView } from "@/common/SvelteItemView.ts"; +import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts"; +import type { P2PPaneParams } from "@/lib/src/replication/trystero/UseP2PReplicatorResult"; +import P2PServerStatusPane from "./P2PServerStatusPane.svelte"; + +export const VIEW_TYPE_P2P_SERVER_STATUS = "p2p-server-status"; + +export class P2PServerStatusPaneView extends SvelteItemView { + core: LiveSyncBaseCore; + private _p2pResult: P2PPaneParams; + override icon = "waypoints"; + override navigation = false; + + constructor(leaf: WorkspaceLeaf, core: LiveSyncBaseCore, p2pResult: P2PPaneParams) { + super(leaf); + this.core = core; + this._p2pResult = p2pResult; + } + + override getIcon(): string { + return "waypoints"; + } + + getViewType() { + return VIEW_TYPE_P2P_SERVER_STATUS; + } + + getDisplayText() { + return "P2P Server Status"; + } + + instantiateComponent(target: HTMLElement) { + return mount(P2PServerStatusPane, { + target, + props: { + liveSyncReplicator: this._p2pResult.replicator, + }, + }); + } +} diff --git a/src/lib b/src/lib index ed4502e..2868aae 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit ed4502e0035bfee88eca5f311d09ffc239ab9734 +Subproject commit 2868aae6fdd9ca7738f4a69c88fa6d7a01842e4d diff --git a/src/main.ts b/src/main.ts index 63242b0..102d120 100644 --- a/src/main.ts +++ b/src/main.ts @@ -44,6 +44,7 @@ import { useSetupManagerHandlersFeature } from "./serviceFeatures/setupObsidian/ import { useP2PReplicatorFeature } from "@lib/replication/trystero/useP2PReplicatorFeature.ts"; import { useP2PReplicatorCommands } from "@lib/replication/trystero/useP2PReplicatorCommands.ts"; import { useP2PReplicatorUI } from "./serviceFeatures/useP2PReplicatorUI.ts"; +import { createOpenReplicationUI } from "./features/P2PSync/P2PReplicator/P2PReplicationUI.ts"; export type LiveSyncCore = LiveSyncBaseCore; export default class ObsidianLiveSyncPlugin extends Plugin { core: LiveSyncCore; @@ -176,7 +177,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin { const curriedFeature = () => featuresInitialiser(core); core.services.appLifecycle.onLayoutReady.addHandler(curriedFeature); const setupManager = core.getModule(SetupManager); - + const replicator = useP2PReplicatorFeature(core, createOpenReplicationUI(this.app)); + useP2PReplicatorCommands(core, replicator); + useP2PReplicatorUI(core, core, replicator); useRemoteConfiguration(core); useSetupProtocolFeature(core, setupManager); @@ -190,9 +193,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin { // VIEW_TYPE_P2P, // (leaf: any) => new P2PReplicatorPaneView(leaf, core, p2pReplicatorResult!), // ]); - const replicator = useP2PReplicatorFeature(core); - useP2PReplicatorCommands(core, replicator); - useP2PReplicatorUI(core, core, replicator); } ); } diff --git a/src/modules/core/ModuleReplicator.ts b/src/modules/core/ModuleReplicator.ts index 79b7b49..ddc59d0 100644 --- a/src/modules/core/ModuleReplicator.ts +++ b/src/modules/core/ModuleReplicator.ts @@ -1,3 +1,4 @@ +import type PouchDB from "pouchdb-core"; import { fireAndForget } from "octagonal-wheels/promises"; import { AbstractModule } from "../AbstractModule"; import { Logger, LOG_LEVEL_NOTICE, LOG_LEVEL_INFO } from "octagonal-wheels/common/logger"; diff --git a/src/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte b/src/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte index b07c157..64a794f 100644 --- a/src/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte +++ b/src/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte @@ -39,18 +39,20 @@ const { setResult, getInitialData }: Props = $props(); onMount(() => { + let initialData: P2PSyncSetting | undefined = undefined; if (getInitialData) { - const initialData = getInitialData(); + initialData = getInitialData(); if (initialData) { copyTo(initialData, syncSetting); } - if (context.services.config.getSmallConfig(SETTING_KEY_P2P_DEVICE_NAME)) { - syncSetting.P2P_DevicePeerName = context.services.config.getSmallConfig( - SETTING_KEY_P2P_DEVICE_NAME - ) as string; - } else { - syncSetting.P2P_DevicePeerName = ""; - } + } + const initialPeerName = (initialData?.P2P_DevicePeerName ?? "").trim(); + if (initialPeerName !== "") { + return; + } + const cachedPeerName = context.services.config.getSmallConfig(SETTING_KEY_P2P_DEVICE_NAME); + if (cachedPeerName) { + syncSetting.P2P_DevicePeerName = cachedPeerName as string; } }); function generateSetting() { @@ -100,7 +102,7 @@ const dummyPouch = new PouchDB("dummy"); const env: ReplicatorHostEnv = { settings: trialRemoteSetting, - processReplicatedDocs: async (docs: any[]) => { + processReplicatedDocs: async (_docs: any[]) => { return; }, confirm: context.services.confirm, @@ -116,7 +118,7 @@ await replicator.open(); for (let i = 0; i < 10; i++) { // await delay(1000); - await new Promise((resolve) => setTimeout(resolve, 1000)); + await new Promise((resolve) => window.setTimeout(resolve, 1000)); // Logger(`Checking known advertisements... (${i})`, LOG_LEVEL_INFO); if (replicator.knownAdvertisements.length > 0) { break; diff --git a/src/modules/services/ObsidianAPIService.ts b/src/modules/services/ObsidianAPIService.ts index f543c30..19784e4 100644 --- a/src/modules/services/ObsidianAPIService.ts +++ b/src/modules/services/ObsidianAPIService.ts @@ -38,6 +38,20 @@ export class ObsidianAPIService extends InjectableAPIService { + const rightLeaf = this.app.workspace.getRightLeaf(false); + if (rightLeaf) { + await rightLeaf.setViewState({ + type: viewType, + active: false, + }); + await this.app.workspace.revealLeaf(rightLeaf); + return; + } + + await this.showWindow(viewType); + } + private get app() { return this.context.app; } diff --git a/src/serviceFeatures/useP2PReplicatorUI.ts b/src/serviceFeatures/useP2PReplicatorUI.ts index a2ea2d1..b55a895 100644 --- a/src/serviceFeatures/useP2PReplicatorUI.ts +++ b/src/serviceFeatures/useP2PReplicatorUI.ts @@ -4,6 +4,10 @@ import type { NecessaryServices } from "@lib/interfaces/ServiceModule"; import { type UseP2PReplicatorResult } from "@/lib/src/replication/trystero/UseP2PReplicatorResult"; import { P2PLogCollector } from "@/lib/src/replication/trystero/P2PLogCollector"; import { P2PReplicatorPaneView, VIEW_TYPE_P2P } from "@/features/P2PSync/P2PReplicator/P2PReplicatorPaneView"; +import { + P2PServerStatusPaneView, + VIEW_TYPE_P2P_SERVER_STATUS, +} from "@/features/P2PSync/P2PReplicator/P2PServerStatusPaneView"; import type { LiveSyncCore } from "@/main"; import type { WorkspaceLeaf } from "@/deps"; @@ -34,6 +38,19 @@ export function useP2PReplicatorUI( core: LiveSyncCore, replicator: UseP2PReplicatorResult ) { + const api = host.services.API as { + showWindow: (type: string) => Promise; + showWindowOnRight?: (type: string) => Promise; + registerWindow: (type: string, factory: (leaf: WorkspaceLeaf) => unknown) => void; + addCommand: (command: { id: string; name: string; callback: () => void }) => unknown; + addRibbonIcon: ( + icon: string, + title: string, + callback: () => void + ) => { addClass?: (name: string) => unknown } | undefined; + getPlatform: () => string; + }; + // const env: LiveSyncTrysteroReplicatorEnv = { services: host.services as any }; const getReplicator = () => replicator.replicator; const p2pLogCollector = new P2PLogCollector(); @@ -51,26 +68,64 @@ export function useP2PReplicatorUI( storeP2PStatusLine, }); }; - const openPane = () => host.services.API.showWindow(viewType); - host.services.API.registerWindow(viewType, factory); + const statusFactory = (leaf: WorkspaceLeaf) => { + return new P2PServerStatusPaneView(leaf, core, { + replicator: getReplicator(), + p2pLogCollector, + storeP2PStatusLine, + }); + }; + const openPane = () => api.showWindow(viewType); + const openStatusPane = () => { + if (api.showWindowOnRight) { + return api.showWindowOnRight(VIEW_TYPE_P2P_SERVER_STATUS); + } + return api.showWindow(VIEW_TYPE_P2P_SERVER_STATUS); + }; + api.registerWindow(viewType, factory); + api.registerWindow(VIEW_TYPE_P2P_SERVER_STATUS, statusFactory); host.services.appLifecycle.onInitialise.addHandler(() => { eventHub.onEvent(EVENT_REQUEST_OPEN_P2P, () => { void openPane(); }); - host.services.API.addCommand({ + api.addCommand({ id: "open-p2p-replicator", - name: "P2P Sync : Open P2P Replicator", + name: "P2P Sync : Open P2P Replicator (Old UI)", callback: () => { void openPane(); }, }); - host.services.API.addRibbonIcon("waypoints", "P2P Replicator", () => { - void openPane(); - })?.addClass?.("livesync-ribbon-replicate-p2p"); + api.addCommand({ + id: "open-p2p-server-status", + name: "P2P Sync : Open P2P Server Status", + callback: () => { + void openStatusPane(); + }, + }); + // api.addRibbonIcon("waypoints", "P2P Replicator", () => { + // void openPane(); + // })?.addClass?.("livesync-ribbon-replicate-p2p"); + + api.addRibbonIcon("waypoints", "P2P Server Status", () => { + void openStatusPane(); + })?.addClass?.("livesync-ribbon-p2p-server-status"); + + return Promise.resolve(true); + }); + + host.services.appLifecycle.onLayoutReady.addHandler(() => { + if (api.getPlatform() !== "obsidian") { + return Promise.resolve(true); + } + if (api.showWindowOnRight) { + void api.showWindowOnRight(VIEW_TYPE_P2P_SERVER_STATUS); + } else { + void api.showWindow(VIEW_TYPE_P2P_SERVER_STATUS); + } return Promise.resolve(true); }); return { replicator: getReplicator(), p2pLogCollector, storeP2PStatusLine }; diff --git a/updates.md b/updates.md index 621065b..a392a7f 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,18 @@ Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025) The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). Because 0.25 got a lot of updates, thankfully, compatibility is kept and we do not need breaking changes! In other words, when get enough stabled. The next version will be v1.0.0. Even though it my hope. +## Unreleased + +15th May, 2026 + + +### Fixed +- The issue which cannot synchronise in Only-P2P mode has been fixed. + +### P2P Replication UI Improvements +- Brand-new P2P Server Status pane has been added to provide real-time visibility into your connection status and peer network. +- Now `Replicate` button or ribbon icon opens a redesigned interactive replication dialogue that performs smart bidirectional sync with a single click. + ## 0.25.62 14th May, 2026 From f0628a0d2c64b273a875269b6d7288b7a64d8601 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 16 May 2026 23:09:11 +0900 Subject: [PATCH 42/73] Improve UI --- docs/p2p_sync_updates_2026.md | 4 ++-- src/features/P2PSync/P2PReplicator/P2PServerStatusCard.svelte | 2 +- src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte | 4 ++-- src/features/P2PSync/P2PReplicator/P2PServerStatusPaneView.ts | 2 +- src/lib | 2 +- src/serviceFeatures/useP2PReplicatorUI.ts | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/p2p_sync_updates_2026.md b/docs/p2p_sync_updates_2026.md index 5a8f9e4..13acf46 100644 --- a/docs/p2p_sync_updates_2026.md +++ b/docs/p2p_sync_updates_2026.md @@ -6,11 +6,11 @@ Peer-to-Peer (P2P) synchronisation has evolved significantly. This guide covers P2P synchronisation allows your devices to talk directly to each other using WebRTC. A central server is not required for data storage, ensuring maximum privacy and "freedom." ## 2. Setting Up via P2P Status Pane -You no longer need to navigate through complex menus. Simply open the **P2P Server Status** (via the ribbon icon or command palette) and click the **⚙ (Cog)** icon. +You no longer need to navigate through complex menus. Simply open the **P2P Status** (via the ribbon icon or command palette) and click the **⚙ (Cog)** icon. This opens the **P2P Setup** dialogue where you can configure the essentials: - **Room ID:** A unique identifier for your synchronisation group. -- **Password:** Your encryption key. Ensure all your devices use the exact same password. +- **Passphrase:** Your encryption key. Ensure all your devices use the exact same passphrase. - **Device Name:** A recognisable name for the current device (e.g., `iphone-16`). Once you have saved the settings, return to the **P2P Status Pane** and click the **Connect** button to join the network. diff --git a/src/features/P2PSync/P2PReplicator/P2PServerStatusCard.svelte b/src/features/P2PSync/P2PReplicator/P2PServerStatusCard.svelte index cf64a30..980aed4 100644 --- a/src/features/P2PSync/P2PReplicator/P2PServerStatusCard.svelte +++ b/src/features/P2PSync/P2PReplicator/P2PServerStatusCard.svelte @@ -21,7 +21,7 @@ let replicatorStatus = $state(undefined); async function requestServerStatus() { - await liveSyncReplicator.requestStatus(); + await Promise.resolve(liveSyncReplicator.requestStatus()); eventHub.emitEvent(EVENT_REQUEST_STATUS); } diff --git a/src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte b/src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte index cfcc206..2952146 100644 --- a/src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte +++ b/src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte @@ -149,7 +149,7 @@
-

P2P Host

+

P2P Status

diff --git a/src/features/P2PSync/P2PReplicator/P2PServerStatusPaneView.ts b/src/features/P2PSync/P2PReplicator/P2PServerStatusPaneView.ts index d6cc66a..99d2347 100644 --- a/src/features/P2PSync/P2PReplicator/P2PServerStatusPaneView.ts +++ b/src/features/P2PSync/P2PReplicator/P2PServerStatusPaneView.ts @@ -28,7 +28,7 @@ export class P2PServerStatusPaneView extends SvelteItemView { } getDisplayText() { - return "P2P Server Status"; + return "P2P Status"; } instantiateComponent(target: HTMLElement) { diff --git a/src/lib b/src/lib index 2868aae..bf060df 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 2868aae6fdd9ca7738f4a69c88fa6d7a01842e4d +Subproject commit bf060df09183f2992829f0e8ec19bb6e389c1613 diff --git a/src/serviceFeatures/useP2PReplicatorUI.ts b/src/serviceFeatures/useP2PReplicatorUI.ts index b55a895..59ef277 100644 --- a/src/serviceFeatures/useP2PReplicatorUI.ts +++ b/src/serviceFeatures/useP2PReplicatorUI.ts @@ -100,7 +100,7 @@ export function useP2PReplicatorUI( api.addCommand({ id: "open-p2p-server-status", - name: "P2P Sync : Open P2P Server Status", + name: "P2P Sync : Open P2P Status", callback: () => { void openStatusPane(); }, @@ -110,7 +110,7 @@ export function useP2PReplicatorUI( // void openPane(); // })?.addClass?.("livesync-ribbon-replicate-p2p"); - api.addRibbonIcon("waypoints", "P2P Server Status", () => { + api.addRibbonIcon("waypoints", "P2P Status", () => { void openStatusPane(); })?.addClass?.("livesync-ribbon-p2p-server-status"); From 9a90256a8a9dca35bd2b1c627884d26c1211df15 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 16 May 2026 23:50:08 +0900 Subject: [PATCH 43/73] Enhance P2P synchronization features and UI improvements --- docs/p2p_sync_updates_2026.md | 29 +++++++---- .../P2POpenReplicationPane.svelte | 27 +++++----- .../P2PReplicator/P2PServerStatusPane.svelte | 51 +++++++++++++++++-- .../P2PReplicator/P2PServerStatusPaneView.ts | 1 + src/lib | 2 +- .../dialogs/SelectMethodNewUser.svelte | 2 + src/serviceFeatures/useP2PReplicatorUI.ts | 36 +++++++++++++ 7 files changed, 122 insertions(+), 26 deletions(-) diff --git a/docs/p2p_sync_updates_2026.md b/docs/p2p_sync_updates_2026.md index 13acf46..34dc9de 100644 --- a/docs/p2p_sync_updates_2026.md +++ b/docs/p2p_sync_updates_2026.md @@ -13,7 +13,7 @@ This opens the **P2P Setup** dialogue where you can configure the essentials: - **Passphrase:** Your encryption key. Ensure all your devices use the exact same passphrase. - **Device Name:** A recognisable name for the current device (e.g., `iphone-16`). -Once you have saved the settings, return to the **P2P Status Pane** and click the **Connect** button to join the network. +Once you have saved the settings, return to the **P2P Status Pane** and click the **Connect** button to join the network. *Tip: You can also toggle **Auto Connect** in the setup dialogue to automatically join the network whenever Obsidian starts.* @@ -23,19 +23,30 @@ The status pane in the right sidebar provides granular control over your synchro - **Signalling Status:** Shows if you are connected to the relay (🟢 Online). - **Live-push (Broadcast):** Toggle "Broadcast changes" to notify other peers whenever you make an edit. - **Watch:** Enable "Watch" on specific peers to automatically pull changes when they broadcast. This creates a "LiveSync-like" experience. +- **Sync (🔄/🔁):** Mark specific peers as **sync targets**. Peers marked here will be included when you run the **"P2P: Sync with targets"** command (see section 5). Click the button next to a peer to toggle it on (🔄, highlighted) or off (🔁). This setting is persisted in your configuration. -## 4. Enhanced Replication Dialogue (Bidirectional Sync) -If you want to synchronise manually, click the **🔄 (Replicate)** button next to a peer in the device list. This opens the **Replication Dialogue**. +## 4. Replication Dialogue +If you want to synchronise with a specific peer manually, use the **Replication** command or button. This opens the **Replication Dialogue** listing available devices. -Inside the dialogue, you can still see the **Server Status** at the top, so you will know if you are still connected while performing manual synchronisations. +Inside the dialogue, the **Server Status** card at the top confirms you are still connected while performing the sync. -When you trigger a synchronisation this way, the system now performs a **Bidirectional Synchronisation**: -1. **Pull:** It first fetches changes from the peer. -2. **Push:** If the pull is successful, it immediately pushes your local changes to that peer. +Two actions are available per peer: -This "one-click" approach ensures both devices are perfectly in synchronisation without manual back-and-forth. +- **Sync** — Starts a bidirectional synchronisation (Pull then Push) and keeps the dialogue open so you can monitor progress or sync with additional peers. +- **Start Sync & Close** — Starts the same bidirectional sync in the background and **immediately closes the dialogue**, so you can continue working without waiting. -## 5. Technical Improvements in 2026 +## 5. Syncing with Registered Targets via Command Palette + +You can now trigger a synchronisation with all your pre-registered target peers in one step, without opening any UI. + +1. Open the **Command Palette** (`Ctrl/Cmd + P`). +2. Run **"P2P: Sync with targets"**. + +This command synchronises with every peer whose **SYNC** toggle is enabled in the **Detected Peers** list. If no targets are registered, or if the P2P server is not running, the command will notify you accordingly. + +*Tip: Pair this command with a hotkey for a quick, keyboard-driven sync workflow.* + +## 6. Technical Improvements in 2026 - **Decoupled Architecture:** The UI is now strictly separated from the core logic, making the plugin more stable across different platforms (Mobile, Desktop, and Web). - **Svelte 5 UI:** The interface has been rebuilt for better responsiveness and clearer status indicators. - **Security:** All data remains end-to-end encrypted. Even the signalling relay never sees your actual notes. diff --git a/src/features/P2PSync/P2PReplicator/P2POpenReplicationPane.svelte b/src/features/P2PSync/P2PReplicator/P2POpenReplicationPane.svelte index 8247b67..c745891 100644 --- a/src/features/P2PSync/P2PReplicator/P2POpenReplicationPane.svelte +++ b/src/features/P2PSync/P2PReplicator/P2POpenReplicationPane.svelte @@ -57,16 +57,16 @@ } async function handleSyncAndClose(peerId: string) { - try { - syncingPeerId = peerId; - Logger(`Starting sync and close with ${peerId}`, logLevel); - await onSyncAndClose(peerId); - Logger(`Sync and close completed with ${peerId}`, logLevel); - } catch (e) { - Logger(`Error during sync and close: ${e instanceof Error ? e.message : String(e)}`, logLevel); - } finally { - syncingPeerId = null; - } + fireAndForget(async () => { + try { + Logger(`Starting sync with ${peerId}`, logLevel); + await onSync(peerId); + Logger(`Sync completed with ${peerId}`, logLevel); + } catch (e) { + Logger(`Error during sync: ${e instanceof Error ? e.message : String(e)}`, logLevel); + } + }); + onClose(); } async function disconnect(){ try { @@ -103,7 +103,7 @@ />
-

Available Devices

+

Available Peers

{#if serverInfo && serverInfo.knownAdvertisements.length > 0}
{#each serverInfo.knownAdvertisements as peer (peer.peerId)} @@ -133,7 +133,7 @@ disabled={syncingPeerId !== null} onclick={() => handleSyncAndClose(peer.peerId)} > - {syncingPeerId === peer.peerId ? "Syncing..." : "Sync & Close"} + Start Sync & Close
@@ -181,6 +181,7 @@ .peer-item { display: flex; justify-content: space-between; + flex-wrap: wrap; align-items: center; padding: 0.75rem; background-color: var(--background-secondary); @@ -234,9 +235,9 @@ } .peer-actions { + flex-wrap: wrap; display: flex; gap: 0.5rem; - flex-shrink: 0; } .btn { diff --git a/src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte b/src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte index 2952146..fc148f8 100644 --- a/src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte +++ b/src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte @@ -12,17 +12,32 @@ import type { P2PReplicatorStatus, P2PReplicationReport } from "@/lib/src/replication/trystero/TrysteroReplicator"; import { delay, fireAndForget } from "octagonal-wheels/promises"; import P2PServerStatusCard from "./P2PServerStatusCard.svelte"; + import { EVENT_SETTING_SAVED } from "@lib/events/coreEvents"; + import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore"; interface Props { liveSyncReplicator: LiveSyncTrysteroReplicator; + core: LiveSyncBaseCore; } - let { liveSyncReplicator }: Props = $props(); + let { liveSyncReplicator, core }: Props = $props(); let serverInfo = $state(undefined); let replicatorInfo = $state(undefined); let decidingPeerId = $state(null); let communicatingUntil = $state>({}); const COMMUNICATION_HOLD_MS = 2500; + let syncOnReplicationSetting = $state( + core.services.setting.currentSettings()?.P2P_SyncOnReplication ?? "" + ); + + function addToList(item: string, list: string): string { + const items = list.split(",").map((e) => e.trim()).filter((e) => e); + if (!items.includes(item)) items.push(item); + return items.join(","); + } + function removeFromList(item: string, list: string): string { + return list.split(",").map((e) => e.trim()).filter((e) => e && e !== item).join(","); + } function markCommunicating(peerId: string) { const expiry = Date.now() + COMMUNICATION_HOLD_MS; @@ -60,6 +75,10 @@ } }); + const unsubscribeSettings = eventHub.onEvent(EVENT_SETTING_SAVED, (settings) => { + syncOnReplicationSetting = settings?.P2P_SyncOnReplication ?? ""; + }); + fireAndForget(async () => { await delay(100); await requestServerStatus(); @@ -69,6 +88,7 @@ unsubscribe(); unsubscribeReplicatorStatus(); unsubscribeReplicatorProgress(); + unsubscribeSettings(); }; }); @@ -145,6 +165,22 @@ const isHeldCommunicating = (communicatingUntil[peerId] ?? 0) > Date.now(); return isLiveCommunicating || isHeldCommunicating; } + + function isSyncTarget(peerName: string) { + return syncOnReplicationSetting + .split(",") + .map((e) => e.trim()) + .filter((e) => e) + .includes(peerName); + } + + async function toggleSyncTarget(peer: P2PServerInfo["knownAdvertisements"][number]) { + const currentValue = core.services.setting.currentSettings()?.P2P_SyncOnReplication ?? ""; + const newValue = isSyncTarget(peer.name) + ? removeFromList(peer.name, currentValue) + : addToList(peer.name, currentValue); + await core.services.setting.applyPartial({ P2P_SyncOnReplication: newValue }, true); + }
@@ -207,8 +243,17 @@ > {isWatching(peer.peerId) ? '👁' : '👁‍🗨'} -
- {:else} +
+ SYNC + +
{:else}
{getAcceptanceStatus(peer)} diff --git a/src/features/P2PSync/P2PReplicator/P2PServerStatusPaneView.ts b/src/features/P2PSync/P2PReplicator/P2PServerStatusPaneView.ts index 99d2347..a182b8a 100644 --- a/src/features/P2PSync/P2PReplicator/P2PServerStatusPaneView.ts +++ b/src/features/P2PSync/P2PReplicator/P2PServerStatusPaneView.ts @@ -36,6 +36,7 @@ export class P2PServerStatusPaneView extends SvelteItemView { target, props: { liveSyncReplicator: this._p2pResult.replicator, + core: this.core, }, }); } diff --git a/src/lib b/src/lib index bf060df..4ce9136 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit bf060df09183f2992829f0e8ec19bb6e389c1613 +Subproject commit 4ce9136a434fe540ee25ee48568a510703a31b1a diff --git a/src/modules/features/SetupWizard/dialogs/SelectMethodNewUser.svelte b/src/modules/features/SetupWizard/dialogs/SelectMethodNewUser.svelte index 2478521..b4dd9ea 100644 --- a/src/modules/features/SetupWizard/dialogs/SelectMethodNewUser.svelte +++ b/src/modules/features/SetupWizard/dialogs/SelectMethodNewUser.svelte @@ -48,6 +48,8 @@ bind:value={userType} > This is an advanced option for users who do not have a URI or who wish to configure detailed settings. + You can also select this option if you intend to use P2P (Peer-to-Peer) synchronisation + instead of a CouchDB/S3 server — P2P requires no server setup at all. diff --git a/src/serviceFeatures/useP2PReplicatorUI.ts b/src/serviceFeatures/useP2PReplicatorUI.ts index 59ef277..3e3882a 100644 --- a/src/serviceFeatures/useP2PReplicatorUI.ts +++ b/src/serviceFeatures/useP2PReplicatorUI.ts @@ -10,6 +10,7 @@ import { } from "@/features/P2PSync/P2PReplicator/P2PServerStatusPaneView"; import type { LiveSyncCore } from "@/main"; import type { WorkspaceLeaf } from "@/deps"; +import { REMOTE_P2P } from "@lib/common/models/setting.const"; /** * ServiceFeature: P2P Replicator lifecycle management. @@ -105,6 +106,41 @@ export function useP2PReplicatorUI( void openStatusPane(); }, }); + host.services.API.addCommand({ + id: "replicate-now-by-p2p-default-peer", + name: "Replicate P2P to default peer", + checkCallback: (isChecking: boolean) => { + const settings = host.services.setting.currentSettings(); + if (isChecking) { + if (settings.remoteType == REMOTE_P2P) return false; + return replicator.replicator?.server?.isServing ?? false; + } + void replicator.replicator?.openReplication(settings, false, true, false); + }, + }); + host.services.API.addCommand({ + id: "replicate-now-by-p2p", + name: "Replicate now by P2P", + checkCallback: (isChecking: boolean) => { + const settings = host.services.setting.currentSettings(); + if (isChecking) { + if (settings.remoteType == REMOTE_P2P) return false; + return replicator.replicator?.server?.isServing ?? false; + } + void replicator.replicator?.openReplication(settings, false, true, false); + }, + }); + + host.services.API.addCommand({ + id: "p2p-sync-targets", + name: "P2P: Sync with targets", + checkCallback: (isChecking: boolean) => { + if (isChecking) { + return replicator.replicator?.server?.isServing ?? false; + } + void replicator.replicator?.replicateFromCommand(true); + }, + }); // api.addRibbonIcon("waypoints", "P2P Replicator", () => { // void openPane(); From 4ed1749652c5534409e8016a32310bf0ea29224c Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sun, 17 May 2026 01:36:09 +0900 Subject: [PATCH 44/73] Enhance P2P synchronization features and UI improvements --- .../P2PReplicator/P2POpenReplicationModal.ts | 15 +++- .../P2POpenReplicationPane.svelte | 74 ++++++++++++------- .../P2PSync/P2PReplicator/P2PReplicationUI.ts | 60 ++++++++++++++- src/lib | 2 +- src/main.ts | 8 +- .../ModuleResolveMismatchedTweaks.ts | 4 + src/modules/services/ObsidianAPIService.ts | 5 ++ src/serviceFeatures/redFlag.ts | 9 ++- 8 files changed, 145 insertions(+), 32 deletions(-) diff --git a/src/features/P2PSync/P2PReplicator/P2POpenReplicationModal.ts b/src/features/P2PSync/P2PReplicator/P2POpenReplicationModal.ts index 8be0ab3..aa2cd94 100644 --- a/src/features/P2PSync/P2PReplicator/P2POpenReplicationModal.ts +++ b/src/features/P2PSync/P2PReplicator/P2POpenReplicationModal.ts @@ -13,17 +13,26 @@ export class P2POpenReplicationModal extends Modal { callback?: P2POpenReplicationModalCallback; component?: ReturnType; showResult: boolean; + title: string; + onClosed?: () => void; + rebuildMode: boolean; constructor( app: App, liveSyncReplicator: LiveSyncTrysteroReplicator, callback?: P2POpenReplicationModalCallback, - showResult: boolean = false + showResult: boolean = false, + title: string = "P2P Replication", + onClosed?: () => void, + rebuildMode: boolean = false ) { super(app); this.liveSyncReplicator = liveSyncReplicator; this.callback = callback; this.showResult = showResult; + this.title = title; + this.onClosed = onClosed; + this.rebuildMode = rebuildMode; } async onSync(peerId: string) { @@ -41,7 +50,7 @@ export class P2POpenReplicationModal extends Modal { override onOpen() { const { contentEl } = this; - this.titleEl.setText("P2P Replication"); + this.titleEl.setText(this.title); contentEl.empty(); if (this.component === undefined) { @@ -53,6 +62,7 @@ export class P2POpenReplicationModal extends Modal { onSyncAndClose: (peerId: string) => this.onSyncAndClose(peerId), onClose: () => this.close(), showResult: this.showResult, + rebuildMode: this.rebuildMode, }, }); } @@ -65,5 +75,6 @@ export class P2POpenReplicationModal extends Modal { void unmount(this.component); this.component = undefined; } + this.onClosed?.(); } } diff --git a/src/features/P2PSync/P2PReplicator/P2POpenReplicationPane.svelte b/src/features/P2PSync/P2PReplicator/P2POpenReplicationPane.svelte index c745891..58c7e99 100644 --- a/src/features/P2PSync/P2PReplicator/P2POpenReplicationPane.svelte +++ b/src/features/P2PSync/P2PReplicator/P2POpenReplicationPane.svelte @@ -19,9 +19,10 @@ onSyncAndClose: (_peerId: string) => Promise; onClose: () => void; showResult: boolean; + rebuildMode?: boolean; } - let { onSync, onSyncAndClose, onClose, showResult, liveSyncReplicator }: Props = $props(); + let { onSync, onSyncAndClose, onClose, showResult, liveSyncReplicator, rebuildMode = false }: Props = $props(); let serverInfo = $state(undefined); let syncingPeerId = $state(null); @@ -36,10 +37,10 @@ const unsubscribe = eventHub.onEvent(EVENT_SERVER_STATUS, (status) => { serverInfo = status; }); - fireAndForget(async ()=>{ + fireAndForget(async () => { await delay(100); await requestServerStatus(); - }) + }); return unsubscribe; }); @@ -55,6 +56,18 @@ syncingPeerId = null; } } + async function handleSyncThenClose(peerId: string) { + try { + syncingPeerId = peerId; + Logger(`Starting sync with ${peerId}`, logLevel); + await onSyncAndClose(peerId); + Logger(`Sync completed with ${peerId}`, logLevel); + } catch (e) { + Logger(`Error during sync: ${e instanceof Error ? e.message : String(e)}`, logLevel); + } finally { + syncingPeerId = null; + } + } async function handleSyncAndClose(peerId: string) { fireAndForget(async () => { @@ -68,7 +81,7 @@ }); onClose(); } - async function disconnect(){ + async function disconnect() { try { await liveSyncReplicator.close(); Logger("Signalling connection closed.", logLevel); @@ -76,7 +89,7 @@ Logger(`Failed to close signalling connection: ${e instanceof Error ? e.message : String(e)}`, logLevel); } } - async function onCloseAndDisconnect(){ + async function onCloseAndDisconnect() { await disconnect(); onClose(); } @@ -97,10 +110,7 @@
- +

Available Peers

@@ -121,20 +131,30 @@
- - + {#if !rebuildMode} + + + {:else} + + {/if}
{/each} @@ -145,8 +165,12 @@ diff --git a/src/features/P2PSync/P2PReplicator/P2PReplicationUI.ts b/src/features/P2PSync/P2PReplicator/P2PReplicationUI.ts index 28907db..ae5f02f 100644 --- a/src/features/P2PSync/P2PReplicator/P2PReplicationUI.ts +++ b/src/features/P2PSync/P2PReplicator/P2PReplicationUI.ts @@ -1,7 +1,7 @@ import { App } from "@/deps.ts"; import { Logger } from "@lib/common/logger"; import { LOG_LEVEL_NOTICE, LOG_LEVEL_INFO } from "@lib/common/types"; -import type { LiveSyncTrysteroReplicator } from "@/lib/src/replication/trystero/LiveSyncTrysteroReplicator"; +import type { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator"; import { P2POpenReplicationModal } from "./P2POpenReplicationModal"; /** @@ -71,3 +71,61 @@ export function createOpenReplicationUI( }); }; } + +/** + * Creates an openRebuildUI factory for Obsidian environments. + * Opens the P2P Replication modal in "rebuild" mode — one-way pull only, + * with setOnSetup / clearOnSetup bracketing the replicateFrom call. + * + * Usage: + * const factory = createOpenRebuildUI(app); + * useP2PReplicatorFeature(core, createOpenReplicationUI(app), factory); + */ +export function createOpenRebuildUI( + app: App +): (replicator: LiveSyncTrysteroReplicator) => (showResult: boolean) => Promise { + return (replicator: LiveSyncTrysteroReplicator) => + (showResult: boolean): Promise => { + const logLevel = showResult ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO; + return new Promise((resolve) => { + let resolved = false; + const safeResolve = (val: boolean) => { + if (!resolved) { + resolved = true; + resolve(val); + } + }; + + const doRebuild = async (peerId: string) => { + replicator.setOnSetup(); + try { + Logger(`Rebuilding from peer ${peerId}`, logLevel); + const result = await replicator.replicateFrom(peerId, showResult); + safeResolve(result?.ok ?? false); + } catch (e) { + Logger( + `Error in rebuild from ${peerId}: ${e instanceof Error ? e.message : String(e)}`, + logLevel + ); + safeResolve(false); + } finally { + replicator.clearOnSetup(); + } + }; + + const modal = new P2POpenReplicationModal( + app, + replicator, + { + onSync: doRebuild, + onSyncAndClose: doRebuild, + }, + showResult, + "P2P Rebuild", + () => safeResolve(false), + true + ); + modal.open(); + }); + }; +} diff --git a/src/lib b/src/lib index 4ce9136..cc552ac 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 4ce9136a434fe540ee25ee48568a510703a31b1a +Subproject commit cc552acad2995eb9f4eb470a2c09d447c3db867f diff --git a/src/main.ts b/src/main.ts index 102d120..6c5c3f3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -44,7 +44,7 @@ import { useSetupManagerHandlersFeature } from "./serviceFeatures/setupObsidian/ import { useP2PReplicatorFeature } from "@lib/replication/trystero/useP2PReplicatorFeature.ts"; import { useP2PReplicatorCommands } from "@lib/replication/trystero/useP2PReplicatorCommands.ts"; import { useP2PReplicatorUI } from "./serviceFeatures/useP2PReplicatorUI.ts"; -import { createOpenReplicationUI } from "./features/P2PSync/P2PReplicator/P2PReplicationUI.ts"; +import { createOpenReplicationUI, createOpenRebuildUI } from "./features/P2PSync/P2PReplicator/P2PReplicationUI.ts"; export type LiveSyncCore = LiveSyncBaseCore; export default class ObsidianLiveSyncPlugin extends Plugin { core: LiveSyncCore; @@ -177,7 +177,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin { const curriedFeature = () => featuresInitialiser(core); core.services.appLifecycle.onLayoutReady.addHandler(curriedFeature); const setupManager = core.getModule(SetupManager); - const replicator = useP2PReplicatorFeature(core, createOpenReplicationUI(this.app)); + const replicator = useP2PReplicatorFeature( + core, + createOpenReplicationUI(this.app), + createOpenRebuildUI(this.app) + ); useP2PReplicatorCommands(core, replicator); useP2PReplicatorUI(core, core, replicator); useRemoteConfiguration(core); diff --git a/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.ts b/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.ts index fab0091..d5776ef 100644 --- a/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.ts +++ b/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.ts @@ -14,6 +14,7 @@ import { AbstractModule } from "../AbstractModule.ts"; import { $msg } from "../../lib/src/common/i18n.ts"; import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts"; import type { LiveSyncCore } from "../../main.ts"; +import { REMOTE_P2P } from "@lib/common/models/setting.const.ts"; export class ModuleResolvingMismatchedTweaks extends AbstractModule { async _anyAfterConnectCheckFailed(): Promise { @@ -186,6 +187,9 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule { async _checkAndAskUseRemoteConfiguration( trialSetting: RemoteDBSettings ): Promise<{ result: false | TweakValues; requireFetch: boolean }> { + if (trialSetting.remoteType === REMOTE_P2P) { + return { result: false, requireFetch: false }; + } const preferred = await this.services.tweakValue.fetchRemotePreferred(trialSetting); if (preferred) { return await this.services.tweakValue.askUseRemoteConfiguration(trialSetting, preferred); diff --git a/src/modules/services/ObsidianAPIService.ts b/src/modules/services/ObsidianAPIService.ts index 19784e4..9f19c2d 100644 --- a/src/modules/services/ObsidianAPIService.ts +++ b/src/modules/services/ObsidianAPIService.ts @@ -39,6 +39,11 @@ export class ObsidianAPIService extends InjectableAPIService { + const existing = this.app.workspace.getLeavesOfType(viewType); + if (existing.length > 0) { + await this.app.workspace.revealLeaf(existing[0]); + return; + } const rightLeaf = this.app.workspace.getRightLeaf(false); if (rightLeaf) { await rightLeaf.setViewState({ diff --git a/src/serviceFeatures/redFlag.ts b/src/serviceFeatures/redFlag.ts index 90e052e..fa1dada 100644 --- a/src/serviceFeatures/redFlag.ts +++ b/src/serviceFeatures/redFlag.ts @@ -5,7 +5,7 @@ import { FlagFilesHumanReadable, FlagFilesOriginal } from "@lib/common/models/re import FetchEverything from "../modules/features/SetupWizard/dialogs/FetchEverything.svelte"; import RebuildEverything from "../modules/features/SetupWizard/dialogs/RebuildEverything.svelte"; import { extractObject } from "octagonal-wheels/object"; -import { REMOTE_MINIO } from "@lib/common/models/setting.const"; +import { REMOTE_MINIO, REMOTE_P2P } from "@lib/common/models/setting.const"; import type { ObsidianLiveSyncSettings } from "@lib/common/models/setting.type"; import { TweakValuesShouldMatchedTemplate } from "@lib/common/models/tweak.definition"; @@ -200,6 +200,13 @@ export async function adjustSettingToRemoteIfNeeded( return; } + // P2P has no centralised remote configuration; skip to avoid a spurious + // "Failed to connect to the remote server" error dialog. + if (config.remoteType === REMOTE_P2P) { + log("Remote configuration fetch skipped (P2P mode).", LOG_LEVEL_INFO); + return; + } + // Remote configuration fetched and applied. if (await adjustSettingToRemote(host, log, config)) { config = host.services.setting.currentSettings(); From a379b5bd7845fad88a1abbd3a8b74003f998f7b1 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sun, 17 May 2026 01:40:50 +0900 Subject: [PATCH 45/73] bump --- manifest.json | 18 +++++++++--------- package-lock.json | 4 ++-- package.json | 2 +- src/lib | 2 +- updates.md | 22 +++++++++++++++------- 5 files changed, 28 insertions(+), 20 deletions(-) diff --git a/manifest.json b/manifest.json index 66379e4..91eb189 100644 --- a/manifest.json +++ b/manifest.json @@ -1,10 +1,10 @@ { - "id": "obsidian-livesync", - "name": "Self-hosted LiveSync", - "version": "0.25.62", - "minAppVersion": "1.7.2", - "description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", - "author": "vorotamoroz", - "authorUrl": "https://github.com/vrtmrz", - "isDesktopOnly": false -} + "id": "obsidian-livesync", + "name": "Self-hosted LiveSync", + "version": "0.25.63", + "minAppVersion": "1.7.2", + "description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", + "author": "vorotamoroz", + "authorUrl": "https://github.com/vrtmrz", + "isDesktopOnly": false +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4349282..68c1113 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.62", + "version": "0.25.63", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.62", + "version": "0.25.63", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 4bbb2c6..4fd1805 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.62", + "version": "0.25.63", "description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "main": "main.js", "type": "module", diff --git a/src/lib b/src/lib index cc552ac..07e287c 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit cc552acad2995eb9f4eb470a2c09d447c3db867f +Subproject commit 07e287c53122a09300f916b5679826e2d1d75b5a diff --git a/updates.md b/updates.md index a392a7f..a5c9090 100644 --- a/updates.md +++ b/updates.md @@ -3,18 +3,26 @@ Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025) The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). Because 0.25 got a lot of updates, thankfully, compatibility is kept and we do not need breaking changes! In other words, when get enough stabled. The next version will be v1.0.0. Even though it my hope. +## 0.25.63 + +17th May, 2026 + +### Fixed +- The issue which cannot synchronise in Only-P2P mode has been fixed. +- Fixed an issue where "Failed to connect to the remote server" was shown during the redFlag rebuild flow when P2P was the primary remote type. Remote configuration fetch is now skipped for P2P. + +### P2P Replication UI Improvements +- Brand-new P2P Server Status pane has been added to provide real-time visibility into your connection status and peer network. + - For detailed instructions on using the new P2P features, please refer to the updated [User Guide: Peer-to-Peer Synchronisation (2026 Edition)](./docs/p2p_sync_updates_2026.md). +- Now `Replicate` button or ribbon icon opens a redesigned interactive replication dialogue that performs smart bidirectional sync with a single click. +- The vault rebuild flow (`replicateAllFromServer`) now opens the redesigned P2P Replication modal instead of a plain text selection dialogue, providing a consistent UI experience. + + ## Unreleased 15th May, 2026 -### Fixed -- The issue which cannot synchronise in Only-P2P mode has been fixed. - -### P2P Replication UI Improvements -- Brand-new P2P Server Status pane has been added to provide real-time visibility into your connection status and peer network. -- Now `Replicate` button or ribbon icon opens a redesigned interactive replication dialogue that performs smart bidirectional sync with a single click. - ## 0.25.62 14th May, 2026 From 83228e2077c9ffb2ec866492dacec490eae5dde8 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sun, 17 May 2026 02:23:58 +0900 Subject: [PATCH 46/73] Fix P2P replicator creation and enhance error handling in synchronization functions --- src/apps/cli/commands/p2p.ts | 17 +++++++++++------ src/apps/cli/main.ts | 11 +++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/apps/cli/commands/p2p.ts b/src/apps/cli/commands/p2p.ts index f47b62e..e3297ee 100644 --- a/src/apps/cli/commands/p2p.ts +++ b/src/apps/cli/commands/p2p.ts @@ -32,10 +32,15 @@ function validateP2PSettings(core: LiveSyncBaseCore) { settings.P2P_IsHeadless = true; } -function createReplicator(core: LiveSyncBaseCore): LiveSyncTrysteroReplicator { +async function createReplicator(core: LiveSyncBaseCore): Promise { validateP2PSettings(core); - const replicator = new LiveSyncTrysteroReplicator({ services: core.services }); - addP2PEventHandlers(replicator); + const replicator = await core.services.replicator.getNewReplicator(); + if (!replicator) { + throw new Error("Failed to create replicator instance. Ensure P2P is enabled in settings."); + } + if (!(replicator instanceof LiveSyncTrysteroReplicator)) { + throw new Error("Unexpected replicator type. Expected LiveSyncTrysteroReplicator."); + } return replicator; } @@ -49,7 +54,7 @@ export async function collectPeers( core: LiveSyncBaseCore, timeoutSec: number ): Promise { - const replicator = createReplicator(core); + const replicator = await createReplicator(core); await replicator.open(); try { await delay(timeoutSec * 1000); @@ -79,7 +84,7 @@ export async function syncWithPeer( peerToken: string, timeoutSec: number ): Promise { - const replicator = createReplicator(core); + const replicator = await createReplicator(core); await replicator.open(); try { const timeoutMs = timeoutSec * 1000; @@ -115,7 +120,7 @@ export async function syncWithPeer( } export async function openP2PHost(core: LiveSyncBaseCore): Promise { - const replicator = createReplicator(core); + const replicator = await createReplicator(core); await replicator.open(); return replicator; } diff --git a/src/apps/cli/main.ts b/src/apps/cli/main.ts index 7067c70..b75dd34 100644 --- a/src/apps/cli/main.ts +++ b/src/apps/cli/main.ts @@ -8,7 +8,6 @@ import * as path from "path"; import { NodeServiceContext, NodeServiceHub } from "./services/NodeServiceHub"; import { configureNodeLocalStorage, ensureGlobalNodeLocalStorage } from "./services/NodeLocalStorage"; import { LiveSyncBaseCore } from "../../LiveSyncBaseCore"; -import { ModuleReplicatorP2P } from "../../modules/core/ModuleReplicatorP2P"; import { initialiseServiceModulesCLI } from "./serviceModules/CLIServiceModules"; import { DEFAULT_SETTINGS, LOG_LEVEL_VERBOSE, type LOG_LEVEL, type ObsidianLiveSyncSettings } from "@lib/common/types"; import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub"; @@ -27,6 +26,7 @@ import type { CLICommand, CLIOptions } from "./commands/types"; import { getPathFromUXFileInfo } from "@lib/common/typeUtils"; import { stripAllPrefixes } from "@lib/string_and_binary/path"; import { IgnoreRules } from "./serviceModules/IgnoreRules"; +import { useP2PReplicatorFeature } from "@/lib/src/replication/trystero/useP2PReplicatorFeature"; const SETTINGS_FILE = ".livesync/settings.json"; ensureGlobalNodeLocalStorage(); @@ -368,12 +368,11 @@ export async function main() { (core: LiveSyncBaseCore, serviceHub: InjectableServiceHub) => { return initialiseServiceModulesCLI(vaultPath, core, serviceHub, ignoreRules, watchEnabled); }, - (core) => [ - // No modules need to be registered for P2P replication in CLI. Directly using Replicators in p2p.ts - // new ModuleReplicatorP2P(core), - ], + (core) => [], () => [], // No add-ons (core) => { + // Register P2P replicator feature. + const _replicator = useP2PReplicatorFeature(core); // Add target filter to prevent internal files are handled core.services.vault.isTargetFile.addHandler(async (target) => { const targetPath = stripAllPrefixes(getPathFromUXFileInfo(target)); @@ -424,7 +423,7 @@ export async function main() { // Save the settings file before any lifecycle events can mutate and persist them. // suspendAllSync and other lifecycle hooks clobber sync settings in memory, and // various code paths persist the clobbered state to disk. We restore on shutdown. - const settingsBackup = await fs.readFile(settingsPath, "utf-8").catch(() => null); + const settingsBackup = await fs.readFile(settingsPath, "utf-8").catch(() => null!); // Restore settings file on any exit to undo lifecycle mutations. // Write to a temp path first so a crash mid-write doesn't leave a truncated file. From 9d9364af36b55534700035614f74fc19c0975145 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sun, 17 May 2026 02:24:58 +0900 Subject: [PATCH 47/73] Fix updates.md --- updates.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/updates.md b/updates.md index a5c9090..76da4db 100644 --- a/updates.md +++ b/updates.md @@ -17,12 +17,6 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid - Now `Replicate` button or ribbon icon opens a redesigned interactive replication dialogue that performs smart bidirectional sync with a single click. - The vault rebuild flow (`replicateAllFromServer`) now opens the redesigned P2P Replication modal instead of a plain text selection dialogue, providing a consistent UI experience. - -## Unreleased - -15th May, 2026 - - ## 0.25.62 14th May, 2026 From eea26dee746393d771579266edd1c983a144548d Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sun, 17 May 2026 02:35:20 +0900 Subject: [PATCH 48/73] Update about P2P --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9980ca7..ed2540e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Self-hosted LiveSync is a community-developed synchronisation plug-in available on all Obsidian-compatible platforms. It leverages robust server solutions such as CouchDB or object storage systems (e.g., MinIO, S3, R2, etc.) to ensure reliable data synchronisation. -Additionally, it supports peer-to-peer synchronisation using WebRTC now (experimental), enabling you to synchronise your notes directly between devices without relying on a server. +Additionally, it supports peer-to-peer synchronisation using WebRTC, enabling you to synchronise your notes directly between devices without relying on a server. Documentations is available for [Peer-to-Peer Synchronisation](./docs/p2p_sync_updates_2026.md). ![obsidian_live_sync_demo](https://user-images.githubusercontent.com/45774780/137355323-f57a8b09-abf2-4501-836c-8cb7d2ff24a3.gif) From 6ef866a77ca8b66e1a632bda8f6e4c5d0b517a0a Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sun, 17 May 2026 13:08:51 +0900 Subject: [PATCH 49/73] P2P: Enhance status pane and card with active remote selection and replication features - Added active P2P remote selector and creation option in the status pane. - Introduced immediate replication action for accepted peers. - Updated status control icons for clarity. - Display stable Room ID suffix above Peer ID in the status card. - Implemented dedicated active remote configuration for P2P features. - Added migration support for P2P active remote selection. - Improved unit test coverage for P2P settings. --- docs/p2p_sync_updates_2026.md | 10 +- .../P2PReplicator/P2PReplicatorPane.svelte | 30 +- .../P2PReplicator/P2PServerStatusCard.svelte | 25 +- .../P2PReplicator/P2PServerStatusPane.svelte | 306 +++++++++++++++++- src/lib | 2 +- .../SetupWizard/dialogs/SetupRemoteP2P.svelte | 30 +- updates.md | 29 ++ 7 files changed, 378 insertions(+), 54 deletions(-) diff --git a/docs/p2p_sync_updates_2026.md b/docs/p2p_sync_updates_2026.md index 34dc9de..b6f3903 100644 --- a/docs/p2p_sync_updates_2026.md +++ b/docs/p2p_sync_updates_2026.md @@ -20,15 +20,21 @@ Once you have saved the settings, return to the **P2P Status Pane** and click th ## 3. Real-time Control The status pane in the right sidebar provides granular control over your synchronisation: +- **Active P2P Remote (new):** P2P now has its own active remote selection, separate from the normal active remote for database replication. Use the combo box next to the cog icon to choose which P2P remote configuration is active for P2P features. +- **Create P2P Remote (new):** Use the **+** button to open the P2P setup dialogue and create a dedicated P2P remote configuration. This is recommended when no P2P active remote has been selected yet. +- **Selection required (new):** If no P2P active remote is selected, the pane asks for selection before P2P target-related changes are saved. + - **Signalling Status:** Shows if you are connected to the relay (🟢 Online). - **Live-push (Broadcast):** Toggle "Broadcast changes" to notify other peers whenever you make an edit. -- **Watch:** Enable "Watch" on specific peers to automatically pull changes when they broadcast. This creates a "LiveSync-like" experience. -- **Sync (🔄/🔁):** Mark specific peers as **sync targets**. Peers marked here will be included when you run the **"P2P: Sync with targets"** command (see section 5). Click the button next to a peer to toggle it on (🔄, highlighted) or off (🔁). This setting is persisted in your configuration. +- **Replicate now (🔄):** Start immediate bidirectional replication with a visible peer (Pull, then Push). +- **Watch (🔔/🔕):** Enable "Watch" on specific peers to automatically pull changes when they broadcast. This creates a "LiveSync-like" experience. +- **Sync target (🔗/⛓️‍💥):** Mark specific peers as **sync targets**. Peers marked here will be included when you run the **"P2P: Sync with targets"** command (see section 5). Click the button next to a peer to toggle it on (🔗, highlighted) or off (⛓️‍💥). This setting is persisted in your configuration. ## 4. Replication Dialogue If you want to synchronise with a specific peer manually, use the **Replication** command or button. This opens the **Replication Dialogue** listing available devices. Inside the dialogue, the **Server Status** card at the top confirms you are still connected while performing the sync. +The status card now shows a stable **Room ID suffix** above **Peer ID**. The Room ID suffix is better for identifying your P2P group, while Peer ID may change between connections. Two actions are available per peer: diff --git a/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte b/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte index 9dd7c8e..948a20e 100644 --- a/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte +++ b/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte @@ -5,20 +5,21 @@ AcceptedStatus, ConnectionStatus, type PeerStatus, - } from "../../../lib/src/replication/trystero/P2PReplicatorPaneCommon"; - import type { LiveSyncTrysteroReplicator } from "../../../lib/src/replication/trystero/LiveSyncTrysteroReplicator"; + } from "@lib/replication/trystero/P2PReplicatorPaneCommon"; + import type { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator"; import PeerStatusRow from "../P2PReplicator/PeerStatusRow.svelte"; - import { EVENT_LAYOUT_READY, eventHub } from "../../../common/events"; + import { EVENT_LAYOUT_READY, eventHub } from "@/common/events"; import { type PeerInfo, type P2PServerInfo, EVENT_SERVER_STATUS, EVENT_REQUEST_STATUS, EVENT_P2P_REPLICATOR_STATUS, - } from "../../../lib/src/replication/trystero/TrysteroReplicatorP2PServer"; - import { type P2PReplicatorStatus } from "../../../lib/src/replication/trystero/TrysteroReplicator"; - import { $msg as _msg } from "../../../lib/src/common/i18n"; - import { SETTING_KEY_P2P_DEVICE_NAME } from "../../../lib/src/common/types"; + } from "@lib/replication/trystero/TrysteroReplicatorP2PServer"; + import { type P2PReplicatorStatus } from "@lib/replication/trystero/TrysteroReplicator"; + import { $msg as _msg } from "@lib/common/i18n"; + import { SETTING_KEY_P2P_DEVICE_NAME } from "@lib/common/types"; + import { generateP2PRoomId } from "@lib/common/utils"; import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore"; interface Props { @@ -148,6 +149,7 @@ eventHub.emitEvent(EVENT_REQUEST_STATUS); return () => { r(); + rx(); r2(); r3(); }; @@ -216,18 +218,8 @@ function useDefaultRelay() { eRelay = DEFAULT_SETTINGS.P2P_relays; } - function _generateRandom() { - return (Math.floor(Math.random() * 1000) + 1000).toString().substring(1); - } - function generateRandom(length: number) { - let buf = ""; - while (buf.length < length) { - buf += "-" + _generateRandom(); - } - return buf.substring(1, length); - } function chooseRandom() { - eRoomId = generateRandom(12) + "-" + Math.random().toString(36).substring(2, 5); + eRoomId = generateP2PRoomId(); } async function openServer() { @@ -251,7 +243,7 @@ setting?: boolean; }; return initialDialogStatus; - } catch (e) { + } catch { return {}; } }; diff --git a/src/features/P2PSync/P2PReplicator/P2PServerStatusCard.svelte b/src/features/P2PSync/P2PReplicator/P2PServerStatusCard.svelte index 980aed4..4d53651 100644 --- a/src/features/P2PSync/P2PReplicator/P2PServerStatusCard.svelte +++ b/src/features/P2PSync/P2PReplicator/P2PServerStatusCard.svelte @@ -8,17 +8,22 @@ EVENT_REQUEST_STATUS, EVENT_P2P_REPLICATOR_STATUS, } from "@lib/replication/trystero/TrysteroReplicatorP2PServer"; + import { EVENT_SETTING_SAVED } from "@lib/events/coreEvents"; import type { LiveSyncTrysteroReplicator } from "@/lib/src/replication/trystero/LiveSyncTrysteroReplicator"; import type { P2PReplicatorStatus } from "@/lib/src/replication/trystero/TrysteroReplicator"; + import { extractP2PRoomSuffix } from "@/lib/src/common/utils"; + import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore"; interface Props { liveSyncReplicator: LiveSyncTrysteroReplicator; showBroadcastToggle?: boolean; + core?: LiveSyncBaseCore; } - let { liveSyncReplicator, showBroadcastToggle = true }: Props = $props(); + let { liveSyncReplicator, showBroadcastToggle = true, core }: Props = $props(); let serverInfo = $state(undefined); let replicatorStatus = $state(undefined); + let roomSuffix = $state(extractP2PRoomSuffix(core?.services.setting.currentSettings()?.P2P_roomID ?? "")); async function requestServerStatus() { await Promise.resolve(liveSyncReplicator.requestStatus()); @@ -46,10 +51,14 @@ onMount(() => { const unsubscribe = eventHub.onEvent(EVENT_SERVER_STATUS, (status) => { serverInfo = status; + roomSuffix = extractP2PRoomSuffix(status?.roomId ?? ""); }); const unsubscribeStatus = eventHub.onEvent(EVENT_P2P_REPLICATOR_STATUS, (status) => { replicatorStatus = status; }); + const unsubscribeSettings = eventHub.onEvent(EVENT_SETTING_SAVED, (settings) => { + roomSuffix = extractP2PRoomSuffix(settings?.P2P_roomID ?? ""); + }); fireAndForget(async () => { await delay(100); @@ -59,6 +68,7 @@ return () => { unsubscribe(); unsubscribeStatus(); + unsubscribeSettings(); }; }); @@ -85,6 +95,13 @@ {#if serverInfo} +
+ Room ID suffix: + + {roomSuffix || "-"} + +
+
Peer ID: @@ -162,6 +179,12 @@ text-overflow: ellipsis; } + .room-suffix-display { + font-family: monospace; + font-size: 0.85rem; + font-weight: 600; + } + .broadcast-row { align-items: center; margin-top: 0.25rem; diff --git a/src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte b/src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte index fc148f8..aeeee48 100644 --- a/src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte +++ b/src/features/P2PSync/P2PReplicator/P2PServerStatusPane.svelte @@ -1,6 +1,6 @@

P2P Status

- +
+
+ + +
+ +
- + {#if !canEditP2PSettings()} +

Please select an active P2P remote configuration to change P2P sync targets.

+ {/if} + +
@@ -225,6 +457,15 @@ {getAcceptanceStatus(peer)} +
SYNC @@ -249,9 +491,10 @@ class="emoji-button {isSyncTarget(peer.name) ? 'is-watching' : ''}" title={isSyncTarget(peer.name) ? 'Sync target \u2014 click to remove' : 'Set as sync target'} aria-label={isSyncTarget(peer.name) ? 'Remove sync target' : 'Set sync target'} + disabled={!canEditP2PSettings()} onclick={() => toggleSyncTarget(peer)} > - {isSyncTarget(peer.name) ? '🔄' : '🔁'} + {isSyncTarget(peer.name) ? '🔗' : '⛓️‍💥'}
{:else}
@@ -345,6 +588,37 @@ gap: 0.5rem; } + .pane-header-actions { + display: flex; + align-items: center; + gap: 0.4rem; + min-width: 0; + } + + .remote-picker-wrap { + display: inline-flex; + gap: 0.3rem; + align-items: center; + min-width: 0; + } + + .remote-picker { + max-width: 14rem; + min-width: 8rem; + height: 1.9rem; + border: 1px solid var(--divider-color); + border-radius: 0.4rem; + background-color: var(--interactive-normal); + color: var(--text-normal); + padding: 0 0.45rem; + } + + .warning-line { + margin: -0.2rem 0 0; + font-size: 0.82rem; + color: var(--text-warning); + } + .pane-header h2 { margin: 0; font-size: 1.1rem; @@ -511,7 +785,7 @@ } .accepted-row { - grid-template-columns: 1fr auto; + grid-template-columns: 1fr auto auto; } .decision-label { diff --git a/src/lib b/src/lib index 07e287c..f2b910a 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 07e287c53122a09300f916b5679826e2d1d75b5a +Subproject commit f2b910aa4e9c4217f62d4d51ea5e25855cb3e62b diff --git a/src/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte b/src/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte index 64a794f..cd2020e 100644 --- a/src/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte +++ b/src/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte @@ -1,13 +1,13 @@