From 787627a15668f26529d4ab91f5addaac12d25829 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 12 Feb 2026 06:27:29 +0000 Subject: [PATCH 001/339] Refactor: Move some functions from modules to services --- src/lib | 2 +- src/modules/AbstractModule.ts | 10 ++++++ src/modules/AbstractObsidianModule.ts | 29 +++------------- src/modules/core/ModuleTargetFilter.ts | 12 +------ .../coreObsidian/ModuleFileAccessObsidian.ts | 11 ------- .../storageLib/SerializedFileAccess.ts | 10 +++--- .../essentialObsidian/ModuleObsidianAPI.ts | 23 +------------ src/modules/features/ModuleLog.ts | 2 +- src/modules/services/ObsidianServiceHub.ts | 6 ++-- src/modules/services/ObsidianServices.ts | 18 ++++++++-- src/modules/services/ObsidianVaultService.ts | 33 +++++++++++++++++++ 11 files changed, 75 insertions(+), 81 deletions(-) create mode 100644 src/modules/services/ObsidianVaultService.ts diff --git a/src/lib b/src/lib index 4ff3cad..750ddbb 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 4ff3cad80b7b42ec6940f765b113bf11d5ba555a +Subproject commit 750ddbb082157fa4c52953becaacc8ee8048f2b9 diff --git a/src/modules/AbstractModule.ts b/src/modules/AbstractModule.ts index 0cbd402..b49d155 100644 --- a/src/modules/AbstractModule.ts +++ b/src/modules/AbstractModule.ts @@ -62,4 +62,14 @@ export abstract class AbstractModule { get services() { return this.core._services; } + + isMainReady() { + return this.services.appLifecycle.isReady(); + } + isMainSuspended() { + return this.services.appLifecycle.isSuspended(); + } + isDatabaseReady() { + return this.services.database.isDatabaseReady(); + } } diff --git a/src/modules/AbstractObsidianModule.ts b/src/modules/AbstractObsidianModule.ts index c25db21..acc635e 100644 --- a/src/modules/AbstractObsidianModule.ts +++ b/src/modules/AbstractObsidianModule.ts @@ -10,20 +10,11 @@ export type ModuleKeys = keyof IObsidianModule; export type ChainableModuleProps = ChainableExecuteFunction; export abstract class AbstractObsidianModule extends AbstractModule { - addCommand = this.plugin.addCommand.bind(this.plugin); - registerView = this.plugin.registerView.bind(this.plugin); - addRibbonIcon = this.plugin.addRibbonIcon.bind(this.plugin); - registerObsidianProtocolHandler = this.plugin.registerObsidianProtocolHandler.bind(this.plugin); + addCommand = this.services.API.addCommand.bind(this.services.API); + registerView = this.services.API.registerWindow.bind(this.services.API); + addRibbonIcon = this.services.API.addRibbonIcon.bind(this.services.API); + registerObsidianProtocolHandler = this.services.API.registerProtocolHandler.bind(this.services.API); - get localDatabase() { - return this.plugin.localDatabase; - } - get settings() { - return this.plugin.settings; - } - set settings(value) { - this.plugin.settings = value; - } get app() { return this.plugin.app; } @@ -35,18 +26,6 @@ export abstract class AbstractObsidianModule extends AbstractModule { super(core); } - saveSettings = this.plugin.saveSettings.bind(this.plugin); - - isMainReady() { - return this.services.appLifecycle.isReady(); - } - isMainSuspended() { - return this.services.appLifecycle.isSuspended(); - } - isDatabaseReady() { - return this.services.database.isDatabaseReady(); - } - //should be overridden isThisModuleEnabled() { return true; diff --git a/src/modules/core/ModuleTargetFilter.ts b/src/modules/core/ModuleTargetFilter.ts index 32f5fe4..6e266ef 100644 --- a/src/modules/core/ModuleTargetFilter.ts +++ b/src/modules/core/ModuleTargetFilter.ts @@ -53,15 +53,6 @@ export class ModuleTargetFilter extends AbstractModule { ); } - private _isFileSizeExceeded(size: number) { - if (this.settings.syncMaxSizeInMB > 0 && size > 0) { - if (this.settings.syncMaxSizeInMB * 1024 * 1024 < size) { - return true; - } - } - return false; - } - _markFileListPossiblyChanged(): void { this.totalFileEventCount++; } @@ -110,7 +101,7 @@ export class ModuleTargetFilter extends AbstractModule { const filepath = getStoragePathFromUXFileInfo(file); const lc = filepath.toLowerCase(); - if (this.services.setting.shouldCheckCaseInsensitively()) { + if (this.services.vault.shouldCheckCaseInsensitively()) { if (lc in fileCount && fileCount[lc] > 1) { return false; } @@ -178,7 +169,6 @@ export class ModuleTargetFilter extends AbstractModule { services.path.id2path.setHandler(this._id2path.bind(this)); services.path.path2id.setHandler(this._path2id.bind(this)); services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this)); - services.vault.isFileSizeTooLarge.setHandler(this._isFileSizeExceeded.bind(this)); services.vault.isIgnoredByIgnoreFile.setHandler(this._isIgnoredByIgnoreFiles.bind(this)); services.vault.isTargetFile.setHandler(this._isTargetFile.bind(this)); } diff --git a/src/modules/coreObsidian/ModuleFileAccessObsidian.ts b/src/modules/coreObsidian/ModuleFileAccessObsidian.ts index a7e58ed..369c008 100644 --- a/src/modules/coreObsidian/ModuleFileAccessObsidian.ts +++ b/src/modules/coreObsidian/ModuleFileAccessObsidian.ts @@ -79,15 +79,6 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements return Promise.resolve(true); } - _isStorageInsensitive(): boolean { - return this.vaultAccess.isStorageInsensitive(); - } - - _shouldCheckCaseInsensitive(): boolean { - if (this.services.vault.isStorageInsensitive()) return false; - return !this.settings.handleFilenameCaseSensitive; - } - async writeFileAuto(path: string, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise { const file = this.vaultAccess.getAbstractFileByPath(path); if (file instanceof TFile) { @@ -386,8 +377,6 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements super(plugin, core); } onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { - services.vault.isStorageInsensitive.setHandler(this._isStorageInsensitive.bind(this)); - services.setting.shouldCheckCaseInsensitively.setHandler(this._shouldCheckCaseInsensitive.bind(this)); services.appLifecycle.onFirstInitialise.addHandler(this._everyOnFirstInitialize.bind(this)); services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this)); diff --git a/src/modules/coreObsidian/storageLib/SerializedFileAccess.ts b/src/modules/coreObsidian/storageLib/SerializedFileAccess.ts index 59d8a32..4c448f6 100644 --- a/src/modules/coreObsidian/storageLib/SerializedFileAccess.ts +++ b/src/modules/coreObsidian/storageLib/SerializedFileAccess.ts @@ -1,11 +1,12 @@ import { type App, TFile, type DataWriteOptions, TFolder, TAbstractFile } from "../../../deps.ts"; import { Logger } from "../../../lib/src/common/logger.ts"; import { isPlainText } from "../../../lib/src/string_and_binary/path.ts"; -import type { FilePath, HasSettings, UXFileInfoStub } from "../../../lib/src/common/types.ts"; +import type { FilePath, UXFileInfoStub } from "../../../lib/src/common/types.ts"; import { createBinaryBlob, isDocContentSame } from "../../../lib/src/common/utils.ts"; import type { InternalFileInfo } from "../../../common/types.ts"; import { markChangesAreSame } from "../../../common/utils.ts"; import type { StorageAccess } from "../../interfaces/StorageAccess.ts"; +import type { LiveSyncCore } from "@/main.ts"; function toArrayBuffer(arr: Uint8Array | ArrayBuffer | DataView): ArrayBuffer { if (arr instanceof Uint8Array) { return arr.buffer; @@ -18,9 +19,9 @@ function toArrayBuffer(arr: Uint8Array | ArrayBuffer | DataView; + plugin: LiveSyncCore; storageAccess: StorageAccess; - constructor(app: App, plugin: SerializedFileAccess["plugin"], storageAccess: StorageAccess) { + constructor(app: App, plugin: LiveSyncCore, storageAccess: StorageAccess) { this.app = app; this.plugin = plugin; this.storageAccess = storageAccess; @@ -163,8 +164,7 @@ export class SerializedFileAccess { } isStorageInsensitive(): boolean { - //@ts-ignore - return this.app.vault.adapter.insensitive ?? true; + return this.plugin.services.vault.isStorageInsensitive(); } getAbstractFileByPathInsensitive(path: FilePath | string): TAbstractFile | null { diff --git a/src/modules/essentialObsidian/ModuleObsidianAPI.ts b/src/modules/essentialObsidian/ModuleObsidianAPI.ts index cdd085b..3396bb4 100644 --- a/src/modules/essentialObsidian/ModuleObsidianAPI.ts +++ b/src/modules/essentialObsidian/ModuleObsidianAPI.ts @@ -8,8 +8,7 @@ import { type LOG_LEVEL, } from "octagonal-wheels/common/logger"; import { Notice, requestUrl, type RequestUrlParam, type RequestUrlResponse } from "../../deps.ts"; -import { type CouchDBCredentials, type EntryDoc, type FilePath } from "../../lib/src/common/types.ts"; -import { getPathFromTFile } from "../../common/utils.ts"; +import { type CouchDBCredentials, type EntryDoc } from "../../lib/src/common/types.ts"; import { isCloudantURI, isValidRemoteCouchDBURI } from "../../lib/src/pouchdb/utils_couchdb.ts"; import { replicationFilter } from "@/lib/src/pouchdb/compress.ts"; import { disableEncryption } from "@/lib/src/pouchdb/encryption.ts"; @@ -279,23 +278,6 @@ export class ModuleObsidianAPI extends AbstractObsidianModule { } } - _vaultName(): string { - return this.app.vault.getName(); - } - _getVaultName(): string { - return ( - this.services.vault.vaultName() + - (this.settings?.additionalSuffixOfDatabaseName ? "-" + this.settings.additionalSuffixOfDatabaseName : "") - ); - } - _getActiveFilePath(): FilePath | undefined { - const file = this.app.workspace.getActiveFile(); - if (file) { - return getPathFromTFile(file); - } - return undefined; - } - private _reportUnresolvedMessages(): Promise<(string | Error)[]> { return Promise.resolve([...this._previousErrors]); } @@ -303,9 +285,6 @@ export class ModuleObsidianAPI extends AbstractObsidianModule { onBindFunction(core: LiveSyncCore, services: typeof core.services) { services.API.isLastPostFailedDueToPayloadSize.setHandler(this._getLastPostFailedBySize.bind(this)); services.remote.connect.setHandler(this._connectRemoteCouchDB.bind(this)); - services.vault.getVaultName.setHandler(this._getVaultName.bind(this)); - services.vault.vaultName.setHandler(this._vaultName.bind(this)); - services.vault.getActiveFilePath.setHandler(this._getActiveFilePath.bind(this)); services.appLifecycle.getUnresolvedMessages.addHandler(this._reportUnresolvedMessages.bind(this)); } } diff --git a/src/modules/features/ModuleLog.ts b/src/modules/features/ModuleLog.ts index f841256..4c6d1d2 100644 --- a/src/modules/features/ModuleLog.ts +++ b/src/modules/features/ModuleLog.ts @@ -251,7 +251,7 @@ export class ModuleLog extends AbstractObsidianModule { } } // Case Sensitivity - if (this.services.setting.shouldCheckCaseInsensitively()) { + if (this.services.vault.shouldCheckCaseInsensitively()) { const f = this.core.storageAccess .getFiles() .map((e) => e.path) diff --git a/src/modules/services/ObsidianServiceHub.ts b/src/modules/services/ObsidianServiceHub.ts index c01d747..83465eb 100644 --- a/src/modules/services/ObsidianServiceHub.ts +++ b/src/modules/services/ObsidianServiceHub.ts @@ -13,12 +13,12 @@ import { ObsidianRemoteService, ObsidianSettingService, ObsidianTweakValueService, - ObsidianVaultService, ObsidianTestService, ObsidianDatabaseEventService, ObsidianPathService, ObsidianConfigService, } from "./ObsidianServices"; +import { ObsidianVaultService } from "./ObsidianVaultService"; import { ObsidianUIService } from "./ObsidianUIService"; // InjectableServiceHub @@ -37,7 +37,9 @@ export class ObsidianServiceHub extends InjectableServiceHub(command: TCommand): TCommand { + return this.context.plugin.addCommand(command) as TCommand; + } + + registerWindow(type: string, factory: ViewCreator): void { + return this.context.plugin.registerView(type, factory); + } + addRibbonIcon(icon: string, title: string, callback: (evt: MouseEvent) => any): HTMLElement { + return this.context.plugin.addRibbonIcon(icon, title, callback); + } + registerProtocolHandler(action: string, handler: (params: Record) => any): void { + return this.context.plugin.registerObsidianProtocolHandler(action, handler); + } } export class ObsidianPathService extends InjectablePathService {} export class ObsidianDatabaseService extends InjectableDatabaseService { @@ -117,8 +131,6 @@ export class ObsidianAppLifecycleService extends InjectableAppLifecycleService {} // InjectableTweakValueService export class ObsidianTweakValueService extends InjectableTweakValueService {} -// InjectableVaultService -export class ObsidianVaultService extends InjectableVaultService {} // InjectableTestService export class ObsidianTestService extends InjectableTestService {} export class ObsidianConfigService extends ConfigServiceBrowserCompat {} diff --git a/src/modules/services/ObsidianVaultService.ts b/src/modules/services/ObsidianVaultService.ts new file mode 100644 index 0000000..00912b7 --- /dev/null +++ b/src/modules/services/ObsidianVaultService.ts @@ -0,0 +1,33 @@ +import { getPathFromTFile } from "@/common/utils"; +import { InjectableVaultService } from "@/lib/src/services/implements/injectable/InjectableVaultService"; +import type { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext"; +import type { FilePath } from "@/lib/src/common/types"; + +declare module "obsidian" { + interface DataAdapter { + insensitive?: boolean; + } +} + +// InjectableVaultService +export class ObsidianVaultService extends InjectableVaultService { + vaultName(): string { + return this.context.app.vault.getName(); + } + getActiveFilePath(): FilePath | undefined { + const file = this.context.app.workspace.getActiveFile(); + if (file) { + return getPathFromTFile(file); + } + return undefined; + } + isStorageInsensitive(): boolean { + return this.context.app.vault.adapter.insensitive ?? true; + } + + override shouldCheckCaseInsensitively(): boolean { + // If the storage is insensitive, always return false, that because no need to check again. + if (this.isStorageInsensitive()) return false; + return super.shouldCheckCaseInsensitively(); // Check the setting + } +} From 1b5ca9e52c172722ff836f7900af046340c1d8cf Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 12 Feb 2026 08:56:30 +0000 Subject: [PATCH 002/339] Refactor (write notes later) --- src/apps/webpeer/package.json | 3 +- src/apps/webpeer/src/P2PReplicatorShim.ts | 9 +- src/common/types.ts | 25 ++-- src/common/utils.ts | 72 ++--------- .../HiddenFileSync/CmdHiddenFileSync.ts | 45 +++++-- src/features/LiveSyncCommands.ts | 4 +- .../P2PReplicator/P2PReplicatorPane.svelte | 12 +- src/lib | 2 +- src/modules/AbstractModule.ts | 15 ++- src/modules/core/ModuleFileHandler.ts | 19 +-- .../core/ModuleLocalDatabaseObsidian.ts | 4 +- src/modules/core/ModuleTargetFilter.ts | 33 +---- src/modules/core/ReplicateResultProcessor.ts | 18 +-- .../essential/ModuleInitializerFile.ts | 12 +- src/modules/essential/ModuleMigration.ts | 4 +- .../GlobalHistory/GlobalHistory.svelte | 116 +++++++++--------- .../ModuleInteractiveConflictResolver.ts | 11 +- .../features/ModuleObsidianDocumentHistory.ts | 3 +- .../features/SettingDialogue/PaneHatch.ts | 4 +- src/modules/main/ModuleLiveSyncMain.ts | 2 +- src/modules/services/ObsidianPathService.ts | 8 ++ src/modules/services/ObsidianServiceHub.ts | 6 +- src/modules/services/ObsidianServices.ts | 2 - src/modules/services/ObsidianUIService.ts | 5 +- src/modules/services/SvelteDialogObsidian.ts | 4 +- 25 files changed, 204 insertions(+), 234 deletions(-) create mode 100644 src/modules/services/ObsidianPathService.ts diff --git a/src/apps/webpeer/package.json b/src/apps/webpeer/package.json index dd12898..990666f 100644 --- a/src/apps/webpeer/package.json +++ b/src/apps/webpeer/package.json @@ -20,6 +20,7 @@ "vite": "^7.3.0" }, "imports": { - "../../src/worker/bgWorker.ts": "../../src/worker/bgWorker.mock.ts" + "../../src/worker/bgWorker.ts": "../../src/worker/bgWorker.mock.ts", + "@lib/worker/bgWorker.ts": "@lib/worker/bgWorker.mock.ts" } } diff --git a/src/apps/webpeer/src/P2PReplicatorShim.ts b/src/apps/webpeer/src/P2PReplicatorShim.ts index c548fd1..65b8759 100644 --- a/src/apps/webpeer/src/P2PReplicatorShim.ts +++ b/src/apps/webpeer/src/P2PReplicatorShim.ts @@ -35,6 +35,7 @@ import { SETTING_KEY_P2P_DEVICE_NAME } from "@lib/common/types"; import { ServiceContext } from "@lib/services/base/ServiceBase"; import type { InjectableServiceHub } from "@lib/services/InjectableServices"; import { Menu } from "@/lib/src/services/implements/browser/Menu"; +import type { InjectableVaultServiceCompat } from "@/lib/src/services/implements/injectable/InjectableVaultService"; function addToList(item: string, list: string) { return unique( @@ -79,7 +80,13 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { constructor() { const browserServiceHub = new BrowserServiceHub(); this.services = browserServiceHub; - this.services.vault.getVaultName.setHandler(() => "p2p-livesync-web-peer"); + (this.services.vault as InjectableVaultServiceCompat).vaultName.setHandler( + () => "p2p-livesync-web-peer" + ); + + this.services.setting.currentSettings.setHandler(() => { + return this.settings as any; + }); } async init() { // const { simpleStoreAPI } = await getWrappedSynchromesh(); diff --git a/src/common/types.ts b/src/common/types.ts index 51f877e..c7b903c 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -49,21 +49,16 @@ export type queueItem = { warned?: boolean; }; -// Hidden items (Now means `chunk`) -export const CHeader = "h:"; - -// Plug-in Stored Container (Obsolete) -export const PSCHeader = "ps:"; -export const PSCHeaderEnd = "ps;"; - -// Internal data Container -export const ICHeader = "i:"; -export const ICHeaderEnd = "i;"; -export const ICHeaderLength = ICHeader.length; - -// Internal data Container (eXtended) -export const ICXHeader = "ix:"; - export const FileWatchEventQueueMax = 10; export { configURIBase, configURIBaseQR } from "../lib/src/common/types.ts"; + +export { + CHeader, + PSCHeader, + PSCHeaderEnd, + ICHeader, + ICHeaderEnd, + ICHeaderLength, + ICXHeader, +} from "../lib/src/common/models/fileaccess.const.ts"; diff --git a/src/common/utils.ts b/src/common/utils.ts index 75f6546..254a419 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -23,7 +23,7 @@ import { type UXFileInfo, type UXFileInfoStub, } from "../lib/src/common/types.ts"; -import { CHeader, ICHeader, ICHeaderLength, ICXHeader, PSCHeader } from "./types.ts"; +import { ICHeader, ICXHeader } from "./types.ts"; import type ObsidianLiveSyncPlugin from "../main.ts"; import { writeString } from "../lib/src/string_and_binary/convert.ts"; import { fireAndForget } from "../lib/src/common/utils.ts"; @@ -63,23 +63,12 @@ export function id2path(id: DocumentID, entry?: EntryHasPath): FilePathWithPrefi const fixedPath = temp.join(":") as FilePathWithPrefix; return fixedPath; } -export function getPath(entry: AnyEntry) { - return id2path(entry._id, entry); -} -export function getPathWithoutPrefix(entry: AnyEntry) { - const f = getPath(entry); - return stripAllPrefixes(f); -} export function getPathFromTFile(file: TAbstractFile) { return file.path as FilePath; } -export function isInternalFile(file: UXFileInfoStub | string | FilePathWithPrefix) { - if (typeof file == "string") return file.startsWith(ICHeader); - if (file.isInternal) return true; - return false; -} +import { isInternalFile } from "@lib/common/typeUtils.ts"; export function getPathFromUXFileInfo(file: UXFileInfoStub | string | FilePathWithPrefix) { if (typeof file == "string") return file as FilePathWithPrefix; return file.path; @@ -137,32 +126,14 @@ export function trimPrefix(target: string, prefix: string) { return target.startsWith(prefix) ? target.substring(prefix.length) : target; } -/** - * returns is internal chunk of file - * @param id ID - * @returns - */ -export function isInternalMetadata(id: FilePath | FilePathWithPrefix | DocumentID): boolean { - return id.startsWith(ICHeader); -} -export function stripInternalMetadataPrefix(id: T): T { - return id.substring(ICHeaderLength) as T; -} -export function id2InternalMetadataId(id: DocumentID): DocumentID { - return (ICHeader + id) as DocumentID; -} - -// const CHeaderLength = CHeader.length; -export function isChunk(str: string): boolean { - return str.startsWith(CHeader); -} - -export function isPluginMetadata(str: string): boolean { - return str.startsWith(PSCHeader); -} -export function isCustomisationSyncMetadata(str: string): boolean { - return str.startsWith(ICXHeader); -} +export { + isInternalMetadata, + id2InternalMetadataId, + isChunk, + isCustomisationSyncMetadata, + isPluginMetadata, + stripInternalMetadataPrefix, +} from "@lib/common/typeUtils.ts"; export class PeriodicProcessor { _process: () => Promise; @@ -430,29 +401,6 @@ export function displayRev(rev: string) { return `${number}-${hash.substring(0, 6)}`; } -type DocumentProps = { - id: DocumentID; - rev?: string; - prefixedPath: FilePathWithPrefix; - path: FilePath; - isDeleted: boolean; - revDisplay: string; - shortenedId: string; - shortenedPath: string; -}; - -export function getDocProps(doc: AnyEntry): DocumentProps { - const id = doc._id; - const shortenedId = id.substring(0, 10); - const prefixedPath = getPath(doc); - const path = stripAllPrefixes(prefixedPath); - const rev = doc._rev; - const revDisplay = rev ? displayRev(rev) : "0-NOREVS"; - // const prefix = prefixedPath.substring(0, prefixedPath.length - path.length); - const shortenedPath = path.substring(0, 10); - const isDeleted = doc._deleted || doc.deleted || false; - return { id, rev, revDisplay, prefixedPath, path, isDeleted, shortenedId, shortenedPath }; -} export function getLogLevel(showNotice: boolean) { return showNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO; diff --git a/src/features/HiddenFileSync/CmdHiddenFileSync.ts b/src/features/HiddenFileSync/CmdHiddenFileSync.ts index dd7a323..8d50d74 100644 --- a/src/features/HiddenFileSync/CmdHiddenFileSync.ts +++ b/src/features/HiddenFileSync/CmdHiddenFileSync.ts @@ -30,19 +30,18 @@ import { import { compareMTime, unmarkChanges, - getPath, isInternalMetadata, markChangesAreSame, PeriodicProcessor, TARGET_IS_NEW, scheduleTask, - getDocProps, getLogLevel, autosaveCache, type MapLike, onlyInNTimes, BASE_IS_NEW, EVEN, + displayRev, } from "../../common/utils.ts"; import { serialized, skipIfDuplicated } from "octagonal-wheels/concurrency/lock"; import { JsonResolveModal } from "../HiddenFileCommon/JsonResolveModal.ts"; @@ -139,6 +138,7 @@ export class HiddenFileSync extends LiveSyncCommands { this.updateSettingCache(); }); } + // We cannot initialise autosaveCache because kvDB is not ready yet // async _everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise { // this._fileInfoLastProcessed = await autosaveCache(this.kvDB, "hidden-file-lastProcessed"); @@ -243,7 +243,7 @@ export class HiddenFileSync extends LiveSyncCommands { if (isInternalMetadata(doc._id)) { if (this.isThisModuleEnabled()) { //system file - const filename = getPath(doc); + const filename = this.getPath(doc); if (await this.services.vault.isTargetFile(filename)) { // this.procInternalFile(filename); await this.processReplicationResult(doc); @@ -843,9 +843,32 @@ Offline Changed files: ${processFiles.length}`; // <-- Conflict processing // --> Event Source Handler (Database) - + getDocProps(doc: LoadedEntry) { + /* + type DocumentProps = { + id: DocumentID; + rev?: string; + prefixedPath: FilePathWithPrefix; + path: FilePath; + isDeleted: boolean; + revDisplay: string; + shortenedId: string; + shortenedPath: string; + }; + */ + const id = doc._id; + const shortenedId = id.substring(0, 10); + const prefixedPath = this.getPath(doc); + const path = stripAllPrefixes(prefixedPath); + const rev = doc._rev; + const revDisplay = rev ? displayRev(rev) : "0-NOREVS"; + // const prefix = prefixedPath.substring(0, prefixedPath.length - path.length); + const shortenedPath = path.substring(0, 10); + const isDeleted = doc._deleted || doc.deleted || false; + return { id, rev, revDisplay, prefixedPath, path, isDeleted, shortenedId, shortenedPath }; + } async processReplicationResult(doc: LoadedEntry): Promise { - const info = getDocProps(doc); + const info = this.getDocProps(doc); const path = info.path; const headerLine = `Tracking DB ${info.path} (${info.revDisplay}) :`; const ret = await this.trackDatabaseFileModification(path, headerLine); @@ -1007,7 +1030,7 @@ Offline Changed files: ${processFiles.length}`; p.log("Enumerating database files..."); const currentDatabaseFiles = await this.getAllDatabaseFiles(); const allDatabaseMap = Object.fromEntries( - currentDatabaseFiles.map((e) => [stripAllPrefixes(getPath(e)), e]) + currentDatabaseFiles.map((e) => [stripAllPrefixes(this.getPath(e)), e]) ); const currentDatabaseFileNames = [...Object.keys(allDatabaseMap)] as FilePath[]; const untrackedLocal = currentStorageFiles.filter((e) => !this._fileInfoLastProcessed.has(e)); @@ -1250,14 +1273,14 @@ Offline Changed files: ${files.length}`; : currentStorageFilesAll; p.log("Enumerating database files..."); const allDatabaseFiles = await this.getAllDatabaseFiles(); - const allDatabaseMap = new Map(allDatabaseFiles.map((e) => [stripAllPrefixes(getPath(e)), e])); + const allDatabaseMap = new Map(allDatabaseFiles.map((e) => [stripAllPrefixes(this.getPath(e)), e])); const currentDatabaseFiles = targetFiles - ? allDatabaseFiles.filter((e) => targetFiles.some((f) => f == stripAllPrefixes(getPath(e)))) + ? allDatabaseFiles.filter((e) => targetFiles.some((f) => f == stripAllPrefixes(this.getPath(e)))) : allDatabaseFiles; const allFileNames = new Set([ ...currentStorageFiles, - ...currentDatabaseFiles.map((e) => stripAllPrefixes(getPath(e))), + ...currentDatabaseFiles.map((e) => stripAllPrefixes(this.getPath(e))), ]); const storageToDatabase = [] as FilePath[]; const databaseToStorage = [] as MetaEntry[]; @@ -1340,7 +1363,7 @@ Offline Changed files: ${files.length}`; // However, in perspective of performance and future-proofing, I feel somewhat justified in doing it here. const currentFiles = targetFiles - ? allFiles.filter((e) => targetFiles.some((f) => f == stripAllPrefixes(getPath(e)))) + ? allFiles.filter((e) => targetFiles.some((f) => f == stripAllPrefixes(this.getPath(e)))) : allFiles; p.once(`Database to Storage: ${currentFiles.length} files.`); @@ -1383,7 +1406,7 @@ Offline Changed files: ${files.length}`; const onlyNew = direction == "pull"; p.log(`Started: Database --> Storage ${onlyNew ? "(Only New)" : ""}`); const updatedEntries = await this.rebuildFromDatabase(showMessage, targetFiles, onlyNew); - const updatedFiles = updatedEntries.map((e) => stripAllPrefixes(getPath(e))); + const updatedFiles = updatedEntries.map((e) => stripAllPrefixes(this.getPath(e))); // making doubly sure, No more losing files. await this.adoptCurrentStorageFilesAsProcessed(updatedFiles); await this.adoptCurrentDatabaseFilesAsProcessed(updatedFiles); diff --git a/src/features/LiveSyncCommands.ts b/src/features/LiveSyncCommands.ts index 9732d94..e7db590 100644 --- a/src/features/LiveSyncCommands.ts +++ b/src/features/LiveSyncCommands.ts @@ -1,5 +1,4 @@ import { LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger"; -import { getPath } from "../common/utils.ts"; import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, @@ -36,8 +35,9 @@ export abstract class LiveSyncCommands { async path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise { return await this.services.path.path2id(filename, prefix); } + getPath(entry: AnyEntry): FilePathWithPrefix { - return getPath(entry); + return this.services.path.getPath(entry); } constructor(plugin: ObsidianLiveSyncPlugin) { diff --git a/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte b/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte index c1322f0..a0382bb 100644 --- a/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte +++ b/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte @@ -33,18 +33,15 @@ const initialSettings = { ...plugin.settings }; let settings = $state(initialSettings); - // const vaultName = service.vault.getVaultName(); - // const dbKey = `${vaultName}-p2p-device-name`; - const initialDeviceName = cmdSync.getConfig(SETTING_KEY_P2P_DEVICE_NAME) ?? plugin.services.vault.getVaultName(); - let deviceName = $state(initialDeviceName); + let deviceName = $state(""); let eP2PEnabled = $state(initialSettings.P2P_Enabled); let eRelay = $state(initialSettings.P2P_relays); let eRoomId = $state(initialSettings.P2P_roomID); let ePassword = $state(initialSettings.P2P_passphrase); let eAppId = $state(initialSettings.P2P_AppID); - let eDeviceName = $state(initialDeviceName); + let eDeviceName = $state(""); let eAutoAccept = $state(initialSettings.P2P_AutoAccepting == AutoAccepting.ALL); let eAutoStart = $state(initialSettings.P2P_AutoStart); let eAutoBroadcast = $state(initialSettings.P2P_AutoBroadcast); @@ -103,6 +100,11 @@ let serverInfo = $state(undefined); let replicatorInfo = $state(undefined); const applyLoadSettings = (d: P2PSyncSetting, force: boolean) => { + if(force){ + const initDeviceName = cmdSync.getConfig(SETTING_KEY_P2P_DEVICE_NAME) ?? plugin.services.vault.getVaultName(); + deviceName = initDeviceName; + eDeviceName = initDeviceName; + } const { P2P_relays, P2P_roomID, P2P_passphrase, P2P_AppID, P2P_AutoAccepting } = d; if (force || !isP2PEnabledModified) eP2PEnabled = d.P2P_Enabled; if (force || !isRelayModified) eRelay = P2P_relays; diff --git a/src/lib b/src/lib index 750ddbb..af01893 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 750ddbb082157fa4c52953becaacc8ee8048f2b9 +Subproject commit af0189376f6379d404e99ddb201d1a9019eb3471 diff --git a/src/modules/AbstractModule.ts b/src/modules/AbstractModule.ts index b49d155..33916b2 100644 --- a/src/modules/AbstractModule.ts +++ b/src/modules/AbstractModule.ts @@ -1,7 +1,8 @@ import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger"; -import type { LOG_LEVEL } from "../lib/src/common/types"; -import type { LiveSyncCore } from "../main"; -import { __$checkInstanceBinding } from "../lib/src/dev/checks"; +import type { AnyEntry, FilePathWithPrefix, LOG_LEVEL } from "@lib/common/types"; +import type { LiveSyncCore } from "@/main"; +import { __$checkInstanceBinding } from "@lib/dev/checks"; +import { stripAllPrefixes } from "@lib/string_and_binary/path"; export abstract class AbstractModule { _log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => { @@ -22,6 +23,14 @@ export abstract class AbstractModule { this.core.settings = value; } + getPath(entry: AnyEntry): FilePathWithPrefix { + return this.services.path.getPath(entry); + } + + getPathWithoutPrefix(entry: AnyEntry): FilePathWithPrefix { + return stripAllPrefixes(this.services.path.getPath(entry)); + } + onBindFunction(core: LiveSyncCore, services: typeof core.services) { // Override if needed. } diff --git a/src/modules/core/ModuleFileHandler.ts b/src/modules/core/ModuleFileHandler.ts index 2699004..8094fd6 100644 --- a/src/modules/core/ModuleFileHandler.ts +++ b/src/modules/core/ModuleFileHandler.ts @@ -10,14 +10,7 @@ import type { UXInternalFileInfoStub, } from "../../lib/src/common/types"; import { AbstractModule } from "../AbstractModule.ts"; -import { - compareFileFreshness, - EVEN, - getPath, - getPathWithoutPrefix, - getStoragePathFromUXFileInfo, - markChangesAreSame, -} from "../../common/utils"; +import { compareFileFreshness, EVEN, getStoragePathFromUXFileInfo, markChangesAreSame } from "../../common/utils"; import { getDocDataAsArray, isDocContentSame, readAsBlob, readContent } from "../../lib/src/common/utils"; import { shouldBeIgnored } from "../../lib/src/string_and_binary/path"; import { Semaphore } from "octagonal-wheels/concurrency/semaphore"; @@ -209,13 +202,13 @@ export class ModuleFileHandler extends AbstractModule { ): Promise { const file = typeof info === "string" ? this.storage.getFileStub(info) : info; const mode = file == null ? "create" : "modify"; - const pathFromEntryInfo = typeof entryInfo === "string" ? entryInfo : getPath(entryInfo); + const pathFromEntryInfo = typeof entryInfo === "string" ? entryInfo : this.getPath(entryInfo); const docEntry = await this.db.fetchEntryMeta(pathFromEntryInfo, undefined, true); if (!docEntry) { this._log(`File ${pathFromEntryInfo} is not exist on the database`, LOG_LEVEL_VERBOSE); return false; } - const path = getPath(docEntry); + const path = this.getPath(docEntry); // 1. Check if it already conflicted. const revs = await this.db.getConflictedRevs(path); @@ -364,11 +357,11 @@ export class ModuleFileHandler extends AbstractModule { this._log(`File ${entry.path} should be ignored`, LOG_LEVEL_VERBOSE); return false; } - const path = getPath(entry); + const path = this.getPath(entry); - const targetFile = this.storage.getStub(getPathWithoutPrefix(entry)); + const targetFile = this.storage.getStub(this.getPathWithoutPrefix(entry)); if (targetFile && targetFile.isFolder) { - this._log(`${getPath(entry)} is already exist as the folder`); + this._log(`${path} is already exist as the folder`); // Nothing to do and other modules should also nothing to do. return true; } else { diff --git a/src/modules/core/ModuleLocalDatabaseObsidian.ts b/src/modules/core/ModuleLocalDatabaseObsidian.ts index 5b74a2a..412cadc 100644 --- a/src/modules/core/ModuleLocalDatabaseObsidian.ts +++ b/src/modules/core/ModuleLocalDatabaseObsidian.ts @@ -22,9 +22,9 @@ export class ModuleLocalDatabaseObsidian extends AbstractModule { return getDB(); }, getActiveReplicator: () => this.core.replicator, - id2path: this.services.path.id2path, + id2path: this.services.path.id2path.bind(this.services.path), // path2id: this.core.$$path2id.bind(this.core), - path2id: this.services.path.path2id, + path2id: this.services.path.path2id.bind(this.services.path), get settings() { return getSettings(); }, diff --git a/src/modules/core/ModuleTargetFilter.ts b/src/modules/core/ModuleTargetFilter.ts index 6e266ef..93816ab 100644 --- a/src/modules/core/ModuleTargetFilter.ts +++ b/src/modules/core/ModuleTargetFilter.ts @@ -1,22 +1,12 @@ import { LRUCache } from "octagonal-wheels/memory/LRUCache"; -import { - getStoragePathFromUXFileInfo, - id2path, - isInternalMetadata, - path2id, - stripInternalMetadataPrefix, - useMemo, -} from "../../common/utils"; +import { getStoragePathFromUXFileInfo, useMemo } from "../../common/utils"; import { LOG_LEVEL_VERBOSE, - type DocumentID, - type EntryHasPath, - type FilePath, type FilePathWithPrefix, type ObsidianLiveSyncSettings, type UXFileInfoStub, } from "../../lib/src/common/types"; -import { addPrefix, isAcceptedAll } from "../../lib/src/string_and_binary/path"; +import { isAcceptedAll } from "../../lib/src/string_and_binary/path"; import { AbstractModule } from "../AbstractModule"; import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events"; import { isDirty } from "../../lib/src/common/utils"; @@ -36,23 +26,6 @@ export class ModuleTargetFilter extends AbstractModule { return Promise.resolve(true); } - _id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix { - const tempId = id2path(id, entry); - if (stripPrefix && isInternalMetadata(tempId)) { - const out = stripInternalMetadataPrefix(tempId); - return out; - } - return tempId; - } - async _path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise { - const destPath = addPrefix(filename, prefix ?? ""); - return await path2id( - destPath, - this.settings.usePathObfuscation ? this.settings.passphrase : "", - !this.settings.handleFilenameCaseSensitive - ); - } - _markFileListPossiblyChanged(): void { this.totalFileEventCount++; } @@ -166,8 +139,6 @@ export class ModuleTargetFilter extends AbstractModule { onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.vault.markFileListPossiblyChanged.setHandler(this._markFileListPossiblyChanged.bind(this)); - services.path.id2path.setHandler(this._id2path.bind(this)); - services.path.path2id.setHandler(this._path2id.bind(this)); services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this)); services.vault.isIgnoredByIgnoreFile.setHandler(this._isIgnoredByIgnoreFiles.bind(this)); services.vault.isTargetFile.setHandler(this._isTargetFile.bind(this)); diff --git a/src/modules/core/ReplicateResultProcessor.ts b/src/modules/core/ReplicateResultProcessor.ts index 6aa1acd..6b77a88 100644 --- a/src/modules/core/ReplicateResultProcessor.ts +++ b/src/modules/core/ReplicateResultProcessor.ts @@ -8,7 +8,7 @@ import { type MetaEntry, } from "@/lib/src/common/types"; import type { ModuleReplicator } from "./ModuleReplicator"; -import { getPath, isChunk, isValidPath } from "@/common/utils"; +import { isChunk, isValidPath } from "@/common/utils"; import type { LiveSyncCore } from "@/main"; import { LOG_LEVEL_DEBUG, @@ -58,6 +58,10 @@ export class ReplicateResultProcessor { return this.replicator.core; } + getPath(entry: AnyEntry): string { + return this.services.path.getPath(entry); + } + public suspend() { this._suspended = true; } @@ -230,7 +234,7 @@ export class ReplicateResultProcessor { */ protected enqueueChange(doc: PouchDB.Core.ExistingDocument) { const old = this._queuedChanges.find((e) => e._id == doc._id); - const path = "path" in doc ? getPath(doc) : ""; + const path = "path" in doc ? this.getPath(doc) : ""; const docNote = `${path} (${shortenId(doc._id)}, ${shortenRev(doc._rev)})`; if (old) { if (old._rev == doc._rev) { @@ -322,7 +326,7 @@ export class ReplicateResultProcessor { const docMtime = change.mtime ?? 0; const maxMTime = this.replicator.settings.maxMTimeForReflectEvents; if (maxMTime > 0 && docMtime > maxMTime) { - const docPath = getPath(change); + const docPath = this.getPath(change); this.log( `Processing ${docPath} has been skipped due to modification time (${new Date( docMtime * 1000 @@ -336,7 +340,7 @@ export class ReplicateResultProcessor { if (await this.services.replication.processVirtualDocument(change)) return; // If the document is version info, check compatibility and return. if (isAnyNote(change)) { - const docPath = getPath(change); + const docPath = this.getPath(change); if (!(await this.services.vault.isTargetFile(docPath))) { this.log(`Skipped: ${docPath}`, LOG_LEVEL_VERBOSE); return; @@ -383,7 +387,7 @@ export class ReplicateResultProcessor { // This function is serialized per document to avoid race-condition for the same document. private _applyToDatabase(doc_: PouchDB.Core.ExistingDocument) { const dbDoc = doc_ as LoadedEntry; // It has no `data` - const path = getPath(dbDoc); + const path = this.getPath(dbDoc); return serialized(`replication-process:${dbDoc._id}`, async () => { const docNote = `${path} (${shortenId(dbDoc._id)}, ${shortenRev(dbDoc._rev)})`; const isRequired = await this.checkIsChangeRequiredForDatabaseProcessing(dbDoc); @@ -409,7 +413,7 @@ export class ReplicateResultProcessor { if (await this.services.replication.processOptionalSynchroniseResult(dbDoc)) { // Already processed this.log(`Processed by other processor: ${docNote}`, LOG_LEVEL_DEBUG); - } else if (isValidPath(getPath(doc))) { + } else if (isValidPath(this.getPath(doc))) { // Apply to storage if the path is valid await this.applyToStorage(doc as MetaEntry); this.log(`Processed: ${docNote}`, LOG_LEVEL_DEBUG); @@ -437,7 +441,7 @@ export class ReplicateResultProcessor { * @returns True if processing is required; false otherwise */ protected async checkIsChangeRequiredForDatabaseProcessing(dbDoc: LoadedEntry): Promise { - const path = getPath(dbDoc); + const path = this.getPath(dbDoc); try { const savedDoc = await this.localDatabase.getRaw(dbDoc._id, { conflicts: true, diff --git a/src/modules/essential/ModuleInitializerFile.ts b/src/modules/essential/ModuleInitializerFile.ts index 78e3c4b..d170a01 100644 --- a/src/modules/essential/ModuleInitializerFile.ts +++ b/src/modules/essential/ModuleInitializerFile.ts @@ -1,7 +1,7 @@ import { unique } from "octagonal-wheels/collection"; import { throttle } from "octagonal-wheels/function"; import { EVENT_ON_UNRESOLVED_ERROR, eventHub } from "../../common/events.ts"; -import { BASE_IS_NEW, compareFileFreshness, EVEN, getPath, isValidPath, TARGET_IS_NEW } from "../../common/utils.ts"; +import { BASE_IS_NEW, compareFileFreshness, EVEN, isValidPath, TARGET_IS_NEW } from "../../common/utils.ts"; import { type FilePathWithPrefixLC, type FilePathWithPrefix, @@ -120,7 +120,7 @@ export class ModuleInitializerFile extends AbstractModule { showingNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO, "syncAll" ); - const path = getPath(doc); + const path = this.getPath(doc); if (isValidPath(path) && (await this.services.vault.isTargetFile(path, true))) { if (!isMetaEntry(doc)) { @@ -132,7 +132,7 @@ export class ModuleInitializerFile extends AbstractModule { } const databaseFileNameMap = Object.fromEntries( - _DBEntries.map((e) => [getPath(e), e] as [FilePathWithPrefix, MetaEntry]) + _DBEntries.map((e) => [this.getPath(e), e] as [FilePathWithPrefix, MetaEntry]) ); const databaseFileNames = Object.keys(databaseFileNameMap) as FilePathWithPrefix[]; const databaseFileNameCapsPair = databaseFileNames.map( @@ -224,7 +224,7 @@ export class ModuleInitializerFile extends AbstractModule { runAll("UPDATE STORAGE", filesExistOnlyInDatabase, async (e) => { const w = databaseFileNameMap[databaseFileNameCI2CS[e]]; // Exists in database but not in storage. - const path = getPath(w) ?? e; + const path = this.getPath(w) ?? e; if (w && !(w.deleted || w._deleted)) { if (!this.services.vault.isFileSizeTooLarge(w.size)) { // Prevent applying the conflicted state to the storage. @@ -275,7 +275,7 @@ export class ModuleInitializerFile extends AbstractModule { await this.syncFileBetweenDBandStorage(file, doc); } else { this._log( - `SYNC DATABASE AND STORAGE: ${getPath(doc)} has been skipped due to file size exceeding the limit`, + `SYNC DATABASE AND STORAGE: ${this.getPath(doc)} has been skipped due to file size exceeding the limit`, logLevel ); } @@ -365,7 +365,7 @@ export class ModuleInitializerFile extends AbstractModule { if (isAnyNote(doc)) { if (doc.deleted && doc.mtime - limit < 0) { notes.push({ - path: getPath(doc), + path: this.getPath(doc), mtime: doc.mtime, ttl: (doc.mtime - limit) / 1000 / 86400, doc: doc, diff --git a/src/modules/essential/ModuleMigration.ts b/src/modules/essential/ModuleMigration.ts index a50d25c..90aca81 100644 --- a/src/modules/essential/ModuleMigration.ts +++ b/src/modules/essential/ModuleMigration.ts @@ -10,7 +10,7 @@ import { import { AbstractModule } from "../AbstractModule.ts"; import { $msg } from "src/lib/src/common/i18n.ts"; import { performDoctorConsultation, RebuildOptions } from "../../lib/src/common/configForDoc.ts"; -import { getPath, isValidPath } from "../../common/utils.ts"; +import { isValidPath } from "../../common/utils.ts"; import { isMetaEntry } from "../../lib/src/common/types.ts"; import { isDeletedEntry, isDocContentSame, isLoadedEntry, readAsBlob } from "../../lib/src/common/utils.ts"; import { countCompromisedChunks } from "../../lib/src/pouchdb/negotiation.ts"; @@ -128,7 +128,7 @@ export class ModuleMigration extends AbstractModule { const errorFiles = [] as ErrorInfo[]; for await (const metaDoc of this.localDatabase.findAllNormalDocs({ conflicts: true })) { - const path = getPath(metaDoc); + const path = this.getPath(metaDoc); if (!isValidPath(path)) { continue; diff --git a/src/modules/features/GlobalHistory/GlobalHistory.svelte b/src/modules/features/GlobalHistory/GlobalHistory.svelte index b6ed9a5..5e815e0 100644 --- a/src/modules/features/GlobalHistory/GlobalHistory.svelte +++ b/src/modules/features/GlobalHistory/GlobalHistory.svelte @@ -6,7 +6,6 @@ import { diff_match_patch } from "../../../deps.ts"; import { DocumentHistoryModal } from "../DocumentHistory/DocumentHistoryModal.ts"; import { isPlainText, stripAllPrefixes } from "../../../lib/src/string_and_binary/path.ts"; - import { getPath } from "../../../common/utils.ts"; export let plugin: ObsidianLiveSyncPlugin; let showDiffInfo = false; @@ -44,6 +43,9 @@ }; let history = [] as HistoryData[]; let loading = false; + function getPath(entry: AnyEntry): FilePathWithPrefix { + return plugin.services.path.getPath(entry); + } async function fetchChanges(): Promise { try { @@ -219,69 +221,69 @@ {/if} - - - - - - {#if showChunkCorrected} - - {/if} - - - - - {#each history as entry} - + + + + {#if showChunkCorrected} + + {/if} + + + - - + {#each history as entry} + + + - - {#if showChunkCorrected} - - {/if} - - {/each} - - + + {#if showChunkCorrected} + + {/if} + + {/each} + + - + {:else} +
+ {/if} + +
Date Path Rev Stat Chunks
- {#if loading} -
- {:else} -
- {/if} -
- {entry.mtimeDisp} + Date Path Rev Stat Chunks
+ {#if loading} +
+ {:else} +
+ {/if}
-
- /{entry.dirname.split("/").join(`​/`)} - - - - openFile(entry.path)}>{entry.filename} -
-
- - {#if entry.isPlain} +
+ {entry.mtimeDisp} + +
+ /{entry.dirname.split("/").join(`​/`)} - showHistory(entry.path, entry?.rev || "")}>{entry.rev} - {:else} - {entry.rev} - {/if} - -
- {entry.changes} - - {entry.chunks} + openFile(entry.path)}>{entry.filename} +
- {#if loading} + + + {#if entry.isPlain} + + + + showHistory(entry.path, entry?.rev || "")}>{entry.rev} + {:else} + {entry.rev} + {/if} + + + {entry.changes} + + {entry.chunks} +
+ {#if loading}
- {:else} -
- {/if} -
diff --git a/src/modules/features/ModuleInteractiveConflictResolver.ts b/src/modules/features/ModuleInteractiveConflictResolver.ts index aebd806..aea1fdf 100644 --- a/src/modules/features/ModuleInteractiveConflictResolver.ts +++ b/src/modules/features/ModuleInteractiveConflictResolver.ts @@ -11,7 +11,7 @@ import { } from "../../lib/src/common/types.ts"; import { ConflictResolveModal } from "./InteractiveConflictResolving/ConflictResolveModal.ts"; import { AbstractObsidianModule } from "../AbstractObsidianModule.ts"; -import { displayRev, getPath, getPathWithoutPrefix } from "../../common/utils.ts"; +import { displayRev } from "../../common/utils.ts"; import { fireAndForget } from "octagonal-wheels/promises"; import { serialized } from "octagonal-wheels/concurrency/lock"; import type { LiveSyncCore } from "../../main.ts"; @@ -110,7 +110,12 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule { const notes: { id: DocumentID; path: FilePathWithPrefix; dispPath: string; mtime: number }[] = []; for await (const doc of this.localDatabase.findAllDocs({ conflicts: true })) { if (!("_conflicts" in doc)) continue; - notes.push({ id: doc._id, path: getPath(doc), dispPath: getPathWithoutPrefix(doc), mtime: doc.mtime }); + notes.push({ + id: doc._id, + path: this.getPath(doc), + dispPath: this.getPathWithoutPrefix(doc), + mtime: doc.mtime, + }); } notes.sort((a, b) => b.mtime - a.mtime); const notesList = notes.map((e) => e.dispPath); @@ -134,7 +139,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule { try { for await (const doc of this.localDatabase.findAllDocs({ conflicts: true })) { if (!("_conflicts" in doc)) continue; - notes.push({ path: getPath(doc), mtime: doc.mtime }); + notes.push({ path: this.getPath(doc), mtime: doc.mtime }); } if (notes.length > 0) { this.core.confirm.askInPopup( diff --git a/src/modules/features/ModuleObsidianDocumentHistory.ts b/src/modules/features/ModuleObsidianDocumentHistory.ts index 2b89490..7a49dd7 100644 --- a/src/modules/features/ModuleObsidianDocumentHistory.ts +++ b/src/modules/features/ModuleObsidianDocumentHistory.ts @@ -4,7 +4,6 @@ import { EVENT_REQUEST_SHOW_HISTORY } from "../../common/obsidianEvents.ts"; import type { FilePathWithPrefix, LoadedEntry, DocumentID } from "../../lib/src/common/types.ts"; import { AbstractObsidianModule } from "../AbstractObsidianModule.ts"; import { DocumentHistoryModal } from "./DocumentHistory/DocumentHistoryModal.ts"; -import { getPath } from "../../common/utils.ts"; import { fireAndForget } from "octagonal-wheels/promises"; export class ModuleObsidianDocumentHistory extends AbstractObsidianModule { @@ -41,7 +40,7 @@ export class ModuleObsidianDocumentHistory extends AbstractObsidianModule { async fileHistory() { const notes: { id: DocumentID; path: FilePathWithPrefix; dispPath: string; mtime: number }[] = []; for await (const doc of this.localDatabase.findAllDocs()) { - notes.push({ id: doc._id, path: getPath(doc), dispPath: getPath(doc), mtime: doc.mtime }); + notes.push({ id: doc._id, path: this.getPath(doc), dispPath: this.getPath(doc), mtime: doc.mtime }); } notes.sort((a, b) => b.mtime - a.mtime); const notesList = notes.map((e) => e.dispPath); diff --git a/src/modules/features/SettingDialogue/PaneHatch.ts b/src/modules/features/SettingDialogue/PaneHatch.ts index 441f742..3796de1 100644 --- a/src/modules/features/SettingDialogue/PaneHatch.ts +++ b/src/modules/features/SettingDialogue/PaneHatch.ts @@ -21,7 +21,7 @@ import { } from "../../../lib/src/common/utils.ts"; import { Logger } from "../../../lib/src/common/logger.ts"; import { isCloudantURI } from "../../../lib/src/pouchdb/utils_couchdb.ts"; -import { getPath, requestToCouchDBWithCredentials } from "../../../common/utils.ts"; +import { requestToCouchDBWithCredentials } from "../../../common/utils.ts"; import { addPrefix, shouldBeIgnored, stripAllPrefixes } from "../../../lib/src/string_and_binary/path.ts"; import { $msg } from "../../../lib/src/common/i18n.ts"; import { Semaphore } from "octagonal-wheels/concurrency/semaphore"; @@ -388,7 +388,7 @@ ${stringifyYaml({ const adn = this.plugin.localDatabase.findAllDocs(); for await (const i of adn) { - const path = getPath(i); + const path = this.services.path.getPath(i); if (path.startsWith(ICXHeader)) continue; if (path.startsWith(PSCHeader)) continue; if (!this.plugin.settings.syncInternalFiles && path.startsWith(ICHeader)) continue; diff --git a/src/modules/main/ModuleLiveSyncMain.ts b/src/modules/main/ModuleLiveSyncMain.ts index bc7f95a..84550a5 100644 --- a/src/modules/main/ModuleLiveSyncMain.ts +++ b/src/modules/main/ModuleLiveSyncMain.ts @@ -16,7 +16,7 @@ import { AbstractModule } from "../AbstractModule.ts"; import { EVENT_PLATFORM_UNLOADED } from "@lib/events/coreEvents"; import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub.ts"; import type { LiveSyncCore } from "../../main.ts"; -import { initialiseWorkerModule } from "@/lib/src/worker/bgWorker.ts"; +import { initialiseWorkerModule } from "@lib/worker/bgWorker.ts"; export class ModuleLiveSyncMain extends AbstractModule { async _onLiveSyncReady() { diff --git a/src/modules/services/ObsidianPathService.ts b/src/modules/services/ObsidianPathService.ts new file mode 100644 index 0000000..72e5532 --- /dev/null +++ b/src/modules/services/ObsidianPathService.ts @@ -0,0 +1,8 @@ +import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext"; +import { normalizePath } from "@/deps"; +import { PathService } from "@/lib/src/services/base/PathService"; +export class ObsidianPathService extends PathService { + protected normalizePath(path: string): string { + return normalizePath(path); + } +} diff --git a/src/modules/services/ObsidianServiceHub.ts b/src/modules/services/ObsidianServiceHub.ts index 83465eb..81319eb 100644 --- a/src/modules/services/ObsidianServiceHub.ts +++ b/src/modules/services/ObsidianServiceHub.ts @@ -15,9 +15,9 @@ import { ObsidianTweakValueService, ObsidianTestService, ObsidianDatabaseEventService, - ObsidianPathService, ObsidianConfigService, } from "./ObsidianServices"; +import { ObsidianPathService } from "./ObsidianPathService"; import { ObsidianVaultService } from "./ObsidianVaultService"; import { ObsidianUIService } from "./ObsidianUIService"; @@ -42,7 +42,9 @@ export class ObsidianServiceHub extends InjectableServiceHub {} export class ObsidianDatabaseService extends InjectableDatabaseService { openSimpleStore = handlers().binder("openSimpleStore") as (( kind: string diff --git a/src/modules/services/ObsidianUIService.ts b/src/modules/services/ObsidianUIService.ts index 7c0bd43..37b7230 100644 --- a/src/modules/services/ObsidianUIService.ts +++ b/src/modules/services/ObsidianUIService.ts @@ -5,7 +5,7 @@ import { UIService } from "@lib/services//implements/base/UIService"; import { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext"; import { ObsidianSvelteDialogManager } from "./SvelteDialogObsidian"; import { ObsidianConfirm } from "./ObsidianConfirm"; - +import DialogToCopy from "@/lib/src/UI/dialogues/DialogueToCopy.svelte"; export type ObsidianUIServiceDependencies = { appLifecycle: AppLifecycleService; config: ConfigService; @@ -13,6 +13,9 @@ export type ObsidianUIServiceDependencies { + override get dialogToCopy() { + return DialogToCopy; + } constructor(context: ObsidianServiceContext, dependents: ObsidianUIServiceDependencies) { const obsidianConfirm = new ObsidianConfirm(context); const obsidianSvelteDialogManager = new ObsidianSvelteDialogManager(context, { diff --git a/src/modules/services/SvelteDialogObsidian.ts b/src/modules/services/SvelteDialogObsidian.ts index c57d8f1..b40418b 100644 --- a/src/modules/services/SvelteDialogObsidian.ts +++ b/src/modules/services/SvelteDialogObsidian.ts @@ -7,8 +7,8 @@ import { type SvelteDialogManagerDependencies, } from "@lib/services/implements/base/SvelteDialog"; import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext"; - -export const SvelteDialogBase = SvelteDialogMixIn(Modal); +import DialogHost from "@/lib/src/UI/DialogHost.svelte"; +export const SvelteDialogBase = SvelteDialogMixIn(Modal, DialogHost); export class SvelteDialogObsidian< T, U, From fb59c4a72301e00987b29694c8d364e58a1331b0 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Fri, 13 Feb 2026 12:02:31 +0000 Subject: [PATCH 003/339] Refactored, please refer updates.md --- manifest.json | 2 +- package-lock.json | 4 +- package.json | 2 +- src/common/utils.ts | 1 - src/lib | 2 +- src/main.ts | 14 +- src/modules/AbstractModule.ts | 5 + src/modules/AbstractObsidianModule.ts | 5 - src/modules/core/ModuleTargetFilter.ts | 205 +++++++++--------- .../coreObsidian/ModuleFileAccessObsidian.ts | 6 +- .../ModuleCheckRemoteSize.ts | 4 +- .../essentialObsidian/ModuleObsidianEvents.ts | 9 +- .../essentialObsidian/ModuleObsidianMenu.ts | 12 +- src/modules/features/ModuleLog.ts | 2 - src/modules/features/ModuleObsidianSetting.ts | 6 +- .../ModuleObsidianSettingAsMarkdown.ts | 10 +- src/modules/features/ModuleSetupObsidian.ts | 6 +- src/modules/features/SetupManager.ts | 4 +- .../services/ObsidianAppLifecycleService.ts | 21 ++ src/modules/services/ObsidianServiceHub.ts | 2 +- src/modules/services/ObsidianServices.ts | 3 - updates.md | 45 +++- 22 files changed, 212 insertions(+), 158 deletions(-) create mode 100644 src/modules/services/ObsidianAppLifecycleService.ts diff --git a/manifest.json b/manifest.json index 7599247..dbcefb4 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.43", + "version": "0.25.43-patched-1", "minAppVersion": "0.9.12", "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 6ddaeba..3b8b0ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.43", + "version": "0.25.43-patched-1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.43", + "version": "0.25.43-patched-1", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 59e0107..6f2c734 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.43", + "version": "0.25.43-patched-1", "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/common/utils.ts b/src/common/utils.ts index 254a419..cde7196 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -401,7 +401,6 @@ export function displayRev(rev: string) { return `${number}-${hash.substring(0, 6)}`; } - export function getLogLevel(showNotice: boolean) { return showNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO; } diff --git a/src/lib b/src/lib index af01893..532f25f 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit af0189376f6379d404e99ddb201d1a9019eb3471 +Subproject commit 532f25f94784e46edd98438edee61a632ed53072 diff --git a/src/main.ts b/src/main.ts index fc65dc1..936a8be 100644 --- a/src/main.ts +++ b/src/main.ts @@ -126,31 +126,31 @@ export default class ObsidianLiveSyncPlugin new ModuleRemoteGovernor(this), new ModuleTargetFilter(this), new ModulePeriodicProcess(this), - // Obsidian modules + // Essential Modules new ModuleKeyValueDB(this), new ModuleInitializerFile(this), new ModuleObsidianAPI(this, this), new ModuleObsidianEvents(this, this), new ModuleFileAccessObsidian(this, this), - new ModuleObsidianSettings(this, this), + new ModuleObsidianSettings(this), new ModuleResolvingMismatchedTweaks(this), - new ModuleObsidianSettingsAsMarkdown(this, this), + new ModuleObsidianSettingsAsMarkdown(this), new ModuleObsidianSettingDialogue(this, this), new ModuleLog(this, this), - new ModuleObsidianMenu(this, this), + new ModuleObsidianMenu(this), new ModuleRebuilder(this), - new ModuleSetupObsidian(this, this), + new ModuleSetupObsidian(this), new ModuleObsidianDocumentHistory(this, this), new ModuleMigration(this), new ModuleRedFlag(this), new ModuleInteractiveConflictResolver(this, this), new ModuleObsidianGlobalHistory(this, this), - new ModuleCheckRemoteSize(this, this), + new ModuleCheckRemoteSize(this), // Test and Dev Modules new ModuleDev(this, this), new ModuleReplicateTest(this, this), new ModuleIntegratedTest(this, this), - new SetupManager(this, this), + new SetupManager(this), ] as (IObsidianModule | AbstractModule)[]; getModule(constructor: new (...args: any[]) => T): T { diff --git a/src/modules/AbstractModule.ts b/src/modules/AbstractModule.ts index 33916b2..a354be3 100644 --- a/src/modules/AbstractModule.ts +++ b/src/modules/AbstractModule.ts @@ -13,6 +13,11 @@ export abstract class AbstractModule { Logger(msg, level, key); }; + addCommand = this.services.API.addCommand.bind(this.services.API); + registerView = this.services.API.registerWindow.bind(this.services.API); + addRibbonIcon = this.services.API.addRibbonIcon.bind(this.services.API); + registerObsidianProtocolHandler = this.services.API.registerProtocolHandler.bind(this.services.API); + get localDatabase() { return this.core.localDatabase; } diff --git a/src/modules/AbstractObsidianModule.ts b/src/modules/AbstractObsidianModule.ts index acc635e..cf2ab2e 100644 --- a/src/modules/AbstractObsidianModule.ts +++ b/src/modules/AbstractObsidianModule.ts @@ -10,11 +10,6 @@ export type ModuleKeys = keyof IObsidianModule; export type ChainableModuleProps = ChainableExecuteFunction; export abstract class AbstractObsidianModule extends AbstractModule { - addCommand = this.services.API.addCommand.bind(this.services.API); - registerView = this.services.API.registerWindow.bind(this.services.API); - addRibbonIcon = this.services.API.addRibbonIcon.bind(this.services.API); - registerObsidianProtocolHandler = this.services.API.registerProtocolHandler.bind(this.services.API); - get app() { return this.plugin.app; } diff --git a/src/modules/core/ModuleTargetFilter.ts b/src/modules/core/ModuleTargetFilter.ts index 93816ab..8aa6ff7 100644 --- a/src/modules/core/ModuleTargetFilter.ts +++ b/src/modules/core/ModuleTargetFilter.ts @@ -1,146 +1,155 @@ -import { LRUCache } from "octagonal-wheels/memory/LRUCache"; -import { getStoragePathFromUXFileInfo, useMemo } from "../../common/utils"; -import { - LOG_LEVEL_VERBOSE, - type FilePathWithPrefix, - type ObsidianLiveSyncSettings, - type UXFileInfoStub, -} from "../../lib/src/common/types"; +import { getStoragePathFromUXFileInfo } from "../../common/utils"; +import { LOG_LEVEL_DEBUG, LOG_LEVEL_VERBOSE, type UXFileInfoStub } from "../../lib/src/common/types"; import { isAcceptedAll } from "../../lib/src/string_and_binary/path"; import { AbstractModule } from "../AbstractModule"; -import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events"; -import { isDirty } from "../../lib/src/common/utils"; import type { LiveSyncCore } from "../../main"; +import { Computed } from "octagonal-wheels/dataobject/Computed"; export class ModuleTargetFilter extends AbstractModule { - reloadIgnoreFiles() { + ignoreFiles: string[] = []; + private refreshSettings() { this.ignoreFiles = this.settings.ignoreFiles.split(",").map((e) => e.trim()); + return Promise.resolve(true); } + private _everyOnload(): Promise { - this.reloadIgnoreFiles(); - eventHub.onEvent(EVENT_SETTING_SAVED, (evt: ObsidianLiveSyncSettings) => { - this.reloadIgnoreFiles(); - }); - eventHub.onEvent(EVENT_REQUEST_RELOAD_SETTING_TAB, () => { - this.reloadIgnoreFiles(); - }); + void this.refreshSettings(); return Promise.resolve(true); } _markFileListPossiblyChanged(): void { this.totalFileEventCount++; } - totalFileEventCount = 0; - get fileListPossiblyChanged() { - if (isDirty("totalFileEventCount", this.totalFileEventCount)) { - return true; - } - return false; - } - private async _isTargetFile(file: string | UXFileInfoStub, keepFileCheckList = false) { - const fileCount = useMemo>( - { - key: "fileCount", // forceUpdate: !keepFileCheckList, - }, - (ctx, prev) => { - if (keepFileCheckList && prev) return prev; - if (!keepFileCheckList && prev && !this.fileListPossiblyChanged) { - return prev; + fileCountMap = new Computed({ + evaluation: (fileEventCount: number) => { + const vaultFiles = this.core.storageAccess.getFileNames().sort(); + const fileCountMap: Record = {}; + for (const file of vaultFiles) { + const lc = file.toLowerCase(); + if (!fileCountMap[lc]) { + fileCountMap[lc] = 1; + } else { + fileCountMap[lc]++; } - const fileList = (ctx.get("fileList") ?? []) as FilePathWithPrefix[]; - // const fileNameList = (ctx.get("fileNameList") ?? []) as FilePath[]; - // const fileNames = - const vaultFiles = this.core.storageAccess.getFileNames().sort(); - if (prev && vaultFiles.length == fileList.length) { - const fl3 = new Set([...fileList, ...vaultFiles]); - if (fileList.length == fl3.size && vaultFiles.length == fl3.size) { - return prev; - } - } - ctx.set("fileList", vaultFiles); - - const fileCount: Record = {}; - for (const file of vaultFiles) { - const lc = file.toLowerCase(); - if (!fileCount[lc]) { - fileCount[lc] = 1; - } else { - fileCount[lc]++; - } - } - return fileCount; } - ); + return fileCountMap; + }, + requiresUpdate: (args, previousArgs, previousResult) => { + if (!previousResult) return true; + if (previousResult instanceof Error) return true; + if (!previousArgs) return true; + if (args[0] === previousArgs[0]) { + return false; + } + return true; + }, + }); + + totalFileEventCount = 0; + + private async _isTargetFileByFileNameDuplication(file: string | UXFileInfoStub) { + await this.fileCountMap.updateValue(this.totalFileEventCount); + const fileCountMap = this.fileCountMap.value; + if (!fileCountMap) { + this._log("File count map is not ready yet."); + return false; + } const filepath = getStoragePathFromUXFileInfo(file); const lc = filepath.toLowerCase(); if (this.services.vault.shouldCheckCaseInsensitively()) { - if (lc in fileCount && fileCount[lc] > 1) { + if (lc in fileCountMap && fileCountMap[lc] > 1) { + this._log("File is duplicated (case-insensitive): " + filepath); return false; } } - const fileNameLC = getStoragePathFromUXFileInfo(file).split("/").pop()?.toLowerCase(); - if (this.settings.useIgnoreFiles) { - if (this.ignoreFiles.some((e) => e.toLowerCase() == fileNameLC)) { - // We must reload ignore files due to the its change. - await this.readIgnoreFile(filepath); - } - if (await this.services.vault.isIgnoredByIgnoreFile(file)) { - return false; - } - } - if (!this.localDatabase?.isTargetFile(filepath)) return false; + this._log("File is not duplicated: " + filepath, LOG_LEVEL_DEBUG); return true; } - ignoreFileCache = new LRUCache(300, 250000, true); - ignoreFiles = [] as string[]; - async readIgnoreFile(path: string) { + private ignoreFileCacheMap = new Map(); + + private invalidateIgnoreFileCache(path: string) { + // This erases `/path/to/.ignorefile` from cache, therefore, next access will reload it. + // When detecting edited the ignore file, this method should be called. + // Do not check whether it exists in cache or not; just delete it. + const key = path.toLowerCase(); + this.ignoreFileCacheMap.delete(key); + } + private async getIgnoreFile(path: string): Promise { + const key = path.toLowerCase(); + const cached = this.ignoreFileCacheMap.get(key); + if (cached !== undefined) { + // if cached is not undefined, cache hit (neither exists or not exists, string[] or false). + return cached; + } try { - // this._log(`[ignore]Reading ignore file: ${path}`, LOG_LEVEL_VERBOSE); + // load the ignore file if (!(await this.core.storageAccess.isExistsIncludeHidden(path))) { - this.ignoreFileCache.set(path, false); - // this._log(`[ignore]Ignore file not found: ${path}`, LOG_LEVEL_VERBOSE); + // file does not exist, cache as not exists + this.ignoreFileCacheMap.set(key, false); return false; } const file = await this.core.storageAccess.readHiddenFileText(path); - const gitignore = file.split(/\r?\n/g); - this.ignoreFileCache.set(path, gitignore); - this._log(`[ignore]Ignore file loaded: ${path}`, LOG_LEVEL_VERBOSE); + const gitignore = file + .split(/\r?\n/g) + .map((e) => e.replace(/\r$/, "")) + .map((e) => e.trim()); + this.ignoreFileCacheMap.set(key, gitignore); + this._log(`[ignore] Ignore file loaded: ${path}`, LOG_LEVEL_VERBOSE); return gitignore; } catch (ex) { - this._log(`[ignore]Failed to read ignore file ${path}`); + // Failed to read the ignore file, delete cache. + this._log(`[ignore] Failed to read ignore file ${path}`); this._log(ex, LOG_LEVEL_VERBOSE); - this.ignoreFileCache.set(path, false); + this.ignoreFileCacheMap.set(key, undefined); return false; } } - async getIgnoreFile(path: string) { - if (this.ignoreFileCache.has(path)) { - return this.ignoreFileCache.get(path) ?? false; - } else { - return await this.readIgnoreFile(path); - } - } - private async _isIgnoredByIgnoreFiles(file: string | UXFileInfoStub): Promise { - if (!this.settings.useIgnoreFiles) { - return false; - } + + private async _isTargetFileByLocalDB(file: string | UXFileInfoStub) { const filepath = getStoragePathFromUXFileInfo(file); - if (this.ignoreFileCache.has(filepath)) { - // Renew - await this.readIgnoreFile(filepath); + if (!this.localDatabase?.isTargetFile(filepath)) { + this._log("File is not target by local DB: " + filepath); + return false; } - if (!(await isAcceptedAll(filepath, this.ignoreFiles, (filename) => this.getIgnoreFile(filename)))) { + this._log("File is target by local DB: " + filepath, LOG_LEVEL_DEBUG); + return await Promise.resolve(true); + } + + private async _isTargetFileFinal(file: string | UXFileInfoStub) { + this._log("File is target finally: " + getStoragePathFromUXFileInfo(file), LOG_LEVEL_DEBUG); + return await Promise.resolve(true); + } + + private async _isTargetIgnoredByIgnoreFiles(file: string | UXFileInfoStub): Promise { + if (!this.settings.useIgnoreFiles) { return true; } - return false; + const filepath = getStoragePathFromUXFileInfo(file); + this.invalidateIgnoreFileCache(filepath); + this._log("Checking ignore files for: " + filepath, LOG_LEVEL_DEBUG); + if (!(await isAcceptedAll(filepath, this.ignoreFiles, (filename) => this.getIgnoreFile(filename)))) { + this._log("File is ignored by ignore files: " + filepath); + return false; + } + this._log("File is not ignored by ignore files: " + filepath, LOG_LEVEL_DEBUG); + return true; } onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.vault.markFileListPossiblyChanged.setHandler(this._markFileListPossiblyChanged.bind(this)); services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this)); - services.vault.isIgnoredByIgnoreFile.setHandler(this._isIgnoredByIgnoreFiles.bind(this)); - services.vault.isTargetFile.setHandler(this._isTargetFile.bind(this)); + services.vault.isIgnoredByIgnoreFile.setHandler(this._isTargetIgnoredByIgnoreFiles.bind(this)); + services.vault.isTargetFile.addHandler(this._isTargetFileByFileNameDuplication.bind(this)); + services.vault.isTargetFile.addHandler(this._isTargetIgnoredByIgnoreFiles.bind(this)); + services.vault.isTargetFile.addHandler(this._isTargetFileByLocalDB.bind(this)); + services.vault.isTargetFile.addHandler(this._isTargetFileFinal.bind(this)); + services.setting.onSettingRealised.addHandler(this.refreshSettings.bind(this)); + // services.vault.isTargetFile.use((ctx, next) => { + // const [fileName, keepFileCheckList] = ctx.args; + // const file = getS + + // }); } } diff --git a/src/modules/coreObsidian/ModuleFileAccessObsidian.ts b/src/modules/coreObsidian/ModuleFileAccessObsidian.ts index 369c008..06049f9 100644 --- a/src/modules/coreObsidian/ModuleFileAccessObsidian.ts +++ b/src/modules/coreObsidian/ModuleFileAccessObsidian.ts @@ -56,10 +56,6 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements restoreState() { return this.vaultManager.restoreState(); } - private _everyOnload(): Promise { - this.core.storageAccess = this; - return Promise.resolve(true); - } async _everyOnFirstInitialize(): Promise { await this.vaultManager.beginWatch(); return Promise.resolve(true); @@ -76,6 +72,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements _everyOnloadStart(): Promise { this.vaultAccess = new SerializedFileAccess(this.app, this.plugin, this); + this.core.storageAccess = this; return Promise.resolve(true); } @@ -379,7 +376,6 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { services.appLifecycle.onFirstInitialise.addHandler(this._everyOnFirstInitialize.bind(this)); services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); - services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this)); services.fileProcessing.commitPendingFileEvents.addHandler(this._everyCommitPendingFileEvent.bind(this)); } } diff --git a/src/modules/essentialObsidian/ModuleCheckRemoteSize.ts b/src/modules/essentialObsidian/ModuleCheckRemoteSize.ts index 136ef08..af67bf3 100644 --- a/src/modules/essentialObsidian/ModuleCheckRemoteSize.ts +++ b/src/modules/essentialObsidian/ModuleCheckRemoteSize.ts @@ -2,10 +2,10 @@ import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-w import { sizeToHumanReadable } from "octagonal-wheels/number"; import { $msg } from "src/lib/src/common/i18n.ts"; import type { LiveSyncCore } from "../../main.ts"; -import { AbstractObsidianModule } from "../AbstractObsidianModule.ts"; import { EVENT_REQUEST_CHECK_REMOTE_SIZE, eventHub } from "@/common/events.ts"; +import { AbstractModule } from "../AbstractModule.ts"; -export class ModuleCheckRemoteSize extends AbstractObsidianModule { +export class ModuleCheckRemoteSize extends AbstractModule { checkRemoteSize(): Promise { this.settings.notifyThresholdOfRemoteStorageSize = 1; return this._allScanStat(); diff --git a/src/modules/essentialObsidian/ModuleObsidianEvents.ts b/src/modules/essentialObsidian/ModuleObsidianEvents.ts index fb896ad..ccd2c74 100644 --- a/src/modules/essentialObsidian/ModuleObsidianEvents.ts +++ b/src/modules/essentialObsidian/ModuleObsidianEvents.ts @@ -31,13 +31,8 @@ export class ModuleObsidianEvents extends AbstractObsidianModule { return Promise.resolve(true); } - private _performRestart(): void { - this.__performAppReload(); - } - __performAppReload() { - //@ts-ignore - this.app.commands.executeCommandById("app:reload"); + this.services.appLifecycle.performRestart(); } initialCallback: any; @@ -193,6 +188,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule { } }); } + // TODO: separate private _scheduleAppReload() { if (!this.core._totalProcessingCount) { const __tick = reactiveSource(0); @@ -246,7 +242,6 @@ export class ModuleObsidianEvents extends AbstractObsidianModule { onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.appLifecycle.onLayoutReady.addHandler(this._everyOnLayoutReady.bind(this)); services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); - services.appLifecycle.performRestart.setHandler(this._performRestart.bind(this)); services.appLifecycle.askRestart.setHandler(this._askReload.bind(this)); services.appLifecycle.scheduleRestart.setHandler(this._scheduleAppReload.bind(this)); } diff --git a/src/modules/essentialObsidian/ModuleObsidianMenu.ts b/src/modules/essentialObsidian/ModuleObsidianMenu.ts index 736ae10..0e63e9e 100644 --- a/src/modules/essentialObsidian/ModuleObsidianMenu.ts +++ b/src/modules/essentialObsidian/ModuleObsidianMenu.ts @@ -1,11 +1,11 @@ import { fireAndForget } from "octagonal-wheels/promises"; import { addIcon, type Editor, type MarkdownFileInfo, type MarkdownView } from "../../deps.ts"; import { LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "../../lib/src/common/types.ts"; -import { AbstractObsidianModule } from "../AbstractObsidianModule.ts"; import { $msg } from "src/lib/src/common/i18n.ts"; import type { LiveSyncCore } from "../../main.ts"; +import { AbstractModule } from "../AbstractModule.ts"; -export class ModuleObsidianMenu extends AbstractObsidianModule { +export class ModuleObsidianMenu extends AbstractModule { _everyOnloadStart(): Promise { // UI addIcon( @@ -105,16 +105,8 @@ export class ModuleObsidianMenu extends AbstractObsidianModule { }); return Promise.resolve(true); } - private __onWorkspaceReady() { - void this.services.appLifecycle.onReady(); - } - private _everyOnload(): Promise { - this.app.workspace.onLayoutReady(this.__onWorkspaceReady.bind(this)); - return Promise.resolve(true); - } onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); - services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this)); } } diff --git a/src/modules/features/ModuleLog.ts b/src/modules/features/ModuleLog.ts index 4c6d1d2..b7c9407 100644 --- a/src/modules/features/ModuleLog.ts +++ b/src/modules/features/ModuleLog.ts @@ -63,8 +63,6 @@ function addLog(log: string) { const showDebugLog = false; export const MARK_DONE = "\u{2009}\u{2009}"; export class ModuleLog extends AbstractObsidianModule { - registerView = this.plugin.registerView.bind(this.plugin); - statusBar?: HTMLElement; statusDiv?: HTMLElement; diff --git a/src/modules/features/ModuleObsidianSetting.ts b/src/modules/features/ModuleObsidianSetting.ts index 873479c..c190242 100644 --- a/src/modules/features/ModuleObsidianSetting.ts +++ b/src/modules/features/ModuleObsidianSetting.ts @@ -1,4 +1,3 @@ -import { AbstractObsidianModule } from "../AbstractObsidianModule.ts"; // import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser"; import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts"; import { @@ -18,7 +17,8 @@ import { getLanguage } from "@/deps.ts"; import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "../../lib/src/common/rosetta.ts"; import { decryptString, encryptString } from "@/lib/src/encryption/stringEncryption.ts"; import type { LiveSyncCore } from "../../main.ts"; -export class ModuleObsidianSettings extends AbstractObsidianModule { +import { AbstractModule } from "../AbstractModule.ts"; +export class ModuleObsidianSettings extends AbstractModule { async _everyOnLayoutReady(): Promise { let isChanged = false; if (this.settings.displayLanguage == "") { @@ -105,7 +105,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule { } get appId() { - return `${"appId" in this.app ? this.app.appId : ""}`; + return this.services.API.getAppID(); } async _saveSettingData() { diff --git a/src/modules/features/ModuleObsidianSettingAsMarkdown.ts b/src/modules/features/ModuleObsidianSettingAsMarkdown.ts index c84534f..12dd74e 100644 --- a/src/modules/features/ModuleObsidianSettingAsMarkdown.ts +++ b/src/modules/features/ModuleObsidianSettingAsMarkdown.ts @@ -1,4 +1,3 @@ -import { AbstractObsidianModule } from "../AbstractObsidianModule.ts"; // import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser"; import { isObjectDifferent } from "octagonal-wheels/object"; import { EVENT_SETTING_SAVED, eventHub } from "../../common/events"; @@ -6,9 +5,13 @@ import { fireAndForget } from "octagonal-wheels/promises"; import { DEFAULT_SETTINGS, type FilePathWithPrefix, type ObsidianLiveSyncSettings } from "../../lib/src/common/types"; import { parseYaml, stringifyYaml } from "../../deps"; import { LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger"; +import { AbstractModule } from "../AbstractModule.ts"; +import type { ServiceContext } from "@/lib/src/services/base/ServiceBase.ts"; +import type { InjectableServiceHub } from "@/lib/src/services/InjectableServices.ts"; +import type { LiveSyncCore } from "@/main.ts"; const SETTING_HEADER = "````yaml:livesync-setting\n"; const SETTING_FOOTER = "\n````"; -export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule { +export class ModuleObsidianSettingsAsMarkdown extends AbstractModule { _everyOnloadStart(): Promise { this.addCommand({ id: "livesync-export-config", @@ -242,7 +245,8 @@ We can perform a command in this file. this._log(`Markdown setting: ${filename} has been updated!`, LOG_LEVEL_VERBOSE); } } - onBindFunction(core: typeof this.plugin, services: typeof core.services): void { + + onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); } } diff --git a/src/modules/features/ModuleSetupObsidian.ts b/src/modules/features/ModuleSetupObsidian.ts index ca466af..b66d244 100644 --- a/src/modules/features/ModuleSetupObsidian.ts +++ b/src/modules/features/ModuleSetupObsidian.ts @@ -9,7 +9,6 @@ import { EVENT_REQUEST_SHOW_SETUP_QR, eventHub, } from "../../common/events.ts"; -import { AbstractObsidianModule } from "../AbstractObsidianModule.ts"; import { $msg } from "../../lib/src/common/i18n.ts"; // import { performDoctorConsultation, RebuildOptions } from "@/lib/src/common/configForDoc.ts"; import type { LiveSyncCore } from "../../main.ts"; @@ -20,11 +19,12 @@ import { OutputFormat, } from "../../lib/src/API/processSetting.ts"; import { SetupManager, UserMode } from "./SetupManager.ts"; +import { AbstractModule } from "../AbstractModule.ts"; -export class ModuleSetupObsidian extends AbstractObsidianModule { +export class ModuleSetupObsidian extends AbstractModule { private _setupManager!: SetupManager; private _everyOnload(): Promise { - this._setupManager = this.plugin.getModule(SetupManager); + this._setupManager = this.core.getModule(SetupManager); this.registerObsidianProtocolHandler("setuplivesync", async (conf: any) => { if (conf.settings) { await this._setupManager.onUseSetupURI( diff --git a/src/modules/features/SetupManager.ts b/src/modules/features/SetupManager.ts index 6691338..02eeffc 100644 --- a/src/modules/features/SetupManager.ts +++ b/src/modules/features/SetupManager.ts @@ -8,7 +8,6 @@ import { REMOTE_P2P, } from "../../lib/src/common/types.ts"; import { generatePatchObj, isObjectDifferent } from "../../lib/src/common/utils.ts"; -import { AbstractObsidianModule } from "../AbstractObsidianModule.ts"; import Intro from "./SetupWizard/dialogs/Intro.svelte"; import SelectMethodNewUser from "./SetupWizard/dialogs/SelectMethodNewUser.svelte"; import SelectMethodExisting from "./SetupWizard/dialogs/SelectMethodExisting.svelte"; @@ -23,6 +22,7 @@ import SetupRemoteBucket from "./SetupWizard/dialogs/SetupRemoteBucket.svelte"; import SetupRemoteP2P from "./SetupWizard/dialogs/SetupRemoteP2P.svelte"; import SetupRemoteE2EE from "./SetupWizard/dialogs/SetupRemoteE2EE.svelte"; import { decodeSettingsFromQRCodeData } from "../../lib/src/API/processSetting.ts"; +import { AbstractModule } from "../AbstractModule.ts"; /** * User modes for onboarding and setup @@ -50,7 +50,7 @@ export const enum UserMode { /** * Setup Manager to handle onboarding and configuration setup */ -export class SetupManager extends AbstractObsidianModule { +export class SetupManager extends AbstractModule { // /** // * Dialog manager for handling Svelte dialogs // */ diff --git a/src/modules/services/ObsidianAppLifecycleService.ts b/src/modules/services/ObsidianAppLifecycleService.ts new file mode 100644 index 0000000..12d6ede --- /dev/null +++ b/src/modules/services/ObsidianAppLifecycleService.ts @@ -0,0 +1,21 @@ +import { AppLifecycleServiceBase } from "@/lib/src/services/implements/injectable/InjectableAppLifecycleService"; +import type { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext"; +declare module "obsidian" { + interface App { + commands: { + executeCommandById: (id: string) => Promise; + }; + } +} +// InjectableAppLifecycleService +export class ObsidianAppLifecycleService extends AppLifecycleServiceBase { + constructor(context: T) { + super(context); + // The main entry point when Obsidian's workspace is ready + const onReady = this.onReady; + this.context.app.workspace.onLayoutReady(onReady); + } + performRestart(): void { + void this.context.plugin.app.commands.executeCommandById("app:reload"); + } +} diff --git a/src/modules/services/ObsidianServiceHub.ts b/src/modules/services/ObsidianServiceHub.ts index 81319eb..4a3a9f3 100644 --- a/src/modules/services/ObsidianServiceHub.ts +++ b/src/modules/services/ObsidianServiceHub.ts @@ -4,7 +4,6 @@ import type { ServiceInstances } from "@/lib/src/services/ServiceHub"; import type ObsidianLiveSyncPlugin from "@/main"; import { ObsidianAPIService, - ObsidianAppLifecycleService, ObsidianConflictService, ObsidianDatabaseService, ObsidianFileProcessingService, @@ -17,6 +16,7 @@ import { ObsidianDatabaseEventService, ObsidianConfigService, } from "./ObsidianServices"; +import { ObsidianAppLifecycleService } from "./ObsidianAppLifecycleService"; import { ObsidianPathService } from "./ObsidianPathService"; import { ObsidianVaultService } from "./ObsidianVaultService"; import { ObsidianUIService } from "./ObsidianUIService"; diff --git a/src/modules/services/ObsidianServices.ts b/src/modules/services/ObsidianServices.ts index 551e1fb..2f16c0d 100644 --- a/src/modules/services/ObsidianServices.ts +++ b/src/modules/services/ObsidianServices.ts @@ -1,5 +1,4 @@ import { InjectableAPIService } from "@lib/services/implements/injectable/InjectableAPIService"; -import { InjectableAppLifecycleService } from "@lib/services/implements/injectable/InjectableAppLifecycleService"; import { InjectableConflictService } from "@lib/services/implements/injectable/InjectableConflictService"; import { InjectableDatabaseEventService } from "@lib/services/implements/injectable/InjectableDatabaseEventService"; import { InjectableDatabaseService } from "@lib/services/implements/injectable/InjectableDatabaseService"; @@ -123,8 +122,6 @@ export class ObsidianReplicationService extends InjectableReplicationService {} // InjectableConflictService export class ObsidianConflictService extends InjectableConflictService {} -// InjectableAppLifecycleService -export class ObsidianAppLifecycleService extends InjectableAppLifecycleService {} // InjectableSettingService export class ObsidianSettingService extends InjectableSettingService {} // InjectableTweakValueService diff --git a/updates.md b/updates.md index 8c78353..b686c34 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,50 @@ 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.43-patched-1 + +13th February, 2026 + +You know this is a patch version, is the beta-release practically? Do not worry about the following memos, as they are indeed freaking us out. I trust that you have thought this was too large; you're right. + +If this cannot be stable, I will revert to 0.24.43 and try again. + +### Refactored + +- Now resolving unexpected and inexplicable dependency order issues... +- The function which is able to implement to the service is now moved to each service. + - AppLifecycleService.performRestart +- VaultService.isTargetFile is now uses separated multiple checkers instead of a single function. + - This change allows better separation of concerns and easier extension in the future. +- Application LifeCycle has now started in ServiceHub, not ObsidianMenuModule. + + - It was on a QUITE unexpected place..., isn't it? + - Instead of, we should call `await this.services.appLifecycle.onReady()` in other platforms. + - As in browser platform, it will be called at `DOMContentLoaded` event. + +- ModuleTargetFilter, which is responsible for parsing ignore files has been refined. + - This should be separated to a TargetFilter and a IgnoreFileFilter for better maintainability. +- Using `API.addCommand` or some Obsidian API and shimmer APIs, Many modules have been refactored to be derived to AbstractModule from AbstractObsidianModule, to clarify the dependencies. (we should make `app` usage clearer...) +- Fixed initialising `storageAccess` too late in `FileAccessObsidian` module (I am still wonder why it worked before...). +- Remove some redundant overrides in modules. + +### Planned + +- Some services have ambiguous name, such as `Injectable`. These will be renamed in the future for better clarity. +- Following properties of `ObsidianLiveSyncPlugin` should be initialised more explicitly: + - property : where it is initialised currently + - `localDatabase` : `ModuleLocalDatabaseObsidian` + - `managers` : `ModuleLocalDatabaseObsidian` + - `replicator` : `ModuleReplicator` + - `simpleStore` : `ModuleKeyValueDB` + - `storageAccess` : `ModuleFileAccessObsidian` + - `databaseFileAccess` : `ModuleDatabaseFileAccess` + - `fileHandler` : `ModuleFileHandler` + - `rebuilder` : `ModuleRebuilder` + - `kvDB`: `ModuleKeyValueDB` + - And I think that having feature in modules directly is not good for maintainability, these should be separated to some module (loader) and implementation (not only service, but also independent something). +- Plug-in statuses such as requestCount, responseCount... should be moved to a status service or somewhere for better separation of concerns. + ## 0.25.43 5th, February, 2026 @@ -17,7 +61,6 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid Quite a few packages have been updated in this release. Please report if you find any unexpected behaviour after this update. - ## 0.25.42 2nd, February, 2026 From 6e9ac6a9f916c1c20ecc2c1ede054159123cb7e2 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 14 Feb 2026 15:21:00 +0900 Subject: [PATCH 004/339] - Application LifeCycle has now started in Main, not ServiceHub. --- manifest.json | 2 +- package-lock.json | 4 +-- package.json | 2 +- src/lib | 2 +- src/main.ts | 7 ++++- .../essential/ModuleInitializerFile.ts | 4 +-- src/modules/essential/ModuleMigration.ts | 2 +- .../essentialObsidian/ModuleObsidianEvents.ts | 2 +- .../services/ObsidianAppLifecycleService.ts | 6 ---- updates.md | 29 ++++++++++++------- 10 files changed, 34 insertions(+), 26 deletions(-) diff --git a/manifest.json b/manifest.json index dbcefb4..795a095 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.43-patched-1", + "version": "0.25.43-patched-2", "minAppVersion": "0.9.12", "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 3b8b0ce..2f52bcb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-1", + "version": "0.25.43-patched-2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.43-patched-1", + "version": "0.25.43-patched-2", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 6f2c734..2362ec4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-1", + "version": "0.25.43-patched-2", "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 532f25f..69e7a51 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 532f25f94784e46edd98438edee61a632ed53072 +Subproject commit 69e7a510f19c2aeea53d9862680c133fd9e37c70 diff --git a/src/main.ts b/src/main.ts index 936a8be..856914a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -205,8 +205,13 @@ export default class ObsidianLiveSyncPlugin syncStatus: "CLOSED" as DatabaseConnectingStatus, }); + private async _startUp() { + await this.services.appLifecycle.onLoad(); + const onReady = this.services.appLifecycle.onReady.bind(this.services.appLifecycle); + this.app.workspace.onLayoutReady(onReady); + } onload() { - void this.services.appLifecycle.onLoad(); + void this._startUp(); } async saveSettings() { await this.services.setting.saveSettingData(); diff --git a/src/modules/essential/ModuleInitializerFile.ts b/src/modules/essential/ModuleInitializerFile.ts index d170a01..ed6d661 100644 --- a/src/modules/essential/ModuleInitializerFile.ts +++ b/src/modules/essential/ModuleInitializerFile.ts @@ -78,7 +78,7 @@ export class ModuleInitializerFile extends AbstractModule { const _filesStorage = [] as typeof filesStorageSrc; for (const f of filesStorageSrc) { - if (await this.services.vault.isTargetFile(f.path, f != filesStorageSrc[0])) { + if (await this.services.vault.isTargetFile(f.path)) { _filesStorage.push(f); } } @@ -122,7 +122,7 @@ export class ModuleInitializerFile extends AbstractModule { ); const path = this.getPath(doc); - if (isValidPath(path) && (await this.services.vault.isTargetFile(path, true))) { + if (isValidPath(path) && (await this.services.vault.isTargetFile(path))) { if (!isMetaEntry(doc)) { this._log(`Invalid entry: ${path}`, LOG_LEVEL_INFO); continue; diff --git a/src/modules/essential/ModuleMigration.ts b/src/modules/essential/ModuleMigration.ts index 90aca81..e6c03df 100644 --- a/src/modules/essential/ModuleMigration.ts +++ b/src/modules/essential/ModuleMigration.ts @@ -133,7 +133,7 @@ export class ModuleMigration extends AbstractModule { if (!isValidPath(path)) { continue; } - if (!(await this.services.vault.isTargetFile(path, true))) { + if (!(await this.services.vault.isTargetFile(path))) { continue; } if (!isMetaEntry(metaDoc)) { diff --git a/src/modules/essentialObsidian/ModuleObsidianEvents.ts b/src/modules/essentialObsidian/ModuleObsidianEvents.ts index ccd2c74..7db93a2 100644 --- a/src/modules/essentialObsidian/ModuleObsidianEvents.ts +++ b/src/modules/essentialObsidian/ModuleObsidianEvents.ts @@ -69,7 +69,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule { //@ts-ignore window.CodeMirrorAdapter.commands.save = () => { //@ts-ignore - _this.app.commands.executeCommandById("editor:save-file"); + void _this.app.commands.executeCommandById("editor:save-file"); // _this.app.performCommand('editor:save-file'); }; } diff --git a/src/modules/services/ObsidianAppLifecycleService.ts b/src/modules/services/ObsidianAppLifecycleService.ts index 12d6ede..b751bcb 100644 --- a/src/modules/services/ObsidianAppLifecycleService.ts +++ b/src/modules/services/ObsidianAppLifecycleService.ts @@ -9,12 +9,6 @@ declare module "obsidian" { } // InjectableAppLifecycleService export class ObsidianAppLifecycleService extends AppLifecycleServiceBase { - constructor(context: T) { - super(context); - // The main entry point when Obsidian's workspace is ready - const onReady = this.onReady; - this.context.app.workspace.onLayoutReady(onReady); - } performRestart(): void { void this.context.plugin.app.commands.executeCommandById("app:reload"); } diff --git a/updates.md b/updates.md index b686c34..e7180b2 100644 --- a/updates.md +++ b/updates.md @@ -3,11 +3,21 @@ 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.43-patched-2 + +14th February, 2026 + +### Fixed +- Application LifeCycle has now started in Main, not ServiceHub. + - Indeed, ServiceHub cannot be known other things in main have got ready, so it is quite natural to start the lifecycle in main. + ## 0.25.43-patched-1 13th February, 2026 -You know this is a patch version, is the beta-release practically? Do not worry about the following memos, as they are indeed freaking us out. I trust that you have thought this was too large; you're right. +**NOTE: Hidden File Sync and Customisation Sync may not work in this version.** + +Just a heads-up: this is a patch version, which is essentially a beta release. Do not worry about the following memos, as they are indeed freaking us out. I trust that you have thought this was too large; you're right. If this cannot be stable, I will revert to 0.24.43 and try again. @@ -16,23 +26,22 @@ If this cannot be stable, I will revert to 0.24.43 and try again. - Now resolving unexpected and inexplicable dependency order issues... - The function which is able to implement to the service is now moved to each service. - AppLifecycleService.performRestart -- VaultService.isTargetFile is now uses separated multiple checkers instead of a single function. +- VaultService.isTargetFile is now using multiple checkers instead of a single function. - This change allows better separation of concerns and easier extension in the future. - Application LifeCycle has now started in ServiceHub, not ObsidianMenuModule. - - - It was on a QUITE unexpected place..., isn't it? + - It was in a QUITE unexpected place..., isn't it? - Instead of, we should call `await this.services.appLifecycle.onReady()` in other platforms. - - As in browser platform, it will be called at `DOMContentLoaded` event. + - As in the browser platform, it will be called at `DOMContentLoaded` event. -- ModuleTargetFilter, which is responsible for parsing ignore files has been refined. - - This should be separated to a TargetFilter and a IgnoreFileFilter for better maintainability. +- ModuleTargetFilter, which is responsible for parsing ignore files, has been refined. + - This should be separated to a TargetFilter and an IgnoreFileFilter for better maintainability. - Using `API.addCommand` or some Obsidian API and shimmer APIs, Many modules have been refactored to be derived to AbstractModule from AbstractObsidianModule, to clarify the dependencies. (we should make `app` usage clearer...) -- Fixed initialising `storageAccess` too late in `FileAccessObsidian` module (I am still wonder why it worked before...). +- Fixed initialising `storageAccess` too late in `FileAccessObsidian` module (I am still wondering why it worked before...). - Remove some redundant overrides in modules. ### Planned -- Some services have ambiguous name, such as `Injectable`. These will be renamed in the future for better clarity. +- Some services have an ambiguous name, such as `Injectable`. These will be renamed in the future for better clarity. - Following properties of `ObsidianLiveSyncPlugin` should be initialised more explicitly: - property : where it is initialised currently - `localDatabase` : `ModuleLocalDatabaseObsidian` @@ -44,7 +53,7 @@ If this cannot be stable, I will revert to 0.24.43 and try again. - `fileHandler` : `ModuleFileHandler` - `rebuilder` : `ModuleRebuilder` - `kvDB`: `ModuleKeyValueDB` - - And I think that having feature in modules directly is not good for maintainability, these should be separated to some module (loader) and implementation (not only service, but also independent something). + - And I think that having a feature in modules directly is not good for maintainability, these should be separated to some module (loader) and implementation (not only service, but also independent something). - Plug-in statuses such as requestCount, responseCount... should be moved to a status service or somewhere for better separation of concerns. ## 0.25.43 From e63e3e67255c2cab0c60501f55bb7b7b6f3741b5 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 16 Feb 2026 06:50:31 +0000 Subject: [PATCH 005/339] ### Refactor - Module dependency refined. (For details, please refer to updates.md) --- src/features/LiveSyncCommands.ts | 12 +- src/features/P2PSync/CmdP2PReplicator.ts | 2 +- src/lib | 2 +- src/main.ts | 189 +++++++++++------- src/modules/AbstractModule.ts | 21 +- .../core/ModuleLocalDatabaseObsidian.ts | 46 ----- src/modules/core/ModulePouchDB.ts | 23 --- src/modules/core/ModuleRebuilder.ts | 5 +- src/modules/core/ModuleTargetFilter.ts | 5 - .../essential/ModuleInitializerFile.ts | 8 +- src/modules/essential/ModuleKeyValueDB.ts | 114 ----------- .../ModuleExtraSyncObsidian.ts | 18 -- src/modules/features/ModuleLog.ts | 12 +- src/modules/main/ModuleLiveSyncMain.ts | 5 +- src/modules/services/ObsidianAPIService.ts | 92 +++++++++ .../services/ObsidianDatabaseService.ts | 11 + src/modules/services/ObsidianServiceHub.ts | 17 +- src/modules/services/ObsidianServices.ts | 103 +--------- updates.md | 27 ++- 19 files changed, 300 insertions(+), 412 deletions(-) delete mode 100644 src/modules/core/ModuleLocalDatabaseObsidian.ts delete mode 100644 src/modules/core/ModulePouchDB.ts delete mode 100644 src/modules/essential/ModuleKeyValueDB.ts delete mode 100644 src/modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.ts create mode 100644 src/modules/services/ObsidianAPIService.ts create mode 100644 src/modules/services/ObsidianDatabaseService.ts diff --git a/src/features/LiveSyncCommands.ts b/src/features/LiveSyncCommands.ts index e7db590..17978ac 100644 --- a/src/features/LiveSyncCommands.ts +++ b/src/features/LiveSyncCommands.ts @@ -1,4 +1,4 @@ -import { LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger"; +import { LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger"; import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, @@ -12,6 +12,7 @@ import type ObsidianLiveSyncPlugin from "../main.ts"; import { MARK_DONE } from "../modules/features/ModuleLog.ts"; import type { LiveSyncCore } from "../main.ts"; import { __$checkInstanceBinding } from "../lib/src/dev/checks.ts"; +import { createInstanceLogFunction } from "@/lib/src/services/lib/logUtils.ts"; let noticeIndex = 0; export abstract class LiveSyncCommands { @@ -43,6 +44,7 @@ export abstract class LiveSyncCommands { constructor(plugin: ObsidianLiveSyncPlugin) { this.plugin = plugin; this.onBindFunction(plugin, plugin.services); + this._log = createInstanceLogFunction(this.constructor.name, this.services.API); __$checkInstanceBinding(this); } abstract onunload(): void; @@ -58,13 +60,7 @@ export abstract class LiveSyncCommands { return this.services.database.isDatabaseReady(); } - _log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => { - if (typeof msg === "string" && level !== LOG_LEVEL_NOTICE) { - msg = `[${this.constructor.name}]\u{200A} ${msg}`; - } - // console.log(msg); - Logger(msg, level, key); - }; + _log: ReturnType; _verbose = (msg: any, key?: string) => { this._log(msg, LOG_LEVEL_VERBOSE, key); diff --git a/src/features/P2PSync/CmdP2PReplicator.ts b/src/features/P2PSync/CmdP2PReplicator.ts index 211fd3a..4a691b3 100644 --- a/src/features/P2PSync/CmdP2PReplicator.ts +++ b/src/features/P2PSync/CmdP2PReplicator.ts @@ -107,7 +107,7 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase } init() { - this._simpleStore = this.services.database.openSimpleStore("p2p-sync"); + this._simpleStore = this.services.keyValueDB.openSimpleStore("p2p-sync"); return Promise.resolve(this); } diff --git a/src/lib b/src/lib index 69e7a51..3ae1cba 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 69e7a510f19c2aeea53d9862680c133fd9e37c70 +Subproject commit 3ae1cbabdae286f994807c9da4ac2e937f70d3ee diff --git a/src/main.ts b/src/main.ts index 856914a..8a8d1bd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import { Plugin } from "./deps"; +import { Plugin, type App, type PluginManifest } from "./deps"; import { type EntryDoc, type ObsidianLiveSyncSettings, @@ -6,12 +6,11 @@ import { type HasSettings, } from "./lib/src/common/types.ts"; import { type SimpleStore } from "./lib/src/common/utils.ts"; -import { LiveSyncLocalDB, type LiveSyncLocalDBEnv } from "./lib/src/pouchdb/LiveSyncLocalDB.ts"; +import { type LiveSyncLocalDBEnv } from "./lib/src/pouchdb/LiveSyncLocalDB.ts"; import { LiveSyncAbstractReplicator, type LiveSyncReplicatorEnv, } from "./lib/src/replication/LiveSyncAbstractReplicator.js"; -import { type KeyValueDatabase } from "./lib/src/interfaces/KeyValueDatabase.ts"; import { LiveSyncCommands } from "./features/LiveSyncCommands.ts"; import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts"; import { ConfigSync } from "./features/ConfigSync/CmdConfigSync.ts"; @@ -48,29 +47,23 @@ import { ModuleObsidianDocumentHistory } from "./modules/features/ModuleObsidian import { ModuleObsidianGlobalHistory } from "./modules/features/ModuleGlobalHistory.ts"; import { ModuleObsidianSettingsAsMarkdown } from "./modules/features/ModuleObsidianSettingAsMarkdown.ts"; import { ModuleInitializerFile } from "./modules/essential/ModuleInitializerFile.ts"; -import { ModuleKeyValueDB } from "./modules/essential/ModuleKeyValueDB.ts"; -import { ModulePouchDB } from "./modules/core/ModulePouchDB.ts"; import { ModuleReplicator } from "./modules/core/ModuleReplicator.ts"; import { ModuleReplicatorCouchDB } from "./modules/core/ModuleReplicatorCouchDB.ts"; import { ModuleReplicatorMinIO } from "./modules/core/ModuleReplicatorMinIO.ts"; import { ModuleTargetFilter } from "./modules/core/ModuleTargetFilter.ts"; import { ModulePeriodicProcess } from "./modules/core/ModulePeriodicProcess.ts"; import { ModuleRemoteGovernor } from "./modules/coreFeatures/ModuleRemoteGovernor.ts"; -import { ModuleLocalDatabaseObsidian } from "./modules/core/ModuleLocalDatabaseObsidian.ts"; import { ModuleConflictChecker } from "./modules/coreFeatures/ModuleConflictChecker.ts"; import { ModuleResolvingMismatchedTweaks } from "./modules/coreFeatures/ModuleResolveMismatchedTweaks.ts"; import { ModuleIntegratedTest } from "./modules/extras/ModuleIntegratedTest.ts"; import { ModuleRebuilder } from "./modules/core/ModuleRebuilder.ts"; import { ModuleReplicateTest } from "./modules/extras/ModuleReplicateTest.ts"; import { ModuleLiveSyncMain } from "./modules/main/ModuleLiveSyncMain.ts"; -import { ModuleExtraSyncObsidian } from "./modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.ts"; import { LocalDatabaseMaintenance } from "./features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts"; import { P2PReplicator } from "./features/P2PSync/CmdP2PReplicator.ts"; -import type { LiveSyncManagers } from "./lib/src/managers/LiveSyncManagers.ts"; import type { InjectableServiceHub } from "./lib/src/services/implements/injectable/InjectableServiceHub.ts"; import { ObsidianServiceHub } from "./modules/services/ObsidianServiceHub.ts"; import type { ServiceContext } from "./lib/src/services/base/ServiceBase.ts"; -// import type { InjectableServiceHub } from "./lib/src/services/InjectableServices.ts"; export default class ObsidianLiveSyncPlugin extends Plugin @@ -84,16 +77,36 @@ export default class ObsidianLiveSyncPlugin /** * The service hub for managing all services. */ - _services: InjectableServiceHub = new ObsidianServiceHub(this); + _services: InjectableServiceHub | undefined = undefined; + get services() { + if (!this._services) { + throw new Error("Services not initialised yet"); + } return this._services; } + + private initialiseServices() { + this._services = new ObsidianServiceHub(this); + } + + // Keep order to display the dialogue in order. + addOns = [] as LiveSyncCommands[]; /** * Bind functions to the service hub (for migration purpose). */ // bindFunctions = (this.serviceHub as ObsidianServiceHub).bindFunctions.bind(this.serviceHub); - // --> Module System + private _registerAddOn(addOn: LiveSyncCommands) { + this.addOns.push(addOn); + } + private registerAddOns() { + this._registerAddOn(new ConfigSync(this)); + this._registerAddOn(new HiddenFileSync(this)); + this._registerAddOn(new LocalDatabaseMaintenance(this)); + this._registerAddOn(new P2PReplicator(this)); + } + getAddOn(cls: string) { for (const addon of this.addOns) { if (addon.constructor.name == cls) return addon as T; @@ -101,56 +114,8 @@ export default class ObsidianLiveSyncPlugin return undefined; } - // Keep order to display the dialogue in order. - addOns = [ - new ConfigSync(this), - new HiddenFileSync(this), - new LocalDatabaseMaintenance(this), - new P2PReplicator(this), - ] as LiveSyncCommands[]; - - modules = [ - new ModuleLiveSyncMain(this), - new ModuleExtraSyncObsidian(this, this), - // Only on Obsidian - new ModuleDatabaseFileAccess(this), - // Common - new ModulePouchDB(this), - new ModuleConflictChecker(this), - new ModuleLocalDatabaseObsidian(this), - new ModuleReplicatorMinIO(this), - new ModuleReplicatorCouchDB(this), - new ModuleReplicator(this), - new ModuleFileHandler(this), - new ModuleConflictResolver(this), - new ModuleRemoteGovernor(this), - new ModuleTargetFilter(this), - new ModulePeriodicProcess(this), - // Essential Modules - new ModuleKeyValueDB(this), - new ModuleInitializerFile(this), - new ModuleObsidianAPI(this, this), - new ModuleObsidianEvents(this, this), - new ModuleFileAccessObsidian(this, this), - new ModuleObsidianSettings(this), - new ModuleResolvingMismatchedTweaks(this), - new ModuleObsidianSettingsAsMarkdown(this), - new ModuleObsidianSettingDialogue(this, this), - new ModuleLog(this, this), - new ModuleObsidianMenu(this), - new ModuleRebuilder(this), - new ModuleSetupObsidian(this), - new ModuleObsidianDocumentHistory(this, this), - new ModuleMigration(this), - new ModuleRedFlag(this), - new ModuleInteractiveConflictResolver(this, this), - new ModuleObsidianGlobalHistory(this, this), - new ModuleCheckRemoteSize(this), - // Test and Dev Modules - new ModuleDev(this, this), - new ModuleReplicateTest(this, this), - new ModuleIntegratedTest(this, this), - new SetupManager(this), + private modules = [ + // Move to registerModules ] as (IObsidianModule | AbstractModule)[]; getModule(constructor: new (...args: any[]) => T): T { @@ -159,26 +124,97 @@ export default class ObsidianLiveSyncPlugin } throw new Error(`Module ${constructor} not found or not loaded.`); } + getModulesByType(constructor: new (...args: any[]) => T): T[] { + const matchedModules: T[] = []; + for (const module of this.modules) { + if (module instanceof constructor) matchedModules.push(module); + } + return matchedModules; + } + + private _registerModule(module: IObsidianModule) { + this.modules.push(module); + } + private registerModules() { + this._registerModule(new ModuleLiveSyncMain(this)); + // Only on Obsidian + this._registerModule(new ModuleDatabaseFileAccess(this)); + // Common + this._registerModule(new ModuleConflictChecker(this)); + this._registerModule(new ModuleReplicatorMinIO(this)); + this._registerModule(new ModuleReplicatorCouchDB(this)); + this._registerModule(new ModuleReplicator(this)); + this._registerModule(new ModuleFileHandler(this)); + this._registerModule(new ModuleConflictResolver(this)); + this._registerModule(new ModuleRemoteGovernor(this)); + this._registerModule(new ModuleTargetFilter(this)); + this._registerModule(new ModulePeriodicProcess(this)); + // Essential Modules + this._registerModule(new ModuleInitializerFile(this)); + this._registerModule(new ModuleObsidianAPI(this, this)); + this._registerModule(new ModuleObsidianEvents(this, this)); + this._registerModule(new ModuleFileAccessObsidian(this, this)); + this._registerModule(new ModuleObsidianSettings(this)); + this._registerModule(new ModuleResolvingMismatchedTweaks(this)); + this._registerModule(new ModuleObsidianSettingsAsMarkdown(this)); + this._registerModule(new ModuleObsidianSettingDialogue(this, this)); + this._registerModule(new ModuleLog(this, this)); + this._registerModule(new ModuleObsidianMenu(this)); + this._registerModule(new ModuleRebuilder(this)); + this._registerModule(new ModuleSetupObsidian(this)); + this._registerModule(new ModuleObsidianDocumentHistory(this, this)); + this._registerModule(new ModuleMigration(this)); + this._registerModule(new ModuleRedFlag(this)); + this._registerModule(new ModuleInteractiveConflictResolver(this, this)); + this._registerModule(new ModuleObsidianGlobalHistory(this, this)); + this._registerModule(new ModuleCheckRemoteSize(this)); + // Test and Dev Modules + this._registerModule(new ModuleDev(this, this)); + this._registerModule(new ModuleReplicateTest(this, this)); + this._registerModule(new ModuleIntegratedTest(this, this)); + this._registerModule(new SetupManager(this)); + } - settings!: ObsidianLiveSyncSettings; - localDatabase!: LiveSyncLocalDB; - managers!: LiveSyncManagers; - simpleStore!: SimpleStore; - replicator!: LiveSyncAbstractReplicator; get confirm(): Confirm { return this.services.UI.confirm; } - storageAccess!: StorageAccess; - databaseFileAccess!: DatabaseFileAccess; - fileHandler!: ModuleFileHandler; - rebuilder!: Rebuilder; - kvDB!: KeyValueDatabase; + // This property will be changed from outside often, so will be set later. + settings!: ObsidianLiveSyncSettings; + + getSettings(): ObsidianLiveSyncSettings { + return this.settings; + } + + get localDatabase() { + return this.services.database.localDatabase; + } + + get managers() { + return this.services.database.managers; + } + getDatabase(): PouchDB.Database { return this.localDatabase.localDatabase; } - getSettings(): ObsidianLiveSyncSettings { - return this.settings; + + get simpleStore() { + return this.services.keyValueDB.simpleStore as SimpleStore; + } + + // initialised at ModuleReplicator + replicator!: LiveSyncAbstractReplicator; + // initialised at ModuleFileAccessObsidian + storageAccess!: StorageAccess; + // initialised at ModuleDatabaseFileAccess + databaseFileAccess!: DatabaseFileAccess; + // initialised at ModuleFileHandler + fileHandler!: ModuleFileHandler; + // initialised at ModuleRebuilder + rebuilder!: Rebuilder; + + get kvDB() { + return this.services.keyValueDB.kvDB; } requestCount = reactiveSource(0); @@ -205,6 +241,13 @@ export default class ObsidianLiveSyncPlugin syncStatus: "CLOSED" as DatabaseConnectingStatus, }); + constructor(app: App, manifest: PluginManifest) { + super(app, manifest); + this.initialiseServices(); + this.registerModules(); + this.registerAddOns(); + } + private async _startUp() { await this.services.appLifecycle.onLoad(); const onReady = this.services.appLifecycle.onReady.bind(this.services.appLifecycle); diff --git a/src/modules/AbstractModule.ts b/src/modules/AbstractModule.ts index a354be3..4e63d00 100644 --- a/src/modules/AbstractModule.ts +++ b/src/modules/AbstractModule.ts @@ -1,17 +1,18 @@ -import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger"; -import type { AnyEntry, FilePathWithPrefix, LOG_LEVEL } from "@lib/common/types"; +import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger"; +import type { AnyEntry, FilePathWithPrefix } from "@lib/common/types"; import type { LiveSyncCore } from "@/main"; import { __$checkInstanceBinding } from "@lib/dev/checks"; import { stripAllPrefixes } from "@lib/string_and_binary/path"; +import { createInstanceLogFunction } from "@/lib/src/services/lib/logUtils"; export abstract class AbstractModule { - _log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => { - if (typeof msg === "string" && level !== LOG_LEVEL_NOTICE) { - msg = `[${this.constructor.name}]\u{200A} ${msg}`; + _log = createInstanceLogFunction(this.constructor.name, this.services.API); + get services() { + if (!this.core._services) { + throw new Error("Services are not ready yet."); } - // console.log(msg); - Logger(msg, level, key); - }; + return this.core._services; + } addCommand = this.services.API.addCommand.bind(this.services.API); registerView = this.services.API.registerWindow.bind(this.services.API); @@ -73,10 +74,6 @@ export abstract class AbstractModule { return this.testDone(); } - get services() { - return this.core._services; - } - isMainReady() { return this.services.appLifecycle.isReady(); } diff --git a/src/modules/core/ModuleLocalDatabaseObsidian.ts b/src/modules/core/ModuleLocalDatabaseObsidian.ts deleted file mode 100644 index 412cadc..0000000 --- a/src/modules/core/ModuleLocalDatabaseObsidian.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { $msg } from "../../lib/src/common/i18n"; -import { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB.ts"; -import { initializeStores } from "../../common/stores.ts"; -import { AbstractModule } from "../AbstractModule.ts"; -import { LiveSyncManagers } from "../../lib/src/managers/LiveSyncManagers.ts"; -import type { LiveSyncCore } from "../../main.ts"; - -export class ModuleLocalDatabaseObsidian extends AbstractModule { - _everyOnloadStart(): Promise { - return Promise.resolve(true); - } - private async _openDatabase(): Promise { - if (this.localDatabase != null) { - await this.localDatabase.close(); - } - const vaultName = this.services.vault.getVaultName(); - this._log($msg("moduleLocalDatabase.logWaitingForReady")); - const getDB = () => this.core.localDatabase.localDatabase; - const getSettings = () => this.core.settings; - this.core.managers = new LiveSyncManagers({ - get database() { - return getDB(); - }, - getActiveReplicator: () => this.core.replicator, - id2path: this.services.path.id2path.bind(this.services.path), - // path2id: this.core.$$path2id.bind(this.core), - path2id: this.services.path.path2id.bind(this.services.path), - get settings() { - return getSettings(); - }, - }); - this.core.localDatabase = new LiveSyncLocalDB(vaultName, this.core); - - initializeStores(vaultName); - return await this.localDatabase.initializeDatabase(); - } - - _isDatabaseReady(): boolean { - return this.localDatabase != null && this.localDatabase.isReady; - } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { - services.database.isDatabaseReady.setHandler(this._isDatabaseReady.bind(this)); - services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); - services.database.openDatabase.setHandler(this._openDatabase.bind(this)); - } -} diff --git a/src/modules/core/ModulePouchDB.ts b/src/modules/core/ModulePouchDB.ts deleted file mode 100644 index 821afdc..0000000 --- a/src/modules/core/ModulePouchDB.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AbstractModule } from "../AbstractModule"; -import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser"; -import type { LiveSyncCore } from "../../main"; -import { ExtraSuffixIndexedDB } from "../../lib/src/common/types"; - -export class ModulePouchDB extends AbstractModule { - _createPouchDBInstance( - name?: string, - options?: PouchDB.Configuration.DatabaseConfiguration - ): PouchDB.Database { - const optionPass = options ?? {}; - if (this.settings.useIndexedDBAdapter) { - optionPass.adapter = "indexeddb"; - //@ts-ignore :missing def - optionPass.purged_infos_limit = 1; - return new PouchDB(name + ExtraSuffixIndexedDB, optionPass); - } - return new PouchDB(name, optionPass); - } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { - services.database.createPouchDBInstance.setHandler(this._createPouchDBInstance.bind(this)); - } -} diff --git a/src/modules/core/ModuleRebuilder.ts b/src/modules/core/ModuleRebuilder.ts index 6e5656b..d5fc7dc 100644 --- a/src/modules/core/ModuleRebuilder.ts +++ b/src/modules/core/ModuleRebuilder.ts @@ -224,7 +224,10 @@ Are you sure you wish to proceed?`; await this.services.setting.realiseSetting(); await this.resetLocalDatabase(); await delay(1000); - await this.services.database.openDatabase(); + await this.services.database.openDatabase({ + databaseEvents: this.services.databaseEvents, + replicator: this.services.replicator, + }); // this.core.isReady = true; this.services.appLifecycle.markIsReady(); if (makeLocalChunkBeforeSync) { diff --git a/src/modules/core/ModuleTargetFilter.ts b/src/modules/core/ModuleTargetFilter.ts index 8aa6ff7..4014976 100644 --- a/src/modules/core/ModuleTargetFilter.ts +++ b/src/modules/core/ModuleTargetFilter.ts @@ -146,10 +146,5 @@ export class ModuleTargetFilter extends AbstractModule { services.vault.isTargetFile.addHandler(this._isTargetFileByLocalDB.bind(this)); services.vault.isTargetFile.addHandler(this._isTargetFileFinal.bind(this)); services.setting.onSettingRealised.addHandler(this.refreshSettings.bind(this)); - // services.vault.isTargetFile.use((ctx, next) => { - // const [fileName, keepFileCheckList] = ctx.args; - // const file = getS - - // }); } } diff --git a/src/modules/essential/ModuleInitializerFile.ts b/src/modules/essential/ModuleInitializerFile.ts index ed6d661..e246ca4 100644 --- a/src/modules/essential/ModuleInitializerFile.ts +++ b/src/modules/essential/ModuleInitializerFile.ts @@ -393,7 +393,13 @@ export class ModuleInitializerFile extends AbstractModule { ignoreSuspending: boolean = false ): Promise { this.services.appLifecycle.resetIsReady(); - if (!reopenDatabase || (await this.services.database.openDatabase())) { + if ( + !reopenDatabase || + (await this.services.database.openDatabase({ + databaseEvents: this.services.databaseEvents, + replicator: this.services.replicator, + })) + ) { if (this.localDatabase.isReady) { await this.services.vault.scanVault(showingNotice, ignoreSuspending); } diff --git a/src/modules/essential/ModuleKeyValueDB.ts b/src/modules/essential/ModuleKeyValueDB.ts deleted file mode 100644 index 6d657e8..0000000 --- a/src/modules/essential/ModuleKeyValueDB.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { delay, yieldMicrotask } from "octagonal-wheels/promises"; -import { OpenKeyValueDatabase } from "../../common/KeyValueDB.ts"; -import type { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB.ts"; -import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger"; -import { AbstractModule } from "../AbstractModule.ts"; -import type { LiveSyncCore } from "../../main.ts"; -import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase"; -import type { InjectableServiceHub } from "@/lib/src/services/InjectableServices.ts"; -import type { ObsidianDatabaseService } from "../services/ObsidianServices.ts"; - -export class ModuleKeyValueDB extends AbstractModule { - async tryCloseKvDB() { - try { - await this.core.kvDB?.close(); - return true; - } catch (e) { - this._log("Failed to close KeyValueDB", LOG_LEVEL_VERBOSE); - this._log(e); - return false; - } - } - async openKeyValueDB(): Promise { - await delay(10); - try { - await this.tryCloseKvDB(); - await delay(10); - await yieldMicrotask(); - this.core.kvDB = await OpenKeyValueDatabase(this.services.vault.getVaultName() + "-livesync-kv"); - await yieldMicrotask(); - await delay(100); - } catch (e) { - this.core.kvDB = undefined!; - this._log("Failed to open KeyValueDB", LOG_LEVEL_NOTICE); - this._log(e, LOG_LEVEL_VERBOSE); - return false; - } - return true; - } - async _onDBUnload(db: LiveSyncLocalDB) { - if (this.core.kvDB) await this.core.kvDB.close(); - return Promise.resolve(true); - } - async _onDBClose(db: LiveSyncLocalDB) { - if (this.core.kvDB) await this.core.kvDB.close(); - return Promise.resolve(true); - } - - private async _everyOnloadAfterLoadSettings(): Promise { - if (!(await this.openKeyValueDB())) { - return false; - } - this.core.simpleStore = this.services.database.openSimpleStore("os"); - return Promise.resolve(true); - } - _getSimpleStore(kind: string) { - const getDB = () => this.core.kvDB; - const prefix = `${kind}-`; - return { - get: async (key: string): Promise => { - return await getDB().get(`${prefix}${key}`); - }, - set: async (key: string, value: any): Promise => { - await getDB().set(`${prefix}${key}`, value); - }, - delete: async (key: string): Promise => { - await getDB().del(`${prefix}${key}`); - }, - keys: async ( - from: string | undefined, - to: string | undefined, - count?: number | undefined - ): Promise => { - const ret = await getDB().keys( - IDBKeyRange.bound(`${prefix}${from || ""}`, `${prefix}${to || ""}`), - count - ); - return ret - .map((e) => e.toString()) - .filter((e) => e.startsWith(prefix)) - .map((e) => e.substring(prefix.length)); - }, - db: Promise.resolve(getDB()), - } satisfies SimpleStore; - } - _everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise { - return this.openKeyValueDB(); - } - - async _everyOnResetDatabase(db: LiveSyncLocalDB): Promise { - try { - const kvDBKey = "queued-files"; - await this.core.kvDB.del(kvDBKey); - // localStorage.removeItem(lsKey); - await this.core.kvDB.destroy(); - await yieldMicrotask(); - this.core.kvDB = await OpenKeyValueDatabase(this.services.vault.getVaultName() + "-livesync-kv"); - await delay(100); - } catch (e) { - this.core.kvDB = undefined!; - this._log("Failed to reset KeyValueDB", LOG_LEVEL_NOTICE); - this._log(e, LOG_LEVEL_VERBOSE); - return false; - } - return true; - } - onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { - services.databaseEvents.onUnloadDatabase.addHandler(this._onDBUnload.bind(this)); - services.databaseEvents.onCloseDatabase.addHandler(this._onDBClose.bind(this)); - services.databaseEvents.onDatabaseInitialisation.addHandler(this._everyOnInitializeDatabase.bind(this)); - services.databaseEvents.onResetDatabase.addHandler(this._everyOnResetDatabase.bind(this)); - (services.database as ObsidianDatabaseService).openSimpleStore.setHandler(this._getSimpleStore.bind(this)); - services.appLifecycle.onSettingLoaded.addHandler(this._everyOnloadAfterLoadSettings.bind(this)); - } -} diff --git a/src/modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.ts b/src/modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.ts deleted file mode 100644 index 256987a..0000000 --- a/src/modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { LiveSyncCore } from "../../main.ts"; -import { AbstractObsidianModule } from "../AbstractObsidianModule.ts"; - -export class ModuleExtraSyncObsidian extends AbstractObsidianModule { - deviceAndVaultName: string = ""; - - _getDeviceAndVaultName(): string { - return this.deviceAndVaultName; - } - _setDeviceAndVaultName(name: string): void { - this.deviceAndVaultName = name; - } - - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { - services.setting.getDeviceAndVaultName.setHandler(this._getDeviceAndVaultName.bind(this)); - services.setting.setDeviceAndVaultName.setHandler(this._setDeviceAndVaultName.bind(this)); - } -} diff --git a/src/modules/features/ModuleLog.ts b/src/modules/features/ModuleLog.ts index b7c9407..6664ae0 100644 --- a/src/modules/features/ModuleLog.ts +++ b/src/modules/features/ModuleLog.ts @@ -39,19 +39,22 @@ import { isValidFilenameInDarwin, isValidFilenameInWidows, } from "@/lib/src/string_and_binary/path.ts"; +import { MARK_LOG_SEPARATOR } from "@/lib/src/services/lib/logUtils.ts"; // This module cannot be a core module because it depends on the Obsidian UI. // DI the log again. const recentLogEntries = reactiveSource([]); -setGlobalLogFunction((message: any, level?: number, key?: string) => { +const globalLogFunction = (message: any, level?: number, key?: string) => { const messageX = message instanceof Error ? new LiveSyncError("[Error Logged]: " + message.message, { cause: message }) : message; const entry = { message: messageX, level, key } as LogEntry; recentLogEntries.value = [...recentLogEntries.value, entry]; -}); +}; + +setGlobalLogFunction(globalLogFunction); let recentLogs = [] as string[]; function addLog(log: string) { @@ -304,9 +307,9 @@ export class ModuleLog extends AbstractObsidianModule { // const recent = logMessages.value; const newMsg = message; let newLog = this.settings?.showOnlyIconsOnEditor ? "" : status; - const moduleTagEnd = newLog.indexOf(`]\u{200A}`); + const moduleTagEnd = newLog.indexOf(`]${MARK_LOG_SEPARATOR}`); if (moduleTagEnd != -1) { - newLog = newLog.substring(moduleTagEnd + 2); + newLog = newLog.substring(moduleTagEnd + MARK_LOG_SEPARATOR.length + 1); } this.statusBar?.setText(newMsg.split("\n")[0]); @@ -493,6 +496,7 @@ export class ModuleLog extends AbstractObsidianModule { } } onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + services.API.addLog.setHandler(globalLogFunction); services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); services.appLifecycle.onSettingLoaded.addHandler(this._everyOnloadAfterLoadSettings.bind(this)); services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this)); diff --git a/src/modules/main/ModuleLiveSyncMain.ts b/src/modules/main/ModuleLiveSyncMain.ts index 84550a5..34dc3f6 100644 --- a/src/modules/main/ModuleLiveSyncMain.ts +++ b/src/modules/main/ModuleLiveSyncMain.ts @@ -126,7 +126,10 @@ export class ModuleLiveSyncMain extends AbstractModule { await this.saveSettings(); } localStorage.setItem(lsKey, `${VER}`); - await this.services.database.openDatabase(); + await this.services.database.openDatabase({ + databaseEvents: this.services.databaseEvents, + replicator: this.services.replicator, + }); // this.core.$$realizeSettingSyncMode = this.core.$$realizeSettingSyncMode.bind(this); // this.$$parseReplicationResult = this.$$parseReplicationResult.bind(this); // this.$$replicate = this.$$replicate.bind(this); diff --git a/src/modules/services/ObsidianAPIService.ts b/src/modules/services/ObsidianAPIService.ts new file mode 100644 index 0000000..b771c29 --- /dev/null +++ b/src/modules/services/ObsidianAPIService.ts @@ -0,0 +1,92 @@ +import { InjectableAPIService } from "@/lib/src/services/implements/injectable/InjectableAPIService"; +import type { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext"; +import { Platform, type Command, type ViewCreator } from "obsidian"; +import { ObsHttpHandler } from "../essentialObsidian/APILib/ObsHttpHandler"; + +// All Services will be migrated to be based on Plain Services, not Injectable Services. +// This is a migration step. + +export class ObsidianAPIService extends InjectableAPIService { + _customHandler: ObsHttpHandler | undefined; + getCustomFetchHandler(): ObsHttpHandler { + if (!this._customHandler) this._customHandler = new ObsHttpHandler(undefined, undefined); + return this._customHandler; + } + + async showWindow(viewType: string): Promise { + const leaves = this.app.workspace.getLeavesOfType(viewType); + if (leaves.length == 0) { + await this.app.workspace.getLeaf(true).setViewState({ + type: viewType, + active: true, + }); + } else { + await leaves[0].setViewState({ + type: viewType, + active: true, + }); + } + if (leaves.length > 0) { + await this.app.workspace.revealLeaf(leaves[0]); + } + } + + private get app() { + return this.context.app; + } + + getPlatform(): string { + if (Platform.isAndroidApp) { + return "android-app"; + } else if (Platform.isIosApp) { + return "ios"; + } else if (Platform.isMacOS) { + return "macos"; + } else if (Platform.isMobileApp) { + return "mobile-app"; + } else if (Platform.isMobile) { + return "mobile"; + } else if (Platform.isSafari) { + return "safari"; + } else if (Platform.isDesktop) { + return "desktop"; + } else if (Platform.isDesktopApp) { + return "desktop-app"; + } else { + return "unknown-obsidian"; + } + } + override isMobile(): boolean { + //@ts-ignore : internal API + return this.app.isMobile; + } + override getAppID(): string { + return `${"appId" in this.app ? this.app.appId : ""}`; + } + override getAppVersion(): string { + const navigatorString = globalThis.navigator?.userAgent ?? ""; + const match = navigatorString.match(/obsidian\/([0-9]+\.[0-9]+\.[0-9]+)/); + if (match && match.length >= 2) { + return match[1]; + } + return "0.0.0"; + } + + override getPluginVersion(): string { + return this.context.plugin.manifest.version; + } + + addCommand(command: TCommand): TCommand { + return this.context.plugin.addCommand(command) as TCommand; + } + + registerWindow(type: string, factory: ViewCreator): void { + return this.context.plugin.registerView(type, factory); + } + addRibbonIcon(icon: string, title: string, callback: (evt: MouseEvent) => any): HTMLElement { + return this.context.plugin.addRibbonIcon(icon, title, callback); + } + registerProtocolHandler(action: string, handler: (params: Record) => any): void { + return this.context.plugin.registerObsidianProtocolHandler(action, handler); + } +} diff --git a/src/modules/services/ObsidianDatabaseService.ts b/src/modules/services/ObsidianDatabaseService.ts new file mode 100644 index 0000000..1e88cbb --- /dev/null +++ b/src/modules/services/ObsidianDatabaseService.ts @@ -0,0 +1,11 @@ +import { initializeStores } from "@/common/stores"; + +import { InjectableDatabaseService } from "@/lib/src/services/implements/injectable/InjectableDatabaseService"; +import type { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext"; + +export class ObsidianDatabaseService extends InjectableDatabaseService { + override onOpenDatabase(vaultName: string): Promise { + initializeStores(vaultName); + return Promise.resolve(); + } +} diff --git a/src/modules/services/ObsidianServiceHub.ts b/src/modules/services/ObsidianServiceHub.ts index 4a3a9f3..cd49fc8 100644 --- a/src/modules/services/ObsidianServiceHub.ts +++ b/src/modules/services/ObsidianServiceHub.ts @@ -3,9 +3,7 @@ import { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/O import type { ServiceInstances } from "@/lib/src/services/ServiceHub"; import type ObsidianLiveSyncPlugin from "@/main"; import { - ObsidianAPIService, ObsidianConflictService, - ObsidianDatabaseService, ObsidianFileProcessingService, ObsidianReplicationService, ObsidianReplicatorService, @@ -15,7 +13,10 @@ import { ObsidianTestService, ObsidianDatabaseEventService, ObsidianConfigService, + ObsidianKeyValueDBService, } from "./ObsidianServices"; +import { ObsidianDatabaseService } from "./ObsidianDatabaseService"; +import { ObsidianAPIService } from "./ObsidianAPIService"; import { ObsidianAppLifecycleService } from "./ObsidianAppLifecycleService"; import { ObsidianPathService } from "./ObsidianPathService"; import { ObsidianVaultService } from "./ObsidianVaultService"; @@ -30,7 +31,6 @@ export class ObsidianServiceHub extends InjectableServiceHub>; super(context, serviceInstancesToInit); diff --git a/src/modules/services/ObsidianServices.ts b/src/modules/services/ObsidianServices.ts index 2f16c0d..9ac3129 100644 --- a/src/modules/services/ObsidianServices.ts +++ b/src/modules/services/ObsidianServices.ts @@ -1,7 +1,5 @@ -import { InjectableAPIService } from "@lib/services/implements/injectable/InjectableAPIService"; import { InjectableConflictService } from "@lib/services/implements/injectable/InjectableConflictService"; import { InjectableDatabaseEventService } from "@lib/services/implements/injectable/InjectableDatabaseEventService"; -import { InjectableDatabaseService } from "@lib/services/implements/injectable/InjectableDatabaseService"; import { InjectableFileProcessingService } from "@lib/services/implements/injectable/InjectableFileProcessingService"; import { InjectableRemoteService } from "@lib/services/implements/injectable/InjectableRemoteService"; import { InjectableReplicationService } from "@lib/services/implements/injectable/InjectableReplicationService"; @@ -11,105 +9,8 @@ import { InjectableTestService } from "@lib/services/implements/injectable/Injec import { InjectableTweakValueService } from "@lib/services/implements/injectable/InjectableTweakValueService"; import { ConfigServiceBrowserCompat } from "@lib/services/implements/browser/ConfigServiceBrowserCompat"; import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext.ts"; -import { Platform } from "@/deps"; -import type { SimpleStore } from "@/lib/src/common/utils"; -import type { IDatabaseService } from "@/lib/src/services/base/IService"; -import { handlers } from "@/lib/src/services/lib/HandlerUtils"; -import { ObsHttpHandler } from "../essentialObsidian/APILib/ObsHttpHandler"; -import type { Command, ViewCreator } from "obsidian"; +import { KeyValueDBService } from "@/lib/src/services/base/KeyValueDBService"; -// All Services will be migrated to be based on Plain Services, not Injectable Services. -// This is a migration step. - -export class ObsidianAPIService extends InjectableAPIService { - _customHandler: ObsHttpHandler | undefined; - getCustomFetchHandler(): ObsHttpHandler { - if (!this._customHandler) this._customHandler = new ObsHttpHandler(undefined, undefined); - return this._customHandler; - } - - async showWindow(viewType: string): Promise { - const leaves = this.app.workspace.getLeavesOfType(viewType); - if (leaves.length == 0) { - await this.app.workspace.getLeaf(true).setViewState({ - type: viewType, - active: true, - }); - } else { - await leaves[0].setViewState({ - type: viewType, - active: true, - }); - } - if (leaves.length > 0) { - await this.app.workspace.revealLeaf(leaves[0]); - } - } - - private get app() { - return this.context.app; - } - - getPlatform(): string { - if (Platform.isAndroidApp) { - return "android-app"; - } else if (Platform.isIosApp) { - return "ios"; - } else if (Platform.isMacOS) { - return "macos"; - } else if (Platform.isMobileApp) { - return "mobile-app"; - } else if (Platform.isMobile) { - return "mobile"; - } else if (Platform.isSafari) { - return "safari"; - } else if (Platform.isDesktop) { - return "desktop"; - } else if (Platform.isDesktopApp) { - return "desktop-app"; - } else { - return "unknown-obsidian"; - } - } - override isMobile(): boolean { - //@ts-ignore : internal API - return this.app.isMobile; - } - override getAppID(): string { - return `${"appId" in this.app ? this.app.appId : ""}`; - } - override getAppVersion(): string { - const navigatorString = globalThis.navigator?.userAgent ?? ""; - const match = navigatorString.match(/obsidian\/([0-9]+\.[0-9]+\.[0-9]+)/); - if (match && match.length >= 2) { - return match[1]; - } - return "0.0.0"; - } - - override getPluginVersion(): string { - return this.context.plugin.manifest.version; - } - - addCommand(command: TCommand): TCommand { - return this.context.plugin.addCommand(command) as TCommand; - } - - registerWindow(type: string, factory: ViewCreator): void { - return this.context.plugin.registerView(type, factory); - } - addRibbonIcon(icon: string, title: string, callback: (evt: MouseEvent) => any): HTMLElement { - return this.context.plugin.addRibbonIcon(icon, title, callback); - } - registerProtocolHandler(action: string, handler: (params: Record) => any): void { - return this.context.plugin.registerObsidianProtocolHandler(action, handler); - } -} -export class ObsidianDatabaseService extends InjectableDatabaseService { - openSimpleStore = handlers().binder("openSimpleStore") as (( - kind: string - ) => SimpleStore) & { setHandler: (handler: IDatabaseService["openSimpleStore"], override?: boolean) => void }; -} export class ObsidianDatabaseEventService extends InjectableDatabaseEventService {} // InjectableReplicatorService @@ -129,3 +30,5 @@ export class ObsidianTweakValueService extends InjectableTweakValueService {} export class ObsidianConfigService extends ConfigServiceBrowserCompat {} + +export class ObsidianKeyValueDBService extends KeyValueDBService {} diff --git a/updates.md b/updates.md index e7180b2..4814c92 100644 --- a/updates.md +++ b/updates.md @@ -3,13 +3,37 @@ 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.43-patched-3 + +16th February, 2026 + +### Refactored + +- Now following properties of `ObsidianLiveSyncPlugin` belong to each service: + - property : service + - `localDatabase` : `services.database` + - `managers` : `services.database` + - `simpleStore` : `services.keyValueDB` + - `kvDB`: `services.keyValueDB` +- Initialising modules, addOns, and services are now explicitly separated in the `_startUp` function of the main plug-in class. +- LiveSyncLocalDB now depends more explicitly on specified services, not the whole `ServiceHub`. +- New service `keyValueDB` has been added. This had been separated from the `database` service. +- Non-trivial modules, such as `ModuleExtraSyncObsidian` (which only holds deviceAndVaultName), are simply implemented in the service. +- Add `logUtils` for unifying logging method injection and formatting. This utility is able to accept the API service for log writing. +- `ModuleKeyValueDB` has been removed, and its functionality is now implemented in the `keyValueDB` service. +- `ModulePouchDB` and `ModuleLocalDatabaseObsidian` have been removed, and their functionality is now implemented in the `database` service. + - Please be aware that you have overridden createPouchDBInstance or something by dynamic binding; you should now override the createPouchDBInstance in the database service instead of using the module. + - You can refer to the `DirectFileManipulatorV2` for an example of how to override the createPouchDBInstance function in the database service. + + ## 0.25.43-patched-2 14th February, 2026 ### Fixed + - Application LifeCycle has now started in Main, not ServiceHub. - - Indeed, ServiceHub cannot be known other things in main have got ready, so it is quite natural to start the lifecycle in main. + - Indeed, ServiceHub cannot be known other things in main have got ready, so it is quite natural to start the lifecycle in main. ## 0.25.43-patched-1 @@ -29,6 +53,7 @@ If this cannot be stable, I will revert to 0.24.43 and try again. - VaultService.isTargetFile is now using multiple checkers instead of a single function. - This change allows better separation of concerns and easier extension in the future. - Application LifeCycle has now started in ServiceHub, not ObsidianMenuModule. + - It was in a QUITE unexpected place..., isn't it? - Instead of, we should call `await this.services.appLifecycle.onReady()` in other platforms. - As in the browser platform, it will be called at `DOMContentLoaded` event. From 2b9bb1ed0647e0cf53f382fc655e95115a7713b0 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 16 Feb 2026 06:51:52 +0000 Subject: [PATCH 006/339] beta bump --- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/manifest.json b/manifest.json index 795a095..01793ba 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.43-patched-2", + "version": "0.25.43-patched-3", "minAppVersion": "0.9.12", "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 2f52bcb..cef1588 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-2", + "version": "0.25.43-patched-3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.43-patched-2", + "version": "0.25.43-patched-3", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 2362ec4..56a0110 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-2", + "version": "0.25.43-patched-3", "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", From 2ae70e8f07e0cdc6a2e6b5f550b463113ebe6698 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 16 Feb 2026 11:51:03 +0000 Subject: [PATCH 007/339] Refactor: `DatabaseService` and `Replicator` --- src/common/events.ts | 2 - src/lib | 2 +- src/main.ts | 11 ++- src/modules/core/ModuleRebuilder.ts | 6 +- src/modules/core/ModuleReplicator.ts | 78 +++++-------------- .../ObsidianLiveSyncSettingTab.ts | 2 - .../services/ObsidianDatabaseService.ts | 13 +++- src/modules/services/ObsidianServiceHub.ts | 7 +- updates.md | 21 ++++- 9 files changed, 60 insertions(+), 82 deletions(-) diff --git a/src/common/events.ts b/src/common/events.ts index b4aa2d3..0ef7d04 100644 --- a/src/common/events.ts +++ b/src/common/events.ts @@ -21,7 +21,6 @@ export const EVENT_REQUEST_CLOSE_P2P = "request-close-p2p"; export const EVENT_REQUEST_RUN_DOCTOR = "request-run-doctor"; export const EVENT_REQUEST_RUN_FIX_INCOMPLETE = "request-run-fix-incomplete"; -export const EVENT_ON_UNRESOLVED_ERROR = "on-unresolved-error"; export const EVENT_ANALYSE_DB_USAGE = "analyse-db-usage"; export const EVENT_REQUEST_PERFORM_GC_V3 = "request-perform-gc-v3"; @@ -44,7 +43,6 @@ declare global { [EVENT_REQUEST_SHOW_SETUP_QR]: undefined; [EVENT_REQUEST_RUN_DOCTOR]: string; [EVENT_REQUEST_RUN_FIX_INCOMPLETE]: undefined; - [EVENT_ON_UNRESOLVED_ERROR]: undefined; [EVENT_ANALYSE_DB_USAGE]: undefined; [EVENT_REQUEST_CHECK_REMOTE_SIZE]: undefined; [EVENT_REQUEST_PERFORM_GC_V3]: undefined; diff --git a/src/lib b/src/lib index 3ae1cba..75d46c7 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 3ae1cbabdae286f994807c9da4ac2e937f70d3ee +Subproject commit 75d46c716320ee60fc728f4f460921ef0fa111c8 diff --git a/src/main.ts b/src/main.ts index 8a8d1bd..2d04a91 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,10 +7,7 @@ import { } from "./lib/src/common/types.ts"; import { type SimpleStore } from "./lib/src/common/utils.ts"; import { type LiveSyncLocalDBEnv } from "./lib/src/pouchdb/LiveSyncLocalDB.ts"; -import { - LiveSyncAbstractReplicator, - type LiveSyncReplicatorEnv, -} from "./lib/src/replication/LiveSyncAbstractReplicator.js"; +import { type LiveSyncReplicatorEnv } from "./lib/src/replication/LiveSyncAbstractReplicator.js"; import { LiveSyncCommands } from "./features/LiveSyncCommands.ts"; import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts"; import { ConfigSync } from "./features/ConfigSync/CmdConfigSync.ts"; @@ -202,8 +199,10 @@ export default class ObsidianLiveSyncPlugin return this.services.keyValueDB.simpleStore as SimpleStore; } - // initialised at ModuleReplicator - replicator!: LiveSyncAbstractReplicator; + get replicator() { + return this.services.replicator.getActiveReplicator()!; + } + // initialised at ModuleFileAccessObsidian storageAccess!: StorageAccess; // initialised at ModuleDatabaseFileAccess diff --git a/src/modules/core/ModuleRebuilder.ts b/src/modules/core/ModuleRebuilder.ts index d5fc7dc..a38d286 100644 --- a/src/modules/core/ModuleRebuilder.ts +++ b/src/modules/core/ModuleRebuilder.ts @@ -133,9 +133,9 @@ Please enable them from the settings screen after setup is complete.`, await this.core.replicator.tryCreateRemoteDatabase(this.settings); } - private async _resetLocalDatabase(): Promise { + private _onResetLocalDatabase(): Promise { this.core.storageAccess.clearTouched(); - return await this.localDatabase.resetDatabase(); + return Promise.resolve(true); } async suspendAllSync() { @@ -305,7 +305,7 @@ Are you sure you wish to proceed?`; } onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this)); - services.database.resetDatabase.setHandler(this._resetLocalDatabase.bind(this)); + services.database.onDatabaseReset.addHandler(this._onResetLocalDatabase.bind(this)); services.remote.tryResetDatabase.setHandler(this._tryResetRemoteDatabase.bind(this)); services.remote.tryCreateDatabase.setHandler(this._tryCreateRemoteDatabase.bind(this)); services.setting.suspendAllSync.addHandler(this._allSuspendAllSync.bind(this)); diff --git a/src/modules/core/ModuleReplicator.ts b/src/modules/core/ModuleReplicator.ts index 08af7fa..6a6165f 100644 --- a/src/modules/core/ModuleReplicator.ts +++ b/src/modules/core/ModuleReplicator.ts @@ -1,48 +1,36 @@ -import { fireAndForget, yieldMicrotask } from "octagonal-wheels/promises"; -import type { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB"; +import { fireAndForget } from "octagonal-wheels/promises"; import { AbstractModule } from "../AbstractModule"; -import { - Logger, - LOG_LEVEL_NOTICE, - LOG_LEVEL_INFO, - LOG_LEVEL_VERBOSE, - LEVEL_NOTICE, - LEVEL_INFO, - type LOG_LEVEL, -} from "octagonal-wheels/common/logger"; +import { Logger, LOG_LEVEL_NOTICE, LOG_LEVEL_INFO, LEVEL_NOTICE, type LOG_LEVEL } from "octagonal-wheels/common/logger"; import { isLockAcquired, shareRunningResult, skipIfDuplicated } from "octagonal-wheels/concurrency/lock"; import { balanceChunkPurgedDBs } from "@/lib/src/pouchdb/chunks"; import { purgeUnreferencedChunks } from "@/lib/src/pouchdb/chunks"; import { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator"; import { type EntryDoc, type RemoteType } from "../../lib/src/common/types"; import { rateLimitedSharedExecution, scheduleTask, updatePreviousExecutionTime } from "../../common/utils"; -import { EVENT_FILE_SAVED, EVENT_ON_UNRESOLVED_ERROR, EVENT_SETTING_SAVED, eventHub } from "../../common/events"; -import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator"; +import { EVENT_FILE_SAVED, EVENT_SETTING_SAVED, eventHub } from "../../common/events"; import { $msg } from "../../lib/src/common/i18n"; -import { clearHandlers } from "../../lib/src/replication/SyncParamsHandler"; import type { LiveSyncCore } from "../../main"; import { ReplicateResultProcessor } from "./ReplicateResultProcessor"; +import { UnresolvedErrorManager } from "@/lib/src/services/base/UnresolvedErrorManager"; +import { clearHandlers } from "@/lib/src/replication/SyncParamsHandler"; const KEY_REPLICATION_ON_EVENT = "replicationOnEvent"; const REPLICATION_ON_EVENT_FORECASTED_TIME = 5000; export class ModuleReplicator extends AbstractModule { _replicatorType?: RemoteType; - _previousErrors = new Set(); + processor: ReplicateResultProcessor = new ReplicateResultProcessor(this); + private _unresolvedErrorManager: UnresolvedErrorManager = new UnresolvedErrorManager( + this.core.services.appLifecycle + ); showError(msg: string, max_log_level: LOG_LEVEL = LEVEL_NOTICE) { - const level = this._previousErrors.has(msg) ? LEVEL_INFO : max_log_level; - this._log(msg, level); - if (!this._previousErrors.has(msg)) { - this._previousErrors.add(msg); - eventHub.emitEvent(EVENT_ON_UNRESOLVED_ERROR); - } + this._unresolvedErrorManager.showError(msg, max_log_level); } clearErrors() { - this._previousErrors.clear(); - eventHub.emitEvent(EVENT_ON_UNRESOLVED_ERROR); + this._unresolvedErrorManager.clearErrors(); } private _everyOnloadAfterLoadSettings(): Promise { @@ -52,9 +40,10 @@ export class ModuleReplicator extends AbstractModule { } }); eventHub.onEvent(EVENT_SETTING_SAVED, (setting) => { - if (this._replicatorType !== setting.remoteType) { - void this.setReplicator(); - } + // ReplicatorService responds to `settingService.onRealiseSetting`. + // if (this._replicatorType !== setting.remoteType) { + // void this.setReplicator(); + // } if (this.core.settings.suspendParseReplicationResult) { this.processor.suspend(); } else { @@ -65,39 +54,17 @@ export class ModuleReplicator extends AbstractModule { return Promise.resolve(true); } - async setReplicator() { - const replicator = await this.services.replicator.getNewReplicator(); - if (!replicator) { - this.showError($msg("Replicator.Message.InitialiseFatalError"), LOG_LEVEL_NOTICE); - return false; - } - if (this.core.replicator) { - await this.core.replicator.closeReplication(); - this._log("Replicator closed for changing", LOG_LEVEL_VERBOSE); - } - this.core.replicator = replicator; - this._replicatorType = this.settings.remoteType; - await yieldMicrotask(); - // Clear any existing sync parameter handlers (means clearing key-deriving salt). + _onReplicatorInitialised(): Promise { + // For now, we only need to clear the error related to replicator initialisation, but in the future, if there are more things to do when the replicator is initialised, we can add them here. clearHandlers(); - return true; + return Promise.resolve(true); } - _getReplicator(): LiveSyncAbstractReplicator { - return this.core.replicator; - } - - _everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise { - return this.setReplicator(); - } _everyOnDatabaseInitialized(showNotice: boolean): Promise { fireAndForget(() => this.processor.restoreFromSnapshotOnce()); return Promise.resolve(true); } - _everyOnResetDatabase(db: LiveSyncLocalDB): Promise { - return this.setReplicator(); - } async ensureReplicatorPBKDF2Salt(showMessage: boolean = false): Promise { // Checking salt const replicator = this.services.replicator.getActiveReplicator(); @@ -324,15 +291,9 @@ Even if you choose to clean up, you will see this option again if you exit Obsid return !checkResult; } - private _reportUnresolvedMessages(): Promise { - return Promise.resolve([...this._previousErrors]); - } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { - services.replicator.getActiveReplicator.setHandler(this._getReplicator.bind(this)); - services.databaseEvents.onDatabaseInitialisation.addHandler(this._everyOnInitializeDatabase.bind(this)); + services.replicator.onReplicatorInitialised.addHandler(this._onReplicatorInitialised.bind(this)); services.databaseEvents.onDatabaseInitialised.addHandler(this._everyOnDatabaseInitialized.bind(this)); - services.databaseEvents.onResetDatabase.addHandler(this._everyOnResetDatabase.bind(this)); services.appLifecycle.onSettingLoaded.addHandler(this._everyOnloadAfterLoadSettings.bind(this)); services.replication.parseSynchroniseResult.setHandler(this._parseReplicationResult.bind(this)); services.appLifecycle.onSuspending.addHandler(this._everyBeforeSuspendProcess.bind(this)); @@ -342,6 +303,5 @@ Even if you choose to clean up, you will see this option again if you exit Obsid services.replication.replicateByEvent.setHandler(this._replicateByEvent.bind(this)); services.remote.replicateAllToRemote.setHandler(this._replicateAllToServer.bind(this)); services.remote.replicateAllFromRemote.setHandler(this._replicateAllFromServer.bind(this)); - services.appLifecycle.getUnresolvedMessages.addHandler(this._reportUnresolvedMessages.bind(this)); } } diff --git a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts index 2fb26e8..f2ae1e4 100644 --- a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts +++ b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts @@ -424,8 +424,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { //@ts-ignore manifestVersion: string = MANIFEST_VERSION || "-"; - //@ts-ignore - updateInformation: string = UPDATE_INFO || ""; lastVersion = ~~(versionNumberString2Number(this.manifestVersion) / 1000); diff --git a/src/modules/services/ObsidianDatabaseService.ts b/src/modules/services/ObsidianDatabaseService.ts index 1e88cbb..b2e4146 100644 --- a/src/modules/services/ObsidianDatabaseService.ts +++ b/src/modules/services/ObsidianDatabaseService.ts @@ -1,11 +1,16 @@ import { initializeStores } from "@/common/stores"; -import { InjectableDatabaseService } from "@/lib/src/services/implements/injectable/InjectableDatabaseService"; +// import { InjectableDatabaseService } from "@/lib/src/services/implements/injectable/InjectableDatabaseService"; import type { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext"; +import { DatabaseService, type DatabaseServiceDependencies } from "@lib/services/base/DatabaseService.ts"; -export class ObsidianDatabaseService extends InjectableDatabaseService { - override onOpenDatabase(vaultName: string): Promise { +export class ObsidianDatabaseService extends DatabaseService { + private __onOpenDatabase(vaultName: string) { initializeStores(vaultName); - return Promise.resolve(); + return Promise.resolve(true); + } + constructor(context: T, dependencies: DatabaseServiceDependencies) { + super(context, dependencies); + this.onOpenDatabase.addHandler(this.__onOpenDatabase.bind(this)); } } diff --git a/src/modules/services/ObsidianServiceHub.ts b/src/modules/services/ObsidianServiceHub.ts index cd49fc8..5870f6c 100644 --- a/src/modules/services/ObsidianServiceHub.ts +++ b/src/modules/services/ObsidianServiceHub.ts @@ -33,7 +33,7 @@ export class ObsidianServiceHub extends InjectableServiceHub Date: Mon, 16 Feb 2026 11:51:09 +0000 Subject: [PATCH 008/339] bump --- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/manifest.json b/manifest.json index 01793ba..4956034 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.43-patched-3", + "version": "0.25.43-patched-4", "minAppVersion": "0.9.12", "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 cef1588..0b152bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-3", + "version": "0.25.43-patched-4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.43-patched-3", + "version": "0.25.43-patched-4", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 56a0110..5037b29 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-3", + "version": "0.25.43-patched-4", "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", From 48b0d22da6d4e0a48e0f0a270b516545fbfa47d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wannes=20Salom=C3=A9?= Date: Tue, 17 Feb 2026 00:24:30 +0100 Subject: [PATCH 009/339] fix: handle "File already exists" for .md files in writeFileAuto During concurrent initialisation (UPDATE STORAGE runs up to 10 ops in parallel), getAbstractFileByPath can return null for .md files whose vault index entry hasn't been populated yet, even though the file already exists on disk. This causes vault.create() to throw "File already exists." The same root cause (stale in-memory index) was already identified for non-.md files (see comment above) and handled via adapterWrite. Extend that workaround to .md files by catching the "File already exists" error and falling back to adapterWrite, consistent with the existing approach. Co-Authored-By: Claude Sonnet 4.5 --- .../coreObsidian/ModuleFileAccessObsidian.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/modules/coreObsidian/ModuleFileAccessObsidian.ts b/src/modules/coreObsidian/ModuleFileAccessObsidian.ts index a7e58ed..935754d 100644 --- a/src/modules/coreObsidian/ModuleFileAccessObsidian.ts +++ b/src/modules/coreObsidian/ModuleFileAccessObsidian.ts @@ -108,7 +108,22 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements // For safety, check existence return await this.vaultAccess.adapterExists(path); } else { - return (await this.vaultAccess.vaultCreate(path, data, opt)) instanceof TFile; + // The same stale-index issue described above can also happen for .md files during + // concurrent initialisation (UPDATE STORAGE runs up to 10 ops in parallel). + // getAbstractFileByPath returns null because Obsidian's in-memory index hasn't + // caught up yet, but the file already exists on disk — causing vault.create() to + // throw "File already exists." + // Fall back to adapterWrite (same approach used for non-md files above) so the + // file is written correctly without an error. + try { + return (await this.vaultAccess.vaultCreate(path, data, opt)) instanceof TFile; + } catch (ex) { + if (ex instanceof Error && ex.message === "File already exists.") { + await this.vaultAccess.adapterWrite(path, data, opt); + return await this.vaultAccess.adapterExists(path); + } + throw ex; + } } } else { this._log(`Could not write file (Possibly already exists as a folder): ${path}`, LOG_LEVEL_VERBOSE); From 0a1917e83cc6a40b8a2d02c98481475e3191f4f4 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Tue, 17 Feb 2026 10:14:04 +0000 Subject: [PATCH 010/339] Refactor for 0.25.43-patched-5 (very long, please refer the updates.md) --- src/common/utils.ts | 30 +- src/lib | 2 +- src/main.ts | 196 ++++++-- src/modules/AbstractModule.ts | 3 - src/modules/core/ModuleDatabaseFileAccess.ts | 352 -------------- src/modules/core/ModuleFileHandler.ts | 436 ------------------ src/modules/core/ModuleRebuilder.ts | 313 ------------- .../coreFeatures/ModuleConflictResolver.ts | 20 + .../storageLib/SerializedFileAccess.ts | 85 ++-- .../storageLib/StorageEventManager.ts | 29 +- .../coreObsidian/storageLib/utilObsidian.ts | 4 +- src/modules/extras/ModuleReplicateTest.ts | 5 +- src/modules/features/ModuleObsidianSetting.ts | 21 + .../features/SettingDialogue/PaneHatch.ts | 2 +- src/modules/interfaces/DatabaseFileAccess.ts | 34 -- src/modules/interfaces/DatabaseRebuilder.ts | 12 - src/modules/interfaces/StorageAccess.ts | 61 --- src/modules/services/ObsidianServiceHub.ts | 6 +- src/serviceModules/DatabaseFileAccess.ts | 15 + src/serviceModules/FileHandler.ts | 26 ++ .../ServiceFileAccessObsidian.ts} | 118 ++--- updates.md | 32 ++ 22 files changed, 409 insertions(+), 1393 deletions(-) delete mode 100644 src/modules/core/ModuleDatabaseFileAccess.ts delete mode 100644 src/modules/core/ModuleFileHandler.ts delete mode 100644 src/modules/core/ModuleRebuilder.ts delete mode 100644 src/modules/interfaces/DatabaseFileAccess.ts delete mode 100644 src/modules/interfaces/DatabaseRebuilder.ts delete mode 100644 src/modules/interfaces/StorageAccess.ts create mode 100644 src/serviceModules/DatabaseFileAccess.ts create mode 100644 src/serviceModules/FileHandler.ts rename src/{modules/coreObsidian/ModuleFileAccessObsidian.ts => serviceModules/ServiceFileAccessObsidian.ts} (80%) diff --git a/src/common/utils.ts b/src/common/utils.ts index cde7196..3ef6d80 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -23,7 +23,7 @@ import { type UXFileInfo, type UXFileInfoStub, } from "../lib/src/common/types.ts"; -import { ICHeader, ICXHeader } from "./types.ts"; +export { ICHeader, ICXHeader } from "./types.ts"; import type ObsidianLiveSyncPlugin from "../main.ts"; import { writeString } from "../lib/src/string_and_binary/convert.ts"; import { fireAndForget } from "../lib/src/common/utils.ts"; @@ -68,21 +68,13 @@ export function getPathFromTFile(file: TAbstractFile) { return file.path as FilePath; } -import { isInternalFile } from "@lib/common/typeUtils.ts"; -export function getPathFromUXFileInfo(file: UXFileInfoStub | string | FilePathWithPrefix) { - if (typeof file == "string") return file as FilePathWithPrefix; - return file.path; -} -export function getStoragePathFromUXFileInfo(file: UXFileInfoStub | string | FilePathWithPrefix) { - if (typeof file == "string") return stripAllPrefixes(file as FilePathWithPrefix); - return stripAllPrefixes(file.path); -} -export function getDatabasePathFromUXFileInfo(file: UXFileInfoStub | string | FilePathWithPrefix) { - if (typeof file == "string" && file.startsWith(ICXHeader)) return file as FilePathWithPrefix; - const prefix = isInternalFile(file) ? ICHeader : ""; - if (typeof file == "string") return (prefix + stripAllPrefixes(file as FilePathWithPrefix)) as FilePathWithPrefix; - return (prefix + stripAllPrefixes(file.path)) as FilePathWithPrefix; -} +import { + isInternalFile, + getPathFromUXFileInfo, + getStoragePathFromUXFileInfo, + getDatabasePathFromUXFileInfo, +} from "@lib/common/typeUtils.ts"; +export { isInternalFile, getPathFromUXFileInfo, getStoragePathFromUXFileInfo, getDatabasePathFromUXFileInfo }; const memos: { [key: string]: any } = {}; export function memoObject(key: string, obj: T): T { @@ -263,10 +255,8 @@ export function requestToCouchDBWithCredentials( return _requestToCouchDB(baseUri, credentials, origin, uri, body, method, customHeaders); } -export const BASE_IS_NEW = Symbol("base"); -export const TARGET_IS_NEW = Symbol("target"); -export const EVEN = Symbol("even"); - +import { BASE_IS_NEW, EVEN, TARGET_IS_NEW } from "@lib/common/models/shared.const.symbols.ts"; +export { BASE_IS_NEW, EVEN, TARGET_IS_NEW }; // Why 2000? : ZIP FILE Does not have enough resolution. const resolution = 2000; export function compareMTime( diff --git a/src/lib b/src/lib index 75d46c7..56fc24e 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 75d46c716320ee60fc728f4f460921ef0fa111c8 +Subproject commit 56fc24e001347948bafbc85c342f94fd0ee0a0b5 diff --git a/src/main.ts b/src/main.ts index 2d04a91..e623df0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,6 +4,7 @@ import { type ObsidianLiveSyncSettings, type DatabaseConnectingStatus, type HasSettings, + LOG_LEVEL_INFO, } from "./lib/src/common/types.ts"; import { type SimpleStore } from "./lib/src/common/utils.ts"; import { type LiveSyncLocalDBEnv } from "./lib/src/pouchdb/LiveSyncLocalDB.ts"; @@ -16,9 +17,7 @@ import { type LiveSyncJournalReplicatorEnv } from "./lib/src/replication/journal import { type LiveSyncCouchDBReplicatorEnv } from "./lib/src/replication/couchdb/LiveSyncReplicator.js"; import type { CheckPointInfo } from "./lib/src/replication/journal/JournalSyncTypes.js"; import type { IObsidianModule } from "./modules/AbstractObsidianModule.ts"; - import { ModuleDev } from "./modules/extras/ModuleDev.ts"; -import { ModuleFileAccessObsidian } from "./modules/coreObsidian/ModuleFileAccessObsidian.ts"; import { ModuleMigration } from "./modules/essential/ModuleMigration.ts"; import { ModuleCheckRemoteSize } from "./modules/essentialObsidian/ModuleCheckRemoteSize.ts"; @@ -30,15 +29,13 @@ import { ModuleRedFlag } from "./modules/coreFeatures/ModuleRedFlag.ts"; import { ModuleObsidianMenu } from "./modules/essentialObsidian/ModuleObsidianMenu.ts"; import { ModuleSetupObsidian } from "./modules/features/ModuleSetupObsidian.ts"; import { SetupManager } from "./modules/features/SetupManager.ts"; -import type { StorageAccess } from "./modules/interfaces/StorageAccess.ts"; +import type { StorageAccess } from "@lib/interfaces/StorageAccess.ts"; import type { Confirm } from "./lib/src/interfaces/Confirm.ts"; -import type { Rebuilder } from "./modules/interfaces/DatabaseRebuilder.ts"; -import type { DatabaseFileAccess } from "./modules/interfaces/DatabaseFileAccess.ts"; -import { ModuleDatabaseFileAccess } from "./modules/core/ModuleDatabaseFileAccess.ts"; -import { ModuleFileHandler } from "./modules/core/ModuleFileHandler.ts"; +import type { Rebuilder } from "@lib/interfaces/DatabaseRebuilder.ts"; +import type { DatabaseFileAccess } from "@lib/interfaces/DatabaseFileAccess.ts"; import { ModuleObsidianAPI } from "./modules/essentialObsidian/ModuleObsidianAPI.ts"; import { ModuleObsidianEvents } from "./modules/essentialObsidian/ModuleObsidianEvents.ts"; -import { type AbstractModule } from "./modules/AbstractModule.ts"; +import { AbstractModule } from "./modules/AbstractModule.ts"; import { ModuleObsidianSettingDialogue } from "./modules/features/ModuleObsidianSettingTab.ts"; import { ModuleObsidianDocumentHistory } from "./modules/features/ModuleObsidianDocumentHistory.ts"; import { ModuleObsidianGlobalHistory } from "./modules/features/ModuleGlobalHistory.ts"; @@ -53,7 +50,6 @@ import { ModuleRemoteGovernor } from "./modules/coreFeatures/ModuleRemoteGoverno import { ModuleConflictChecker } from "./modules/coreFeatures/ModuleConflictChecker.ts"; import { ModuleResolvingMismatchedTweaks } from "./modules/coreFeatures/ModuleResolveMismatchedTweaks.ts"; import { ModuleIntegratedTest } from "./modules/extras/ModuleIntegratedTest.ts"; -import { ModuleRebuilder } from "./modules/core/ModuleRebuilder.ts"; import { ModuleReplicateTest } from "./modules/extras/ModuleReplicateTest.ts"; import { ModuleLiveSyncMain } from "./modules/main/ModuleLiveSyncMain.ts"; import { LocalDatabaseMaintenance } from "./features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts"; @@ -61,6 +57,15 @@ import { P2PReplicator } from "./features/P2PSync/CmdP2PReplicator.ts"; import type { InjectableServiceHub } from "./lib/src/services/implements/injectable/InjectableServiceHub.ts"; import { ObsidianServiceHub } from "./modules/services/ObsidianServiceHub.ts"; import type { ServiceContext } from "./lib/src/services/base/ServiceBase.ts"; +import { ServiceRebuilder } from "@lib/serviceModules/Rebuilder.ts"; +import type { IFileHandler } from "@lib/interfaces/FileHandler.ts"; +import { ServiceDatabaseFileAccess } from "@/serviceModules/DatabaseFileAccess.ts"; +import { ServiceFileAccessObsidian } from "@/serviceModules/ServiceFileAccessObsidian.ts"; +import { StorageEventManagerObsidian } from "@/modules/coreObsidian/storageLib/StorageEventManager.ts"; +import { ObsidianFileAccess } from "@/modules/coreObsidian/storageLib/SerializedFileAccess.ts"; +import { StorageAccessManager } from "@lib/managers/StorageProcessingManager.ts"; +import { __$checkInstanceBinding } from "./lib/src/dev/checks.ts"; +import { ServiceFileHandler } from "./serviceModules/FileHandler.ts"; export default class ObsidianLiveSyncPlugin extends Plugin @@ -87,16 +92,17 @@ export default class ObsidianLiveSyncPlugin this._services = new ObsidianServiceHub(this); } - // Keep order to display the dialogue in order. addOns = [] as LiveSyncCommands[]; - /** - * Bind functions to the service hub (for migration purpose). - */ - // bindFunctions = (this.serviceHub as ObsidianServiceHub).bindFunctions.bind(this.serviceHub); + /** + * register an add-onn to the plug-in. + * Add-ons are features that are not essential to the core functionality of the plugin, + * @param addOn + */ private _registerAddOn(addOn: LiveSyncCommands) { this.addOns.push(addOn); } + private registerAddOns() { this._registerAddOn(new ConfigSync(this)); this._registerAddOn(new HiddenFileSync(this)); @@ -104,6 +110,11 @@ export default class ObsidianLiveSyncPlugin this._registerAddOn(new P2PReplicator(this)); } + /** + * Get an add-on by its class name. Returns undefined if not found. + * @param cls + * @returns + */ getAddOn(cls: string) { for (const addon of this.addOns) { if (addon.constructor.name == cls) return addon as T; @@ -111,53 +122,52 @@ export default class ObsidianLiveSyncPlugin return undefined; } + /** + * The modules of the plug-in. Modules are responsible for specific features or functionalities of the plug-in, such as file handling, conflict resolution, replication, etc. + */ private modules = [ // Move to registerModules ] as (IObsidianModule | AbstractModule)[]; + /** + * Get a module by its class. Throws an error if not found. + * Mostly used for getting SetupManager. + * @param constructor + * @returns + */ getModule(constructor: new (...args: any[]) => T): T { for (const module of this.modules) { if (module.constructor === constructor) return module as T; } throw new Error(`Module ${constructor} not found or not loaded.`); } - getModulesByType(constructor: new (...args: any[]) => T): T[] { - const matchedModules: T[] = []; - for (const module of this.modules) { - if (module instanceof constructor) matchedModules.push(module); - } - return matchedModules; - } + /** + * Register a module to the plug-in. + * @param module The module to register. + */ private _registerModule(module: IObsidianModule) { this.modules.push(module); } private registerModules() { this._registerModule(new ModuleLiveSyncMain(this)); - // Only on Obsidian - this._registerModule(new ModuleDatabaseFileAccess(this)); - // Common this._registerModule(new ModuleConflictChecker(this)); this._registerModule(new ModuleReplicatorMinIO(this)); this._registerModule(new ModuleReplicatorCouchDB(this)); this._registerModule(new ModuleReplicator(this)); - this._registerModule(new ModuleFileHandler(this)); this._registerModule(new ModuleConflictResolver(this)); this._registerModule(new ModuleRemoteGovernor(this)); this._registerModule(new ModuleTargetFilter(this)); this._registerModule(new ModulePeriodicProcess(this)); - // Essential Modules this._registerModule(new ModuleInitializerFile(this)); this._registerModule(new ModuleObsidianAPI(this, this)); this._registerModule(new ModuleObsidianEvents(this, this)); - this._registerModule(new ModuleFileAccessObsidian(this, this)); this._registerModule(new ModuleObsidianSettings(this)); this._registerModule(new ModuleResolvingMismatchedTweaks(this)); this._registerModule(new ModuleObsidianSettingsAsMarkdown(this)); this._registerModule(new ModuleObsidianSettingDialogue(this, this)); this._registerModule(new ModuleLog(this, this)); this._registerModule(new ModuleObsidianMenu(this)); - this._registerModule(new ModuleRebuilder(this)); this._registerModule(new ModuleSetupObsidian(this)); this._registerModule(new ModuleObsidianDocumentHistory(this, this)); this._registerModule(new ModuleMigration(this)); @@ -172,6 +182,26 @@ export default class ObsidianLiveSyncPlugin this._registerModule(new SetupManager(this)); } + /** + * Bind module functions to services. + */ + private bindModuleFunctions() { + for (const module of this.modules) { + if (module instanceof AbstractModule) { + module.onBindFunction(this, this.services); + __$checkInstanceBinding(module); // Check if all functions are properly bound, and log warnings if not. + } else { + this.services.API.addLog( + `Module ${module.constructor.name} does not have onBindFunction, skipping binding.`, + LOG_LEVEL_INFO + ); + } + } + } + + /** + * @obsolete Use services.UI.confirm instead. The confirm function to show a confirmation dialog to the user. + */ get confirm(): Confirm { return this.services.UI.confirm; } @@ -183,39 +213,68 @@ export default class ObsidianLiveSyncPlugin return this.settings; } + /** + * @obsolete Use services.database.localDatabase instead. The local database instance. + */ get localDatabase() { return this.services.database.localDatabase; } + /** + * @obsolete Use services.database.managers instead. The database managers, including entry manager, revision manager, etc. + */ get managers() { return this.services.database.managers; } + /** + * @obsolete Use services.database.localDatabase instead. Get the PouchDB database instance. Note that this is not the same as the local database instance, which is a wrapper around the PouchDB database. + * @returns The PouchDB database instance. + */ getDatabase(): PouchDB.Database { return this.localDatabase.localDatabase; } + /** + * @obsolete Use services.keyValueDB.simpleStore instead. A simple key-value store for storing non-file data, such as checkpoints, sync status, etc. + */ get simpleStore() { return this.services.keyValueDB.simpleStore as SimpleStore; } + /** + * @obsolete Use services.replication.getActiveReplicator instead. Get the active replicator instance. Note that there can be multiple replicators, but only one can be active at a time. + */ get replicator() { return this.services.replicator.getActiveReplicator()!; } - // initialised at ModuleFileAccessObsidian - storageAccess!: StorageAccess; - // initialised at ModuleDatabaseFileAccess - databaseFileAccess!: DatabaseFileAccess; - // initialised at ModuleFileHandler - fileHandler!: ModuleFileHandler; - // initialised at ModuleRebuilder - rebuilder!: Rebuilder; - + /** + * @obsolete Use services.keyValueDB.kvDB instead. Get the key-value database instance. This is used for storing large data that cannot be stored in the simple store, such as file metadata, etc. + */ get kvDB() { return this.services.keyValueDB.kvDB; } + /// Modules which were relied on services + /** + * Storage Accessor for handling file operations. + */ + storageAccess: StorageAccess; + /** + * Database File Accessor for handling file operations related to the database, such as exporting the database, importing from a file, etc. + */ + databaseFileAccess: DatabaseFileAccess; + + /** + * File Handler for handling file operations related to replication, such as resolving conflicts, applying changes from replication, etc. + */ + fileHandler: IFileHandler; + /** + * Rebuilder for handling database rebuilding operations. + */ + rebuilder: Rebuilder; + requestCount = reactiveSource(0); responseCount = reactiveSource(0); totalQueued = reactiveSource(0); @@ -240,11 +299,74 @@ export default class ObsidianLiveSyncPlugin syncStatus: "CLOSED" as DatabaseConnectingStatus, }); + private initialiseServiceModules() { + const storageAccessManager = new StorageAccessManager(); + // If we want to implement to the other platform, implement ObsidianXXXXXService. + const vaultAccess = new ObsidianFileAccess(this.app, this, storageAccessManager); + const storageEventManager = new StorageEventManagerObsidian(this, this, storageAccessManager); + const storageAccess = new ServiceFileAccessObsidian({ + API: this.services.API, + setting: this.services.setting, + fileProcessing: this.services.fileProcessing, + vault: this.services.vault, + appLifecycle: this.services.appLifecycle, + storageEventManager: storageEventManager, + storageAccessManager: storageAccessManager, + vaultAccess: vaultAccess, + }); + + const databaseFileAccess = new ServiceDatabaseFileAccess({ + API: this.services.API, + database: this.services.database, + path: this.services.path, + storageAccess: storageAccess, + vault: this.services.vault, + }); + + const fileHandler = new ServiceFileHandler({ + API: this.services.API, + databaseFileAccess: databaseFileAccess, + conflict: this.services.conflict, + setting: this.services.setting, + fileProcessing: this.services.fileProcessing, + vault: this.services.vault, + path: this.services.path, + replication: this.services.replication, + storageAccess: storageAccess, + }); + const rebuilder = new ServiceRebuilder({ + API: this.services.API, + database: this.services.database, + appLifecycle: this.services.appLifecycle, + setting: this.services.setting, + remote: this.services.remote, + databaseEvents: this.services.databaseEvents, + replication: this.services.replication, + replicator: this.services.replicator, + UI: this.services.UI, + vault: this.services.vault, + fileHandler: fileHandler, + storageAccess: storageAccess, + }); + return { + rebuilder, + fileHandler, + databaseFileAccess, + storageAccess, + }; + } + constructor(app: App, manifest: PluginManifest) { super(app, manifest); this.initialiseServices(); this.registerModules(); this.registerAddOns(); + const instances = this.initialiseServiceModules(); + this.rebuilder = instances.rebuilder; + this.fileHandler = instances.fileHandler; + this.databaseFileAccess = instances.databaseFileAccess; + this.storageAccess = instances.storageAccess; + this.bindModuleFunctions(); } private async _startUp() { diff --git a/src/modules/AbstractModule.ts b/src/modules/AbstractModule.ts index 4e63d00..d208bc7 100644 --- a/src/modules/AbstractModule.ts +++ b/src/modules/AbstractModule.ts @@ -1,7 +1,6 @@ import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger"; import type { AnyEntry, FilePathWithPrefix } from "@lib/common/types"; import type { LiveSyncCore } from "@/main"; -import { __$checkInstanceBinding } from "@lib/dev/checks"; import { stripAllPrefixes } from "@lib/string_and_binary/path"; import { createInstanceLogFunction } from "@/lib/src/services/lib/logUtils"; @@ -41,9 +40,7 @@ export abstract class AbstractModule { // Override if needed. } constructor(public core: LiveSyncCore) { - this.onBindFunction(core, core.services); Logger(`[${this.constructor.name}] Loaded`, LOG_LEVEL_VERBOSE); - __$checkInstanceBinding(this); } saveSettings = this.core.saveSettings.bind(this.core); diff --git a/src/modules/core/ModuleDatabaseFileAccess.ts b/src/modules/core/ModuleDatabaseFileAccess.ts deleted file mode 100644 index d3024af..0000000 --- a/src/modules/core/ModuleDatabaseFileAccess.ts +++ /dev/null @@ -1,352 +0,0 @@ -import { LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger"; -import { EVENT_FILE_SAVED, eventHub } from "../../common/events"; -import { - getDatabasePathFromUXFileInfo, - getStoragePathFromUXFileInfo, - isInternalMetadata, - markChangesAreSame, -} from "../../common/utils"; -import type { - UXFileInfoStub, - FilePathWithPrefix, - UXFileInfo, - MetaEntry, - LoadedEntry, - FilePath, - SavingEntry, - DocumentID, -} from "../../lib/src/common/types"; -import type { DatabaseFileAccess } from "../interfaces/DatabaseFileAccess"; -import { isPlainText, shouldBeIgnored, stripAllPrefixes } from "../../lib/src/string_and_binary/path"; -import { - createBlob, - createTextBlob, - delay, - determineTypeFromBlob, - isDocContentSame, - readContent, -} from "../../lib/src/common/utils"; -import { serialized } from "octagonal-wheels/concurrency/lock"; -import { AbstractModule } from "../AbstractModule.ts"; -import { ICHeader } from "../../common/types.ts"; -import type { LiveSyncCore } from "../../main.ts"; - -export class ModuleDatabaseFileAccess extends AbstractModule implements DatabaseFileAccess { - private _everyOnload(): Promise { - this.core.databaseFileAccess = this; - return Promise.resolve(true); - } - - private async _everyModuleTest(): Promise { - if (!this.settings.enableDebugTools) return Promise.resolve(true); - const testString = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec purus nec nunc"; - // Before test, we need to delete completely. - const conflicts = await this.getConflictedRevs("autoTest.md" as FilePathWithPrefix); - for (const rev of conflicts) { - await this.delete("autoTest.md" as FilePathWithPrefix, rev); - } - await this.delete("autoTest.md" as FilePathWithPrefix); - // OK, begin! - - await this._test( - "storeContent", - async () => await this.storeContent("autoTest.md" as FilePathWithPrefix, testString) - ); - // For test, we need to clear the caches. - this.localDatabase.clearCaches(); - await this._test("readContent", async () => { - const content = await this.fetch("autoTest.md" as FilePathWithPrefix); - if (!content) return "File not found"; - if (content.deleted) return "File is deleted"; - return (await content.body.text()) == testString - ? true - : `Content is not same ${await content.body.text()}`; - }); - await this._test("delete", async () => await this.delete("autoTest.md" as FilePathWithPrefix)); - await this._test("read deleted content", async () => { - const content = await this.fetch("autoTest.md" as FilePathWithPrefix); - if (!content) return true; - if (content.deleted) return true; - return `Still exist !:${await content.body.text()},${JSON.stringify(content, undefined, 2)}`; - }); - await delay(100); - return this.testDone(); - } - - async checkIsTargetFile(file: UXFileInfoStub | FilePathWithPrefix): Promise { - const path = getStoragePathFromUXFileInfo(file); - if (!(await this.services.vault.isTargetFile(path))) { - this._log(`File is not target: ${path}`, LOG_LEVEL_VERBOSE); - return false; - } - if (shouldBeIgnored(path)) { - this._log(`File should be ignored: ${path}`, LOG_LEVEL_VERBOSE); - return false; - } - return true; - } - - async delete(file: UXFileInfoStub | FilePathWithPrefix, rev?: string): Promise { - if (!(await this.checkIsTargetFile(file))) { - return true; - } - const fullPath = getDatabasePathFromUXFileInfo(file); - try { - this._log(`deleteDB By path:${fullPath}`); - return await this.deleteFromDBbyPath(fullPath, rev); - } catch (ex) { - this._log(`Failed to delete ${fullPath}`); - this._log(ex, LOG_LEVEL_VERBOSE); - return false; - } - } - - async createChunks(file: UXFileInfo, force: boolean = false, skipCheck?: boolean): Promise { - return await this.__store(file, force, skipCheck, true); - } - - async store(file: UXFileInfo, force: boolean = false, skipCheck?: boolean): Promise { - return await this.__store(file, force, skipCheck, false); - } - async storeContent(path: FilePathWithPrefix, content: string): Promise { - const blob = createTextBlob(content); - const bytes = (await blob.arrayBuffer()).byteLength; - const isInternal = path.startsWith(".") ? true : undefined; - const dummyUXFileInfo: UXFileInfo = { - name: path.split("/").pop() as string, - path: path, - stat: { - size: bytes, - ctime: Date.now(), - mtime: Date.now(), - type: "file", - }, - body: blob, - isInternal, - }; - return await this.__store(dummyUXFileInfo, true, false, false); - } - - private async __store( - file: UXFileInfo, - force: boolean = false, - skipCheck?: boolean, - onlyChunks?: boolean - ): Promise { - if (!skipCheck) { - if (!(await this.checkIsTargetFile(file))) { - return true; - } - } - if (!file) { - this._log("File seems bad", LOG_LEVEL_VERBOSE); - return false; - } - // const path = getPathFromUXFileInfo(file); - const isPlain = isPlainText(file.name); - const possiblyLarge = !isPlain; - const content = file.body; - - const datatype = determineTypeFromBlob(content); - const idPrefix = file.isInternal ? ICHeader : ""; - const fullPath = getStoragePathFromUXFileInfo(file); - const fullPathOnDB = getDatabasePathFromUXFileInfo(file); - - if (possiblyLarge) this._log(`Processing: ${fullPath}`, LOG_LEVEL_VERBOSE); - - // if (isInternalMetadata(fullPath)) { - // this._log(`Internal file: ${fullPath}`, LOG_LEVEL_VERBOSE); - // return false; - // } - if (file.isInternal) { - if (file.deleted) { - file.stat = { - size: 0, - ctime: Date.now(), - mtime: Date.now(), - type: "file", - }; - } else if (file.stat == undefined) { - const stat = await this.core.storageAccess.statHidden(file.path); - if (!stat) { - // We stored actually deleted or not since here, so this is an unexpected case. we should raise an error. - this._log(`Internal file not found: ${fullPath}`, LOG_LEVEL_VERBOSE); - return false; - } - file.stat = stat; - } - } - - const idMain = await this.services.path.path2id(fullPath); - - const id = (idPrefix + idMain) as DocumentID; - const d: SavingEntry = { - _id: id, - path: fullPathOnDB, - data: content, - ctime: file.stat.ctime, - mtime: file.stat.mtime, - size: file.stat.size, - children: [], - datatype: datatype, - type: datatype, - eden: {}, - }; - //upsert should locked - const msg = `STORAGE -> DB (${datatype}) `; - const isNotChanged = await serialized("file-" + fullPath, async () => { - if (force) { - this._log(msg + "Force writing " + fullPath, LOG_LEVEL_VERBOSE); - return false; - } - // Commented out temporarily: this checks that the file was made ourself. - // if (this.core.storageAccess.recentlyTouched(file)) { - // return true; - // } - try { - const old = await this.localDatabase.getDBEntry(d.path, undefined, false, true, false); - if (old !== false) { - const oldData = { data: old.data, deleted: old._deleted || old.deleted }; - const newData = { data: d.data, deleted: d._deleted || d.deleted }; - if (oldData.deleted != newData.deleted) return false; - if (!(await isDocContentSame(old.data, newData.data))) return false; - this._log( - msg + "Skipped (not changed) " + fullPath + (d._deleted || d.deleted ? " (deleted)" : ""), - LOG_LEVEL_VERBOSE - ); - markChangesAreSame(old, d.mtime, old.mtime); - return true; - // d._rev = old._rev; - } - } catch (ex) { - this._log( - msg + - "Error, Could not check the diff for the old one." + - (force ? "force writing." : "") + - fullPath + - (d._deleted || d.deleted ? " (deleted)" : ""), - LOG_LEVEL_VERBOSE - ); - this._log(ex, LOG_LEVEL_VERBOSE); - return !force; - } - return false; - }); - if (isNotChanged) { - this._log(msg + " Skip " + fullPath, LOG_LEVEL_VERBOSE); - return true; - } - const ret = await this.localDatabase.putDBEntry(d, onlyChunks); - if (ret !== false) { - this._log(msg + fullPath); - eventHub.emitEvent(EVENT_FILE_SAVED); - } - return ret != false; - } - - async getConflictedRevs(file: UXFileInfoStub | FilePathWithPrefix): Promise { - if (!(await this.checkIsTargetFile(file))) { - return []; - } - const filename = getDatabasePathFromUXFileInfo(file); - const doc = await this.localDatabase.getDBEntryMeta(filename, { conflicts: true }, true); - if (doc === false) { - return []; - } - return doc._conflicts || []; - } - - async fetch( - file: UXFileInfoStub | FilePathWithPrefix, - rev?: string, - waitForReady?: boolean, - skipCheck = false - ): Promise { - if (skipCheck && !(await this.checkIsTargetFile(file))) { - return false; - } - - const entry = await this.fetchEntry(file, rev, waitForReady, true); - if (entry === false) { - return false; - } - const data = createBlob(readContent(entry)); - const path = stripAllPrefixes(entry.path); - const fileInfo: UXFileInfo = { - name: path.split("/").pop() as string, - path: path, - stat: { - size: entry.size, - ctime: entry.ctime, - mtime: entry.mtime, - type: "file", - }, - body: data, - deleted: entry.deleted || entry._deleted, - }; - if (isInternalMetadata(entry.path)) { - fileInfo.isInternal = true; - } - return fileInfo; - } - async fetchEntryMeta( - file: UXFileInfoStub | FilePathWithPrefix, - rev?: string, - skipCheck = false - ): Promise { - const dbFileName = getDatabasePathFromUXFileInfo(file); - if (skipCheck && !(await this.checkIsTargetFile(file))) { - return false; - } - - const doc = await this.localDatabase.getDBEntryMeta(dbFileName, rev ? { rev: rev } : undefined, true); - if (doc === false) { - return false; - } - return doc as MetaEntry; - } - async fetchEntryFromMeta( - meta: MetaEntry, - waitForReady: boolean = true, - skipCheck = false - ): Promise { - if (skipCheck && !(await this.checkIsTargetFile(meta.path))) { - return false; - } - const doc = await this.localDatabase.getDBEntryFromMeta(meta as LoadedEntry, false, waitForReady); - if (doc === false) { - return false; - } - return doc; - } - async fetchEntry( - file: UXFileInfoStub | FilePathWithPrefix, - rev?: string, - waitForReady: boolean = true, - skipCheck = false - ): Promise { - if (skipCheck && !(await this.checkIsTargetFile(file))) { - return false; - } - const entry = await this.fetchEntryMeta(file, rev, true); - if (entry === false) { - return false; - } - const doc = await this.fetchEntryFromMeta(entry, waitForReady, true); - return doc; - } - async deleteFromDBbyPath(fullPath: FilePath | FilePathWithPrefix, rev?: string): Promise { - if (!(await this.checkIsTargetFile(fullPath))) { - this._log(`storeFromStorage: File is not target: ${fullPath}`); - return true; - } - const opt = rev ? { rev: rev } : undefined; - const ret = await this.localDatabase.deleteDBEntry(fullPath, opt); - eventHub.emitEvent(EVENT_FILE_SAVED); - return ret; - } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { - services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this)); - services.test.test.addHandler(this._everyModuleTest.bind(this)); - } -} diff --git a/src/modules/core/ModuleFileHandler.ts b/src/modules/core/ModuleFileHandler.ts deleted file mode 100644 index 8094fd6..0000000 --- a/src/modules/core/ModuleFileHandler.ts +++ /dev/null @@ -1,436 +0,0 @@ -import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger"; -import { serialized } from "octagonal-wheels/concurrency/lock"; -import type { FileEventItem } from "../../common/types"; -import type { - FilePath, - FilePathWithPrefix, - MetaEntry, - UXFileInfo, - UXFileInfoStub, - UXInternalFileInfoStub, -} from "../../lib/src/common/types"; -import { AbstractModule } from "../AbstractModule.ts"; -import { compareFileFreshness, EVEN, getStoragePathFromUXFileInfo, markChangesAreSame } from "../../common/utils"; -import { getDocDataAsArray, isDocContentSame, readAsBlob, readContent } from "../../lib/src/common/utils"; -import { shouldBeIgnored } from "../../lib/src/string_and_binary/path"; -import { Semaphore } from "octagonal-wheels/concurrency/semaphore"; -import { eventHub } from "../../common/events.ts"; -import type { LiveSyncCore } from "../../main.ts"; - -export class ModuleFileHandler extends AbstractModule { - get db() { - return this.core.databaseFileAccess; - } - get storage() { - return this.core.storageAccess; - } - - _everyOnloadStart(): Promise { - this.core.fileHandler = this; - return Promise.resolve(true); - } - - async readFileFromStub(file: UXFileInfoStub | UXFileInfo) { - if ("body" in file && file.body) { - return file; - } - const readFile = await this.storage.readStubContent(file); - if (!readFile) { - throw new Error(`File ${file.path} is not exist on the storage`); - } - return readFile; - } - - async storeFileToDB( - info: UXFileInfoStub | UXFileInfo | UXInternalFileInfoStub | FilePathWithPrefix, - force: boolean = false, - onlyChunks: boolean = false - ): Promise { - const file = typeof info === "string" ? this.storage.getFileStub(info) : info; - if (file == null) { - this._log(`File ${info} is not exist on the storage`, LOG_LEVEL_VERBOSE); - return false; - } - // const file = item.args.file; - if (file.isInternal) { - this._log( - `Internal file ${file.path} is not allowed to be processed on processFileEvent`, - LOG_LEVEL_VERBOSE - ); - return false; - } - // First, check the file on the database - const entry = await this.db.fetchEntry(file, undefined, true, true); - - if (!entry || entry.deleted || entry._deleted) { - // If the file is not exist on the database, then it should be created. - const readFile = await this.readFileFromStub(file); - if (!onlyChunks) { - return await this.db.store(readFile); - } else { - return await this.db.createChunks(readFile, false, true); - } - } - - // entry is exist on the database, check the difference between the file and the entry. - - let shouldApplied = false; - if (!force && !onlyChunks) { - // 1. if the time stamp is far different, then it should be updated. - // Note: This checks only the mtime with the resolution reduced to 2 seconds. - // 2 seconds it for the ZIP file's mtime. If not, we cannot backup the vault as the ZIP file. - // This is hardcoded on `compareMtime` of `src/common/utils.ts`. - if (compareFileFreshness(file, entry) !== EVEN) { - shouldApplied = true; - } - // 2. if not, the content should be checked. - let readFile: UXFileInfo | undefined = undefined; - if (!shouldApplied) { - readFile = await this.readFileFromStub(file); - if (!readFile) { - this._log(`File ${file.path} is not exist on the storage`, LOG_LEVEL_NOTICE); - return false; - } - if (await isDocContentSame(getDocDataAsArray(entry.data), readFile.body)) { - // Timestamp is different but the content is same. therefore, two timestamps should be handled as same. - // So, mark the changes are same. - markChangesAreSame(readFile, readFile.stat.mtime, entry.mtime); - } else { - shouldApplied = true; - } - } - - if (!shouldApplied) { - this._log(`File ${file.path} is not changed`, LOG_LEVEL_VERBOSE); - return true; - } - if (!readFile) readFile = await this.readFileFromStub(file); - // If the file is changed, then the file should be stored. - if (onlyChunks) { - return await this.db.createChunks(readFile, false, true); - } else { - return await this.db.store(readFile, false, true); - } - } else { - // If force is true, then it should be updated. - const readFile = await this.readFileFromStub(file); - if (onlyChunks) { - return await this.db.createChunks(readFile, true, true); - } else { - return await this.db.store(readFile, true, true); - } - } - } - - async deleteFileFromDB(info: UXFileInfoStub | UXInternalFileInfoStub | FilePath): Promise { - const file = typeof info === "string" ? this.storage.getFileStub(info) : info; - if (file == null) { - this._log(`File ${info} is not exist on the storage`, LOG_LEVEL_VERBOSE); - return false; - } - // const file = item.args.file; - if (file.isInternal) { - this._log( - `Internal file ${file.path} is not allowed to be processed on processFileEvent`, - LOG_LEVEL_VERBOSE - ); - return false; - } - // First, check the file on the database - const entry = await this.db.fetchEntry(file, undefined, true, true); - if (!entry || entry.deleted || entry._deleted) { - this._log(`File ${file.path} is not exist or already deleted on the database`, LOG_LEVEL_VERBOSE); - return false; - } - // Check the file is already conflicted. if so, only the conflicted one should be deleted. - const conflictedRevs = await this.db.getConflictedRevs(file); - if (conflictedRevs.length > 0) { - // If conflicted, then it should be deleted. entry._rev should be own file's rev. - // TODO: I BELIEVED SO. BUT I NOTICED THAT I AN NOT SURE. I SHOULD CHECK THIS. - // ANYWAY, I SHOULD DELETE THE FILE. ACTUALLY WE SIMPLY DELETED THE FILE UNTIL PREVIOUS VERSIONS. - return await this.db.delete(file, entry._rev); - } - // Otherwise, the file should be deleted simply. This is the previous behaviour. - return await this.db.delete(file); - } - - async deleteRevisionFromDB( - info: UXFileInfoStub | FilePath | FilePathWithPrefix, - rev: string - ): Promise { - //TODO: Possibly check the conflicting. - return await this.db.delete(info, rev); - } - - async resolveConflictedByDeletingRevision( - info: UXFileInfoStub | FilePath, - rev: string - ): Promise { - const path = getStoragePathFromUXFileInfo(info); - if (!(await this.deleteRevisionFromDB(info, rev))) { - this._log(`Failed to delete the conflicted revision ${rev} of ${path}`, LOG_LEVEL_VERBOSE); - return false; - } - if (!(await this.dbToStorageWithSpecificRev(info, rev, true))) { - this._log(`Failed to apply the resolved revision ${rev} of ${path} to the storage`, LOG_LEVEL_VERBOSE); - return false; - } - } - - async dbToStorageWithSpecificRev( - info: UXFileInfoStub | UXFileInfo | FilePath | null, - rev: string, - force?: boolean - ): Promise { - const file = typeof info === "string" ? this.storage.getFileStub(info) : info; - if (file == null) { - this._log(`File ${info} is not exist on the storage`, LOG_LEVEL_VERBOSE); - return false; - } - const docEntry = await this.db.fetchEntryMeta(file, rev, true); - if (!docEntry) { - this._log(`File ${file.path} is not exist on the database`, LOG_LEVEL_VERBOSE); - return false; - } - return await this.dbToStorage(docEntry, file, force); - } - - async dbToStorage( - entryInfo: MetaEntry | FilePathWithPrefix, - info: UXFileInfoStub | UXFileInfo | FilePath | null, - force?: boolean - ): Promise { - const file = typeof info === "string" ? this.storage.getFileStub(info) : info; - const mode = file == null ? "create" : "modify"; - const pathFromEntryInfo = typeof entryInfo === "string" ? entryInfo : this.getPath(entryInfo); - const docEntry = await this.db.fetchEntryMeta(pathFromEntryInfo, undefined, true); - if (!docEntry) { - this._log(`File ${pathFromEntryInfo} is not exist on the database`, LOG_LEVEL_VERBOSE); - return false; - } - const path = this.getPath(docEntry); - - // 1. Check if it already conflicted. - const revs = await this.db.getConflictedRevs(path); - if (revs.length > 0) { - // Some conflicts are exist. - if (this.settings.writeDocumentsIfConflicted) { - // If configured to write the document even if conflicted, then it should be written. - // NO OP - } else { - // If not, then it should be checked. and will be processed later (i.e., after the conflict is resolved). - await this.services.conflict.queueCheckForIfOpen(path); - return true; - } - } - - // 2. Check if the file is already exist on the storage. - const existDoc = this.storage.getStub(path); - if (existDoc && existDoc.isFolder) { - this._log(`Folder ${path} is already exist on the storage as a folder`, LOG_LEVEL_VERBOSE); - // We can do nothing, and other modules should also nothing to do. - return true; - } - - // Check existence of both file and docEntry. - const existOnDB = !(docEntry._deleted || docEntry.deleted || false); - const existOnStorage = existDoc != null; - if (!existOnDB && !existOnStorage) { - this._log(`File ${path} seems to be deleted, but already not on storage`, LOG_LEVEL_VERBOSE); - return true; - } - if (!existOnDB && existOnStorage) { - // Deletion has been Transferred. Storage files will be deleted. - // Note: If the folder becomes empty, the folder will be deleted if not configured to keep it. - // This behaviour is implemented on the `ModuleFileAccessObsidian`. - // And it does not care actually deleted. - await this.storage.deleteVaultItem(path); - return true; - } - // Okay, the file is exist on the database. Let's check the file is exist on the storage. - const docRead = await this.db.fetchEntryFromMeta(docEntry); - if (!docRead) { - this._log(`File ${path} is not exist on the database`, LOG_LEVEL_VERBOSE); - return false; - } - - // If we want to process size mismatched files -- in case of having files created by some integrations, enable the toggle. - if (!this.settings.processSizeMismatchedFiles) { - // Check the file is not corrupted - // (Zero is a special case, may be created by some APIs and it might be acceptable). - if (docRead.size != 0 && docRead.size !== readAsBlob(docRead).size) { - this._log( - `File ${path} seems to be corrupted! Writing prevented. (${docRead.size} != ${readAsBlob(docRead).size})`, - LOG_LEVEL_NOTICE - ); - return false; - } - } - - const docData = readContent(docRead); - - if (existOnStorage && !force) { - // The file is exist on the storage. Let's check the difference between the file and the entry. - // But, if force is true, then it should be updated. - // Ok, we have to compare. - let shouldApplied = false; - // 1. if the time stamp is far different, then it should be updated. - // Note: This checks only the mtime with the resolution reduced to 2 seconds. - // 2 seconds it for the ZIP file's mtime. If not, we cannot backup the vault as the ZIP file. - // This is hardcoded on `compareMtime` of `src/common/utils.ts`. - if (compareFileFreshness(existDoc, docEntry) !== EVEN) { - shouldApplied = true; - } - // 2. if not, the content should be checked. - - if (!shouldApplied) { - const readFile = await this.readFileFromStub(existDoc); - if (await isDocContentSame(docData, readFile.body)) { - // The content is same. So, we do not need to update the file. - shouldApplied = false; - // Timestamp is different but the content is same. therefore, two timestamps should be handled as same. - // So, mark the changes are same. - markChangesAreSame(docRead, docRead.mtime, existDoc.stat.mtime); - } else { - shouldApplied = true; - } - } - if (!shouldApplied) { - this._log(`File ${docRead.path} is not changed`, LOG_LEVEL_VERBOSE); - return true; - } - // Let's apply the changes. - } else { - this._log( - `File ${docRead.path} ${existOnStorage ? "(new) " : ""} ${force ? " (forced)" : ""}`, - LOG_LEVEL_VERBOSE - ); - } - await this.storage.ensureDir(path); - const ret = await this.storage.writeFileAuto(path, docData, { ctime: docRead.ctime, mtime: docRead.mtime }); - await this.storage.touched(path); - this.storage.triggerFileEvent(mode, path); - return ret; - } - - private async _anyHandlerProcessesFileEvent(item: FileEventItem): Promise { - const eventItem = item.args; - const type = item.type; - const path = eventItem.file.path; - if (!(await this.services.vault.isTargetFile(path))) { - this._log(`File ${path} is not the target file`, LOG_LEVEL_VERBOSE); - return false; - } - if (shouldBeIgnored(path)) { - this._log(`File ${path} should be ignored`, LOG_LEVEL_VERBOSE); - return false; - } - const lockKey = `processFileEvent-${path}`; - return await serialized(lockKey, async () => { - switch (type) { - case "CREATE": - case "CHANGED": - return await this.storeFileToDB(item.args.file); - case "DELETE": - return await this.deleteFileFromDB(item.args.file); - case "INTERNAL": - // this should be handled on the other module. - return false; - default: - this._log(`Unsupported event type: ${type}`, LOG_LEVEL_VERBOSE); - return false; - } - }); - } - - async _anyProcessReplicatedDoc(entry: MetaEntry): Promise { - return await serialized(entry.path, async () => { - if (!(await this.services.vault.isTargetFile(entry.path))) { - this._log(`File ${entry.path} is not the target file`, LOG_LEVEL_VERBOSE); - return false; - } - if (this.services.vault.isFileSizeTooLarge(entry.size)) { - this._log(`File ${entry.path} is too large (on database) to be processed`, LOG_LEVEL_VERBOSE); - return false; - } - if (shouldBeIgnored(entry.path)) { - this._log(`File ${entry.path} should be ignored`, LOG_LEVEL_VERBOSE); - return false; - } - const path = this.getPath(entry); - - const targetFile = this.storage.getStub(this.getPathWithoutPrefix(entry)); - if (targetFile && targetFile.isFolder) { - this._log(`${path} is already exist as the folder`); - // Nothing to do and other modules should also nothing to do. - return true; - } else { - if (targetFile && this.services.vault.isFileSizeTooLarge(targetFile.stat.size)) { - this._log(`File ${targetFile.path} is too large (on storage) to be processed`, LOG_LEVEL_VERBOSE); - return false; - } - this._log( - `Processing ${path} (${entry._id.substring(0, 8)} :${entry._rev?.substring(0, 5)}) : Started...`, - LOG_LEVEL_VERBOSE - ); - // Before writing (or skipped ), merging dialogue should be cancelled. - eventHub.emitEvent("conflict-cancelled", path); - const ret = await this.dbToStorage(entry, targetFile); - this._log(`Processing ${path} (${entry._id.substring(0, 8)} :${entry._rev?.substring(0, 5)}) : Done`); - return ret; - } - }); - } - - async createAllChunks(showingNotice?: boolean): Promise { - this._log("Collecting local files on the storage", LOG_LEVEL_VERBOSE); - const semaphore = Semaphore(10); - - let processed = 0; - const filesStorageSrc = this.storage.getFiles(); - const incProcessed = () => { - processed++; - if (processed % 25 == 0) - this._log( - `Creating missing chunks: ${processed} of ${total} files`, - showingNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO, - "chunkCreation" - ); - }; - const total = filesStorageSrc.length; - const procAllChunks = filesStorageSrc.map(async (file) => { - if (!(await this.services.vault.isTargetFile(file))) { - incProcessed(); - return true; - } - if (this.services.vault.isFileSizeTooLarge(file.stat.size)) { - incProcessed(); - return true; - } - if (shouldBeIgnored(file.path)) { - incProcessed(); - return true; - } - const release = await semaphore.acquire(); - incProcessed(); - try { - await this.storeFileToDB(file, false, true); - } catch (ex) { - this._log(ex, LOG_LEVEL_VERBOSE); - } finally { - release(); - } - }); - await Promise.all(procAllChunks); - this._log( - `Creating chunks Done: ${processed} of ${total} files`, - showingNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO, - "chunkCreation" - ); - } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { - services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); - services.fileProcessing.processFileEvent.addHandler(this._anyHandlerProcessesFileEvent.bind(this)); - services.replication.processSynchroniseResult.addHandler(this._anyProcessReplicatedDoc.bind(this)); - } -} diff --git a/src/modules/core/ModuleRebuilder.ts b/src/modules/core/ModuleRebuilder.ts deleted file mode 100644 index a38d286..0000000 --- a/src/modules/core/ModuleRebuilder.ts +++ /dev/null @@ -1,313 +0,0 @@ -import { delay } from "octagonal-wheels/promises"; -import { - DEFAULT_SETTINGS, - FLAGMD_REDFLAG2_HR, - FLAGMD_REDFLAG3_HR, - LOG_LEVEL_NOTICE, - LOG_LEVEL_VERBOSE, - REMOTE_COUCHDB, - REMOTE_MINIO, -} from "../../lib/src/common/types.ts"; -import { AbstractModule } from "../AbstractModule.ts"; -import type { Rebuilder } from "../interfaces/DatabaseRebuilder.ts"; -import type { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator.ts"; -import { fetchAllUsedChunks } from "@/lib/src/pouchdb/chunks.ts"; -import { EVENT_DATABASE_REBUILT, eventHub } from "src/common/events.ts"; -import type { LiveSyncCore } from "../../main.ts"; - -export class ModuleRebuilder extends AbstractModule implements Rebuilder { - private _everyOnload(): Promise { - this.core.rebuilder = this; - return Promise.resolve(true); - } - async $performRebuildDB( - method: "localOnly" | "remoteOnly" | "rebuildBothByThisDevice" | "localOnlyWithChunks" - ): Promise { - if (method == "localOnly") { - await this.$fetchLocal(); - } - if (method == "localOnlyWithChunks") { - await this.$fetchLocal(true); - } - if (method == "remoteOnly") { - await this.$rebuildRemote(); - } - if (method == "rebuildBothByThisDevice") { - await this.$rebuildEverything(); - } - } - - async informOptionalFeatures() { - await this.core.services.UI.showMarkdownDialog( - "All optional features are disabled", - `Customisation Sync and Hidden File Sync will all be disabled. -Please enable them from the settings screen after setup is complete.`, - ["OK"] - ); - } - async askUsingOptionalFeature(opt: { enableFetch?: boolean; enableOverwrite?: boolean }) { - if ( - (await this.core.confirm.askYesNoDialog( - "Do you want to enable extra features? If you are new to Self-hosted LiveSync, try the core feature first!", - { title: "Enable extra features", defaultOption: "No", timeout: 15 } - )) == "yes" - ) { - await this.services.setting.suggestOptionalFeatures(opt); - } - } - - async rebuildRemote() { - await this.services.setting.suspendExtraSync(); - this.core.settings.isConfigured = true; - this.core.settings.notifyThresholdOfRemoteStorageSize = DEFAULT_SETTINGS.notifyThresholdOfRemoteStorageSize; - await this.services.setting.realiseSetting(); - await this.services.remote.markLocked(); - await this.services.remote.tryResetDatabase(); - await this.services.remote.markLocked(); - await delay(500); - // await this.askUsingOptionalFeature({ enableOverwrite: true }); - await delay(1000); - await this.services.remote.replicateAllToRemote(true); - await delay(1000); - await this.services.remote.replicateAllToRemote(true, true); - await this.informOptionalFeatures(); - } - $rebuildRemote(): Promise { - return this.rebuildRemote(); - } - - async rebuildEverything() { - await this.services.setting.suspendExtraSync(); - // await this.askUseNewAdapter(); - this.core.settings.isConfigured = true; - this.core.settings.notifyThresholdOfRemoteStorageSize = DEFAULT_SETTINGS.notifyThresholdOfRemoteStorageSize; - await this.services.setting.realiseSetting(); - await this.resetLocalDatabase(); - await delay(1000); - await this.services.databaseEvents.initialiseDatabase(true, true, true); - await this.services.remote.markLocked(); - await this.services.remote.tryResetDatabase(); - await this.services.remote.markLocked(); - await delay(500); - // We do not have any other devices' data, so we do not need to ask for overwriting. - // await this.askUsingOptionalFeature({ enableOverwrite: false }); - await delay(1000); - await this.services.remote.replicateAllToRemote(true); - await delay(1000); - await this.services.remote.replicateAllToRemote(true, true); - await this.informOptionalFeatures(); - } - - $rebuildEverything(): Promise { - return this.rebuildEverything(); - } - - $fetchLocal(makeLocalChunkBeforeSync?: boolean, preventMakeLocalFilesBeforeSync?: boolean): Promise { - return this.fetchLocal(makeLocalChunkBeforeSync, preventMakeLocalFilesBeforeSync); - } - - async scheduleRebuild(): Promise { - try { - await this.core.storageAccess.writeFileAuto(FLAGMD_REDFLAG2_HR, ""); - } catch (ex) { - this._log("Could not create red_flag_rebuild.md", LOG_LEVEL_NOTICE); - this._log(ex, LOG_LEVEL_VERBOSE); - } - this.services.appLifecycle.performRestart(); - } - async scheduleFetch(): Promise { - try { - await this.core.storageAccess.writeFileAuto(FLAGMD_REDFLAG3_HR, ""); - } catch (ex) { - this._log("Could not create red_flag_fetch.md", LOG_LEVEL_NOTICE); - this._log(ex, LOG_LEVEL_VERBOSE); - } - this.services.appLifecycle.performRestart(); - } - - private async _tryResetRemoteDatabase(): Promise { - await this.core.replicator.tryResetRemoteDatabase(this.settings); - } - - private async _tryCreateRemoteDatabase(): Promise { - await this.core.replicator.tryCreateRemoteDatabase(this.settings); - } - - private _onResetLocalDatabase(): Promise { - this.core.storageAccess.clearTouched(); - return Promise.resolve(true); - } - - async suspendAllSync() { - this.core.settings.liveSync = false; - this.core.settings.periodicReplication = false; - this.core.settings.syncOnSave = false; - this.core.settings.syncOnEditorSave = false; - this.core.settings.syncOnStart = false; - this.core.settings.syncOnFileOpen = false; - this.core.settings.syncAfterMerge = false; - await this.services.setting.suspendExtraSync(); - } - async suspendReflectingDatabase() { - if (this.core.settings.doNotSuspendOnFetching) return; - if (this.core.settings.remoteType == REMOTE_MINIO) return; - this._log( - `Suspending reflection: Database and storage changes will not be reflected in each other until completely finished the fetching.`, - LOG_LEVEL_NOTICE - ); - this.core.settings.suspendParseReplicationResult = true; - this.core.settings.suspendFileWatching = true; - await this.core.saveSettings(); - } - async resumeReflectingDatabase() { - if (this.core.settings.doNotSuspendOnFetching) return; - if (this.core.settings.remoteType == REMOTE_MINIO) return; - this._log(`Database and storage reflection has been resumed!`, LOG_LEVEL_NOTICE); - this.core.settings.suspendParseReplicationResult = false; - this.core.settings.suspendFileWatching = false; - await this.services.vault.scanVault(true); - await this.services.replication.onBeforeReplicate(false); //TODO: Check actual need of this. - await this.core.saveSettings(); - } - // No longer needed, both adapters have each advantages and disadvantages. - // async askUseNewAdapter() { - // if (!this.core.settings.useIndexedDBAdapter) { - // const message = `Now this core has been configured to use the old database adapter for keeping compatibility. Do you want to deactivate it?`; - // const CHOICE_YES = "Yes, disable and use latest"; - // const CHOICE_NO = "No, keep compatibility"; - // const choices = [CHOICE_YES, CHOICE_NO]; - // - // const ret = await this.core.confirm.confirmWithMessage( - // "Database adapter", - // message, - // choices, - // CHOICE_YES, - // 10 - // ); - // if (ret == CHOICE_YES) { - // this.core.settings.useIndexedDBAdapter = true; - // } - // } - // } - async fetchLocal(makeLocalChunkBeforeSync?: boolean, preventMakeLocalFilesBeforeSync?: boolean) { - await this.services.setting.suspendExtraSync(); - // await this.askUseNewAdapter(); - this.core.settings.isConfigured = true; - this.core.settings.notifyThresholdOfRemoteStorageSize = DEFAULT_SETTINGS.notifyThresholdOfRemoteStorageSize; - if (this.core.settings.maxMTimeForReflectEvents > 0) { - const date = new Date(this.core.settings.maxMTimeForReflectEvents); - - const ask = `Your settings restrict file reflection times to no later than ${date}. - -**This is a recovery configuration.** - -This operation should only be performed on an empty vault. -Are you sure you wish to proceed?`; - const PROCEED = "I understand, proceed"; - const CANCEL = "Cancel operation"; - const CLEARANDPROCEED = "Clear restriction and proceed"; - const choices = [PROCEED, CLEARANDPROCEED, CANCEL] as const; - const ret = await this.core.confirm.askSelectStringDialogue(ask, choices, { - title: "Confirm restricted fetch", - defaultAction: CANCEL, - timeout: 0, - }); - if (ret == CLEARANDPROCEED) { - this.core.settings.maxMTimeForReflectEvents = 0; - await this.core.saveSettings(); - } - if (ret == CANCEL) { - return; - } - } - await this.suspendReflectingDatabase(); - await this.services.setting.realiseSetting(); - await this.resetLocalDatabase(); - await delay(1000); - await this.services.database.openDatabase({ - databaseEvents: this.services.databaseEvents, - replicator: this.services.replicator, - }); - // this.core.isReady = true; - this.services.appLifecycle.markIsReady(); - if (makeLocalChunkBeforeSync) { - await this.core.fileHandler.createAllChunks(true); - } else if (!preventMakeLocalFilesBeforeSync) { - await this.services.databaseEvents.initialiseDatabase(true, true, true); - } else { - // Do not create local file entries before sync (Means use remote information) - } - await this.services.remote.markResolved(); - await delay(500); - await this.services.remote.replicateAllFromRemote(true); - await delay(1000); - await this.services.remote.replicateAllFromRemote(true); - await this.resumeReflectingDatabase(); - await this.informOptionalFeatures(); - // No longer enable - // await this.askUsingOptionalFeature({ enableFetch: true }); - } - async fetchLocalWithRebuild() { - return await this.fetchLocal(true); - } - - private async _allSuspendAllSync(): Promise { - await this.suspendAllSync(); - return true; - } - - async resetLocalDatabase() { - if (this.core.settings.isConfigured && this.core.settings.additionalSuffixOfDatabaseName == "") { - // Discard the non-suffixed database - await this.services.database.resetDatabase(); - } - const suffix = this.services.API.getAppID() || ""; - this.core.settings.additionalSuffixOfDatabaseName = suffix; - await this.services.database.resetDatabase(); - eventHub.emitEvent(EVENT_DATABASE_REBUILT); - } - async fetchRemoteChunks() { - if ( - !this.core.settings.doNotSuspendOnFetching && - !this.core.settings.useOnlyLocalChunk && - this.core.settings.remoteType == REMOTE_COUCHDB - ) { - this._log(`Fetching chunks`, LOG_LEVEL_NOTICE); - const replicator = this.services.replicator.getActiveReplicator() as LiveSyncCouchDBReplicator; - const remoteDB = await replicator.connectRemoteCouchDBWithSetting( - this.settings, - this.services.API.isMobile(), - true - ); - if (typeof remoteDB == "string") { - this._log(remoteDB, LOG_LEVEL_NOTICE); - } else { - await fetchAllUsedChunks(this.localDatabase.localDatabase, remoteDB.db); - } - this._log(`Fetching chunks done`, LOG_LEVEL_NOTICE); - } - } - async resolveAllConflictedFilesByNewerOnes() { - this._log(`Resolving conflicts by newer ones`, LOG_LEVEL_NOTICE); - const files = this.core.storageAccess.getFileNames(); - - let i = 0; - for (const file of files) { - if (i++ % 10) - this._log( - `Check and Processing ${i} / ${files.length}`, - LOG_LEVEL_NOTICE, - "resolveAllConflictedFilesByNewerOnes" - ); - await this.services.conflict.resolveByNewest(file); - } - this._log(`Done!`, LOG_LEVEL_NOTICE, "resolveAllConflictedFilesByNewerOnes"); - } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { - services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this)); - services.database.onDatabaseReset.addHandler(this._onResetLocalDatabase.bind(this)); - services.remote.tryResetDatabase.setHandler(this._tryResetRemoteDatabase.bind(this)); - services.remote.tryCreateDatabase.setHandler(this._tryCreateRemoteDatabase.bind(this)); - services.setting.suspendAllSync.addHandler(this._allSuspendAllSync.bind(this)); - } -} diff --git a/src/modules/coreFeatures/ModuleConflictResolver.ts b/src/modules/coreFeatures/ModuleConflictResolver.ts index 9cf02f3..7111665 100644 --- a/src/modules/coreFeatures/ModuleConflictResolver.ts +++ b/src/modules/coreFeatures/ModuleConflictResolver.ts @@ -211,10 +211,30 @@ export class ModuleConflictResolver extends AbstractModule { } return true; } + private async _resolveAllConflictedFilesByNewerOnes() { + this._log(`Resolving conflicts by newer ones`, LOG_LEVEL_NOTICE); + + const files = this.core.storageAccess.getFileNames(); + + let i = 0; + for (const file of files) { + if (i++ % 10) + this._log( + `Check and Processing ${i} / ${files.length}`, + LOG_LEVEL_NOTICE, + "resolveAllConflictedFilesByNewerOnes" + ); + await this.services.conflict.resolveByNewest(file); + } + this._log(`Done!`, LOG_LEVEL_NOTICE, "resolveAllConflictedFilesByNewerOnes"); + } onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { services.conflict.resolveByDeletingRevision.setHandler(this._resolveConflictByDeletingRev.bind(this)); services.conflict.resolve.setHandler(this._resolveConflict.bind(this)); services.conflict.resolveByNewest.setHandler(this._anyResolveConflictByNewest.bind(this)); + services.conflict.resolveAllConflictedFilesByNewerOnes.setHandler( + this._resolveAllConflictedFilesByNewerOnes.bind(this) + ); } } diff --git a/src/modules/coreObsidian/storageLib/SerializedFileAccess.ts b/src/modules/coreObsidian/storageLib/SerializedFileAccess.ts index 4c448f6..ac4b2d4 100644 --- a/src/modules/coreObsidian/storageLib/SerializedFileAccess.ts +++ b/src/modules/coreObsidian/storageLib/SerializedFileAccess.ts @@ -5,7 +5,7 @@ import type { FilePath, UXFileInfoStub } from "../../../lib/src/common/types.ts" import { createBinaryBlob, isDocContentSame } from "../../../lib/src/common/utils.ts"; import type { InternalFileInfo } from "../../../common/types.ts"; import { markChangesAreSame } from "../../../common/utils.ts"; -import type { StorageAccess } from "../../interfaces/StorageAccess.ts"; +import type { IStorageAccessManager } from "@lib/interfaces/StorageAccess.ts"; import type { LiveSyncCore } from "@/main.ts"; function toArrayBuffer(arr: Uint8Array | ArrayBuffer | DataView): ArrayBuffer { if (arr instanceof Uint8Array) { @@ -16,44 +16,64 @@ function toArrayBuffer(arr: Uint8Array | ArrayBuffer | DataView { + storageAccessManager: IStorageAccessManager; + constructor(storageAccessManager: IStorageAccessManager) { + this.storageAccessManager = storageAccessManager; + } + abstract getPath(file: TNativeFile | string): FilePath; +} -export class SerializedFileAccess { +export class ObsidianFileAccess extends FileAccessBase { app: App; plugin: LiveSyncCore; - storageAccess: StorageAccess; - constructor(app: App, plugin: LiveSyncCore, storageAccess: StorageAccess) { + + getPath(file: string | TFile): FilePath { + return (typeof file === "string" ? file : file.path) as FilePath; + } + + constructor(app: App, plugin: LiveSyncCore, storageAccessManager: IStorageAccessManager) { + super(storageAccessManager); this.app = app; this.plugin = plugin; - this.storageAccess = storageAccess; } async tryAdapterStat(file: TFile | string) { const path = file instanceof TFile ? file.path : file; - return await this.storageAccess.processReadFile(path as FilePath, async () => { + return await this.storageAccessManager.processReadFile(path as FilePath, async () => { if (!(await this.app.vault.adapter.exists(path))) return null; return this.app.vault.adapter.stat(path); }); } async adapterStat(file: TFile | string) { const path = file instanceof TFile ? file.path : file; - return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.adapter.stat(path)); + return await this.storageAccessManager.processReadFile(path as FilePath, () => + this.app.vault.adapter.stat(path) + ); } async adapterExists(file: TFile | string) { const path = file instanceof TFile ? file.path : file; - return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.adapter.exists(path)); + return await this.storageAccessManager.processReadFile(path as FilePath, () => + this.app.vault.adapter.exists(path) + ); } async adapterRemove(file: TFile | string) { const path = file instanceof TFile ? file.path : file; - return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.adapter.remove(path)); + return await this.storageAccessManager.processWriteFile(path as FilePath, () => + this.app.vault.adapter.remove(path) + ); } async adapterRead(file: TFile | string) { const path = file instanceof TFile ? file.path : file; - return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.adapter.read(path)); + return await this.storageAccessManager.processReadFile(path as FilePath, () => + this.app.vault.adapter.read(path) + ); } async adapterReadBinary(file: TFile | string) { const path = file instanceof TFile ? file.path : file; - return await this.storageAccess.processReadFile(path as FilePath, () => + return await this.storageAccessManager.processReadFile(path as FilePath, () => this.app.vault.adapter.readBinary(path) ); } @@ -61,9 +81,11 @@ export class SerializedFileAccess { async adapterReadAuto(file: TFile | string) { const path = file instanceof TFile ? file.path : file; if (isPlainText(path)) { - return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.adapter.read(path)); + return await this.storageAccessManager.processReadFile(path as FilePath, () => + this.app.vault.adapter.read(path) + ); } - return await this.storageAccess.processReadFile(path as FilePath, () => + return await this.storageAccessManager.processReadFile(path as FilePath, () => this.app.vault.adapter.readBinary(path) ); } @@ -75,39 +97,47 @@ export class SerializedFileAccess { ) { const path = file instanceof TFile ? file.path : file; if (typeof data === "string") { - return await this.storageAccess.processWriteFile(path as FilePath, () => + return await this.storageAccessManager.processWriteFile(path as FilePath, () => this.app.vault.adapter.write(path, data, options) ); } else { - return await this.storageAccess.processWriteFile(path as FilePath, () => + return await this.storageAccessManager.processWriteFile(path as FilePath, () => this.app.vault.adapter.writeBinary(path, toArrayBuffer(data), options) ); } } + adapterList(basePath: string): Promise<{ files: string[]; folders: string[] }> { + return Promise.resolve(this.app.vault.adapter.list(basePath)); + } + async vaultCacheRead(file: TFile) { - return await this.storageAccess.processReadFile(file.path as FilePath, () => this.app.vault.cachedRead(file)); + return await this.storageAccessManager.processReadFile(file.path as FilePath, () => + this.app.vault.cachedRead(file) + ); } async vaultRead(file: TFile) { - return await this.storageAccess.processReadFile(file.path as FilePath, () => this.app.vault.read(file)); + return await this.storageAccessManager.processReadFile(file.path as FilePath, () => this.app.vault.read(file)); } async vaultReadBinary(file: TFile) { - return await this.storageAccess.processReadFile(file.path as FilePath, () => this.app.vault.readBinary(file)); + return await this.storageAccessManager.processReadFile(file.path as FilePath, () => + this.app.vault.readBinary(file) + ); } async vaultReadAuto(file: TFile) { const path = file.path; if (isPlainText(path)) { - return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.read(file)); + return await this.storageAccessManager.processReadFile(path as FilePath, () => this.app.vault.read(file)); } - return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.readBinary(file)); + return await this.storageAccessManager.processReadFile(path as FilePath, () => this.app.vault.readBinary(file)); } async vaultModify(file: TFile, data: string | ArrayBuffer | Uint8Array, options?: DataWriteOptions) { if (typeof data === "string") { - return await this.storageAccess.processWriteFile(file.path as FilePath, async () => { + return await this.storageAccessManager.processWriteFile(file.path as FilePath, async () => { const oldData = await this.app.vault.read(file); if (data === oldData) { if (options && options.mtime) markChangesAreSame(file.path, file.stat.mtime, options.mtime); @@ -117,7 +147,7 @@ export class SerializedFileAccess { return true; }); } else { - return await this.storageAccess.processWriteFile(file.path as FilePath, async () => { + return await this.storageAccessManager.processWriteFile(file.path as FilePath, async () => { const oldData = await this.app.vault.readBinary(file); if (await isDocContentSame(createBinaryBlob(oldData), createBinaryBlob(data))) { if (options && options.mtime) markChangesAreSame(file.path, file.stat.mtime, options.mtime); @@ -134,11 +164,11 @@ export class SerializedFileAccess { options?: DataWriteOptions ): Promise { if (typeof data === "string") { - return await this.storageAccess.processWriteFile(path as FilePath, () => + return await this.storageAccessManager.processWriteFile(path as FilePath, () => this.app.vault.create(path, data, options) ); } else { - return await this.storageAccess.processWriteFile(path as FilePath, () => + return await this.storageAccessManager.processWriteFile(path as FilePath, () => this.app.vault.createBinary(path, toArrayBuffer(data), options) ); } @@ -147,18 +177,21 @@ export class SerializedFileAccess { trigger(name: string, ...data: any[]) { return this.app.vault.trigger(name, ...data); } + async reconcileInternalFile(path: string) { + await (this.app.vault.adapter as any)?.reconcileInternalFile(path); + } async adapterAppend(normalizedPath: string, data: string, options?: DataWriteOptions) { return await this.app.vault.adapter.append(normalizedPath, data, options); } async delete(file: TFile | TFolder, force = false) { - return await this.storageAccess.processWriteFile(file.path as FilePath, () => + return await this.storageAccessManager.processWriteFile(file.path as FilePath, () => this.app.vault.delete(file, force) ); } async trash(file: TFile | TFolder, force = false) { - return await this.storageAccess.processWriteFile(file.path as FilePath, () => + return await this.storageAccessManager.processWriteFile(file.path as FilePath, () => this.app.vault.trash(file, force) ); } diff --git a/src/modules/coreObsidian/storageLib/StorageEventManager.ts b/src/modules/coreObsidian/storageLib/StorageEventManager.ts index 04fcefb..90b1dc6 100644 --- a/src/modules/coreObsidian/storageLib/StorageEventManager.ts +++ b/src/modules/coreObsidian/storageLib/StorageEventManager.ts @@ -10,7 +10,6 @@ import { type FileEventType, type FilePath, type UXFileInfoStub, - type UXInternalFileInfoStub, } from "../../../lib/src/common/types.ts"; import { delay, fireAndForget, throttle } from "../../../lib/src/common/utils.ts"; import { type FileEventItem } from "../../../common/types.ts"; @@ -20,19 +19,11 @@ import { Semaphore } from "octagonal-wheels/concurrency/semaphore"; import type { LiveSyncCore } from "../../../main.ts"; import { InternalFileToUXFileInfoStub, TFileToUXFileInfoStub } from "./utilObsidian.ts"; import ObsidianLiveSyncPlugin from "../../../main.ts"; -import type { StorageAccess } from "../../interfaces/StorageAccess.ts"; +import type { IStorageAccessManager } from "@lib/interfaces/StorageAccess.ts"; import { HiddenFileSync } from "../../../features/HiddenFileSync/CmdHiddenFileSync.ts"; import { promiseWithResolvers, type PromiseWithResolvers } from "octagonal-wheels/promises"; -// import { InternalFileToUXFileInfo } from "../platforms/obsidian.ts"; +import { StorageEventManager, type FileEvent } from "@lib/interfaces/StorageEventManager.ts"; -export type FileEvent = { - type: FileEventType; - file: UXFileInfoStub | UXInternalFileInfoStub; - oldPath?: string; - cachedData?: string; - skipBatchWait?: boolean; - cancelled?: boolean; -}; type WaitInfo = { since: number; type: FileEventType; @@ -46,20 +37,10 @@ type FileEventItemSentinelFlush = { }; type FileEventItemSentinel = FileEventItemSentinelFlush; -export abstract class StorageEventManager { - abstract beginWatch(): Promise; - - abstract appendQueue(items: FileEvent[], ctx?: any): Promise; - - abstract isWaiting(filename: FilePath): boolean; - abstract waitForIdle(): Promise; - abstract restoreState(): Promise; -} - export class StorageEventManagerObsidian extends StorageEventManager { plugin: ObsidianLiveSyncPlugin; core: LiveSyncCore; - storageAccess: StorageAccess; + storageAccess: IStorageAccessManager; get services() { return this.core.services; } @@ -83,9 +64,9 @@ export class StorageEventManagerObsidian extends StorageEventManager { */ snapShotRestored: Promise | null = null; - constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore, storageAccess: StorageAccess) { + constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore, storageAccessManager: IStorageAccessManager) { super(); - this.storageAccess = storageAccess; + this.storageAccess = storageAccessManager; this.plugin = plugin; this.core = core; this.cmdHiddenFileSync = this.plugin.getAddOn(HiddenFileSync.name) as HiddenFileSync; diff --git a/src/modules/coreObsidian/storageLib/utilObsidian.ts b/src/modules/coreObsidian/storageLib/utilObsidian.ts index 65719ae..0158072 100644 --- a/src/modules/coreObsidian/storageLib/utilObsidian.ts +++ b/src/modules/coreObsidian/storageLib/utilObsidian.ts @@ -2,7 +2,7 @@ import { TFile, type TAbstractFile, type TFolder } from "../../../deps.ts"; import { ICHeader } from "../../../common/types.ts"; -import type { SerializedFileAccess } from "./SerializedFileAccess.ts"; +import type { ObsidianFileAccess } from "./SerializedFileAccess.ts"; import { addPrefix, isPlainText } from "../../../lib/src/string_and_binary/path.ts"; import { LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger"; import { createBlob } from "../../../lib/src/common/utils.ts"; @@ -51,7 +51,7 @@ export async function TFileToUXFileInfo( export async function InternalFileToUXFileInfo( fullPath: string, - vaultAccess: SerializedFileAccess, + vaultAccess: ObsidianFileAccess, prefix: string = ICHeader ): Promise { const name = fullPath.split("/").pop() as string; diff --git a/src/modules/extras/ModuleReplicateTest.ts b/src/modules/extras/ModuleReplicateTest.ts index 2911217..4de3492 100644 --- a/src/modules/extras/ModuleReplicateTest.ts +++ b/src/modules/extras/ModuleReplicateTest.ts @@ -511,10 +511,11 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`; return this.__assertStorageContent((this.testRootPath + "task.md") as FilePath, mergedDoc, false, true); } + // No longer tested async checkConflictResolution() { this._log("Before testing conflicted files, resolve all once", LOG_LEVEL_NOTICE); - await this.core.rebuilder.resolveAllConflictedFilesByNewerOnes(); - await this.core.rebuilder.resolveAllConflictedFilesByNewerOnes(); + await this.services.conflict.resolveAllConflictedFilesByNewerOnes(); + await this.services.conflict.resolveAllConflictedFilesByNewerOnes(); await this.services.replication.replicate(); await delay(1000); if (!(await this.testConflictAutomatic())) { diff --git a/src/modules/features/ModuleObsidianSetting.ts b/src/modules/features/ModuleObsidianSetting.ts index c190242..ee27279 100644 --- a/src/modules/features/ModuleObsidianSetting.ts +++ b/src/modules/features/ModuleObsidianSetting.ts @@ -320,6 +320,25 @@ export class ModuleObsidianSettings extends AbstractModule { private _currentSettings(): ObsidianLiveSyncSettings { return this.settings; } + private _updateSettings(updateFn: (settings: ObsidianLiveSyncSettings) => ObsidianLiveSyncSettings): Promise { + try { + const updated = updateFn(this.settings); + this.settings = updated; + } catch (ex) { + this._log("Error in update function: " + ex, LOG_LEVEL_URGENT); + return Promise.reject(ex); + } + return Promise.resolve(); + } + private _applyPartial(partial: Partial): Promise { + try { + this.settings = { ...this.settings, ...partial }; + } catch (ex) { + this._log("Error in applying partial settings: " + ex, LOG_LEVEL_URGENT); + return Promise.reject(ex); + } + return Promise.resolve(); + } onBindFunction(core: LiveSyncCore, services: typeof core.services): void { super.onBindFunction(core, services); @@ -329,6 +348,8 @@ export class ModuleObsidianSettings extends AbstractModule { services.setting.adjustSettings.setHandler(this._adjustSettings.bind(this)); services.setting.loadSettings.setHandler(this._loadSettings.bind(this)); services.setting.currentSettings.setHandler(this._currentSettings.bind(this)); + services.setting.updateSettings.setHandler(this._updateSettings.bind(this)); + services.setting.applyPartial.setHandler(this._applyPartial.bind(this)); services.setting.saveDeviceAndVaultName.setHandler(this._saveDeviceAndVaultName.bind(this)); services.setting.saveSettingData.setHandler(this._saveSettingData.bind(this)); } diff --git a/src/modules/features/SettingDialogue/PaneHatch.ts b/src/modules/features/SettingDialogue/PaneHatch.ts index 3796de1..2898afa 100644 --- a/src/modules/features/SettingDialogue/PaneHatch.ts +++ b/src/modules/features/SettingDialogue/PaneHatch.ts @@ -361,7 +361,7 @@ ${stringifyYaml({ .setButtonText("Resolve All") .setCta() .onClick(async () => { - await this.plugin.rebuilder.resolveAllConflictedFilesByNewerOnes(); + await this.services.conflict.resolveAllConflictedFilesByNewerOnes(); }) ); diff --git a/src/modules/interfaces/DatabaseFileAccess.ts b/src/modules/interfaces/DatabaseFileAccess.ts deleted file mode 100644 index 7f151ac..0000000 --- a/src/modules/interfaces/DatabaseFileAccess.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { - FilePathWithPrefix, - LoadedEntry, - MetaEntry, - UXFileInfo, - UXFileInfoStub, -} from "../../lib/src/common/types"; - -export interface DatabaseFileAccess { - delete: (file: UXFileInfoStub | FilePathWithPrefix, rev?: string) => Promise; - store: (file: UXFileInfo, force?: boolean, skipCheck?: boolean) => Promise; - storeContent(path: FilePathWithPrefix, content: string): Promise; - createChunks: (file: UXFileInfo, force?: boolean, skipCheck?: boolean) => Promise; - fetch: ( - file: UXFileInfoStub | FilePathWithPrefix, - rev?: string, - waitForReady?: boolean, - skipCheck?: boolean - ) => Promise; - fetchEntryFromMeta: (meta: MetaEntry, waitForReady?: boolean, skipCheck?: boolean) => Promise; - fetchEntryMeta: ( - file: UXFileInfoStub | FilePathWithPrefix, - rev?: string, - skipCheck?: boolean - ) => Promise; - fetchEntry: ( - file: UXFileInfoStub | FilePathWithPrefix, - rev?: string, - waitForReady?: boolean, - skipCheck?: boolean - ) => Promise; - getConflictedRevs: (file: UXFileInfoStub | FilePathWithPrefix) => Promise; - // storeFromStorage: (file: UXFileInfoStub | FilePathWithPrefix, force?: boolean) => Promise; -} diff --git a/src/modules/interfaces/DatabaseRebuilder.ts b/src/modules/interfaces/DatabaseRebuilder.ts deleted file mode 100644 index 4dbf477..0000000 --- a/src/modules/interfaces/DatabaseRebuilder.ts +++ /dev/null @@ -1,12 +0,0 @@ -export interface Rebuilder { - $performRebuildDB( - method: "localOnly" | "remoteOnly" | "rebuildBothByThisDevice" | "localOnlyWithChunks" - ): Promise; - $rebuildRemote(): Promise; - $rebuildEverything(): Promise; - $fetchLocal(makeLocalChunkBeforeSync?: boolean, preventMakeLocalFilesBeforeSync?: boolean): Promise; - - scheduleRebuild(): Promise; - scheduleFetch(): Promise; - resolveAllConflictedFilesByNewerOnes(): Promise; -} diff --git a/src/modules/interfaces/StorageAccess.ts b/src/modules/interfaces/StorageAccess.ts deleted file mode 100644 index fbc121b..0000000 --- a/src/modules/interfaces/StorageAccess.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { - FilePath, - FilePathWithPrefix, - UXDataWriteOptions, - UXFileInfo, - UXFileInfoStub, - UXFolderInfo, - UXStat, -} from "../../lib/src/common/types"; -import type { CustomRegExp } from "../../lib/src/common/utils"; - -export interface StorageAccess { - restoreState(): Promise; - processWriteFile(file: UXFileInfoStub | FilePathWithPrefix, proc: () => Promise): Promise; - processReadFile(file: UXFileInfoStub | FilePathWithPrefix, proc: () => Promise): Promise; - isFileProcessing(file: UXFileInfoStub | FilePathWithPrefix): boolean; - - deleteVaultItem(file: FilePathWithPrefix | UXFileInfoStub | UXFolderInfo): Promise; - - writeFileAuto(path: string, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise; - - readFileAuto(path: string): Promise; - readFileText(path: string): Promise; - isExists(path: string): Promise; - writeHiddenFileAuto(path: string, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise; - appendHiddenFile(path: string, data: string, opt?: UXDataWriteOptions): Promise; - - stat(path: string): Promise; - statHidden(path: string): Promise; - removeHidden(path: string): Promise; - readHiddenFileAuto(path: string): Promise; - readHiddenFileBinary(path: string): Promise; - readHiddenFileText(path: string): Promise; - isExistsIncludeHidden(path: string): Promise; - // This could be work also for the hidden files. - ensureDir(path: string): Promise; - triggerFileEvent(event: string, path: string): void; - triggerHiddenFile(path: string): Promise; - - getFileStub(path: string): UXFileInfoStub | null; - readStubContent(stub: UXFileInfoStub): Promise; - getStub(path: string): UXFileInfoStub | UXFolderInfo | null; - - getFiles(): UXFileInfoStub[]; - getFileNames(): FilePathWithPrefix[]; - - touched(file: UXFileInfoStub | FilePathWithPrefix): Promise; - recentlyTouched(file: UXFileInfoStub | FilePathWithPrefix): boolean; - clearTouched(): void; - - // -- Low-Level - delete(file: FilePathWithPrefix | UXFileInfoStub | string, force: boolean): Promise; - trash(file: FilePathWithPrefix | UXFileInfoStub | string, system: boolean): Promise; - - getFilesIncludeHidden( - basePath: string, - includeFilter?: CustomRegExp[], - excludeFilter?: CustomRegExp[], - skipFolder?: string[] - ): Promise; -} diff --git a/src/modules/services/ObsidianServiceHub.ts b/src/modules/services/ObsidianServiceHub.ts index 5870f6c..8e8969a 100644 --- a/src/modules/services/ObsidianServiceHub.ts +++ b/src/modules/services/ObsidianServiceHub.ts @@ -55,7 +55,11 @@ export class ObsidianServiceHub extends InjectableServiceHub = new Set(); - processWriteFile(file: UXFileInfoStub | FilePathWithPrefix, proc: () => Promise): Promise { - const path = typeof file === "string" ? file : file.path; - return serialized(`${fileLockPrefix}${path}`, async () => { - try { - this.processingFiles.add(path); - return await proc(); - } finally { - this.processingFiles.delete(path); - } - }); +export interface StorageAccessObsidianDependencies { + API: APIService; + appLifecycle: AppLifecycleService; + fileProcessing: FileProcessingService; + vault: VaultService; + setting: SettingService; + storageEventManager: StorageEventManager; + storageAccessManager: IStorageAccessManager; + vaultAccess: ObsidianFileAccess; +} + +export class ServiceFileAccessObsidian + extends ServiceModuleBase + implements StorageAccess +{ + private vaultAccess: ObsidianFileAccess; + private vaultManager: StorageEventManager; + private vault: VaultService; + private setting: SettingService; + + constructor(services: StorageAccessObsidianDependencies) { + super(services); + // this.appLifecycle = services.appLifecycle; + this.vault = services.vault; + this.setting = services.setting; + this.vaultManager = services.storageEventManager; + this.vaultAccess = services.vaultAccess; + services.appLifecycle.onFirstInitialise.addHandler(this._everyOnFirstInitialize.bind(this)); + services.fileProcessing.commitPendingFileEvents.addHandler(this._everyCommitPendingFileEvent.bind(this)); } - processReadFile(file: UXFileInfoStub | FilePathWithPrefix, proc: () => Promise): Promise { - const path = typeof file === "string" ? file : file.path; - return serialized(`${fileLockPrefix}${path}`, async () => { - try { - this.processingFiles.add(path); - return await proc(); - } finally { - this.processingFiles.delete(path); - } - }); - } - isFileProcessing(file: UXFileInfoStub | FilePathWithPrefix): boolean { - const path = typeof file === "string" ? file : file.path; - return this.processingFiles.has(path); - } - vaultAccess!: SerializedFileAccess; - vaultManager: StorageEventManager = new StorageEventManagerObsidian(this.plugin, this.core, this); restoreState() { return this.vaultManager.restoreState(); @@ -61,21 +61,11 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements return Promise.resolve(true); } - // $$flushFileEventQueue(): void { - // this.vaultManager.flushQueue(); - // } - async _everyCommitPendingFileEvent(): Promise { await this.vaultManager.waitForIdle(); return Promise.resolve(true); } - _everyOnloadStart(): Promise { - this.vaultAccess = new SerializedFileAccess(this.app, this.plugin, this); - this.core.storageAccess = this; - return Promise.resolve(true); - } - async writeFileAuto(path: string, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise { const file = this.vaultAccess.getAbstractFileByPath(path); if (file instanceof TFile) { @@ -200,8 +190,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements this.vaultAccess.trigger(event, file); } async triggerHiddenFile(path: string): Promise { - //@ts-ignore internal function - await this.app.vault.adapter.reconcileInternalFile(path); + await this.vaultAccess.reconcileInternalFile(path); } // getFileStub(file: TFile): UXFileInfoStub { // return TFileToUXFileInfoStub(file); @@ -252,7 +241,8 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements ): Promise { let w: ListedFiles; try { - w = await this.app.vault.adapter.list(basePath); + w = await this.vaultAccess.adapterList(basePath); + // w = await this.plugin.app.vault.adapter.list(basePath); } catch (ex) { this._log(`Could not traverse(getFilesIncludeHidden):${basePath}`, LOG_LEVEL_INFO); this._log(ex, LOG_LEVEL_VERBOSE); @@ -268,7 +258,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements if (excludeFilter && excludeFilter.some((ee) => ee.test(file))) { continue; } - if (await this.services.vault.isIgnoredByIgnoreFile(file)) continue; + if (await this.vault.isIgnoredByIgnoreFile(file)) continue; files.push(file); } @@ -281,7 +271,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements if (excludeFilter && excludeFilter.some((e) => e.test(v))) { continue; } - if (await this.services.vault.isIgnoredByIgnoreFile(v)) { + if (await this.vault.isIgnoredByIgnoreFile(v)) { continue; } // OK, deep dive! @@ -339,10 +329,11 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements async __deleteVaultItem(file: TFile | TFolder) { if (file instanceof TFile) { - if (!(await this.services.vault.isTargetFile(file.path))) return; + if (!(await this.vault.isTargetFile(file.path))) return; } const dir = file.parent; - if (this.settings.trashInsteadDelete) { + const settings = this.setting.currentSettings(); + if (settings.trashInsteadDelete) { await this.vaultAccess.trash(file, false); } else { await this.vaultAccess.delete(file, true); @@ -351,7 +342,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements if (dir) { this._log(`files: ${dir.children.length}`); if (dir.children.length == 0) { - if (!this.settings.doNotDeleteFolder) { + if (!settings.doNotDeleteFolder) { this._log( `All files under the parent directory (${dir.path}) have been deleted, so delete this one.` ); @@ -369,13 +360,4 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements return await this.__deleteVaultItem(file); } } - - constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) { - super(plugin, core); - } - onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { - services.appLifecycle.onFirstInitialise.addHandler(this._everyOnFirstInitialize.bind(this)); - services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); - services.fileProcessing.commitPendingFileEvents.addHandler(this._everyCommitPendingFileEvent.bind(this)); - } } diff --git a/updates.md b/updates.md index eee07ac..ac65b30 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,38 @@ 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.43-patched-5 + +17th February, 2026 + +Yes, we mostly have got refactored! + +### Refactored + +- Following properties of `ObsidianLiveSyncPlugin` are now initialised more explicitly: + + - property : what is responsible + - `storageAccess` : `ServiceFileAccessObsidian` + - `databaseFileAccess` : `ServiceDatabaseFileAccess` + - `fileHandler` : `ServiceFileHandler` + - `rebuilder` : `ServiceRebuilder` + - Not so long from now, ServiceFileAccessObsidian might be abstracted to a more general FileAccessService, and make more testable and maintainable. + - These properties are initialised in `initialiseServiceModules` on `ObsidianLiveSyncPlugin`. + - They are `ServiceModule`s. + - Which means they do not use dynamic binding themselves, but they use bound services. + - ServiceModules are in src/lib/src/serviceModules for common implementations, and src/serviceModules for Obsidian-specific implementations. + - Hence, now all ambiguous properties of `ObsidianLiveSyncPlugin` are initialised explicitly. We can proceed to testing. + - Well, I will release v0.25.44 after testing this. + +- Conflict service is now responsible for `resolveAllConflictedFilesByNewerOnes` function, which has been in the rebuilder. +- New functions `updateSettings`, and `applyPartial` have been added to the setting service. We should use these functions instead of directly writing the settings on `ObsidianLiveSyncPlugin.setting`. +- Some interfaces for services have been moved to src/lib/src/interfaces. +- `RemoteService.tryResetDatabase` and `tryCreateDatabase` are now moved to the replicator service. + - You know that these functions are surely performed by the replicator. + - Probably, most of the functions in `RemoteService` should be moved to the replicator service, but for now, these two functions are moved as they are the most related ones, to rewrite the rebuilder service. +- Common functions are gradually moved to the common library. +- Now, binding functions on modules have been delayed until the services and service modules are initialised, to avoid fragile behaviour. + ## 0.25.43-patched-4 16th February, 2026 From 627edc96bfddd946bff17b15ca7c07784978a145 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Tue, 17 Feb 2026 10:14:13 +0000 Subject: [PATCH 011/339] bump for beta --- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/manifest.json b/manifest.json index 4956034..9a15043 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.43-patched-4", + "version": "0.25.43-patched-5", "minAppVersion": "0.9.12", "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 0b152bb..ae871f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-4", + "version": "0.25.43-patched-5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.43-patched-4", + "version": "0.25.43-patched-5", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 5037b29..f2b6e94 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-4", + "version": "0.25.43-patched-5", "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", From 4658e3735dafdc13295316c07681777574b1c3cb Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Tue, 17 Feb 2026 10:56:05 +0000 Subject: [PATCH 012/339] Fix Shim --- src/apps/webpeer/src/P2PReplicatorShim.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/webpeer/src/P2PReplicatorShim.ts b/src/apps/webpeer/src/P2PReplicatorShim.ts index 65b8759..004ef5e 100644 --- a/src/apps/webpeer/src/P2PReplicatorShim.ts +++ b/src/apps/webpeer/src/P2PReplicatorShim.ts @@ -36,6 +36,7 @@ import { ServiceContext } from "@lib/services/base/ServiceBase"; import type { InjectableServiceHub } from "@lib/services/InjectableServices"; import { Menu } from "@/lib/src/services/implements/browser/Menu"; import type { InjectableVaultServiceCompat } from "@/lib/src/services/implements/injectable/InjectableVaultService"; +import { SimpleStoreIDBv2 } from "octagonal-wheels/databases/SimpleStoreIDBv2"; function addToList(item: string, list: string) { return unique( @@ -102,8 +103,7 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { Logger(ex, LOG_LEVEL_VERBOSE); } } - - const repStore = this.services.database.openSimpleStore("p2p-livesync-web-peer"); + const repStore = SimpleStoreIDBv2.open("p2p-livesync-web-peer"); this._simpleStore = repStore; let _settings = (await repStore.get("settings")) || ({ ...P2P_DEFAULT_SETTINGS } as P2PSyncSetting); From 2bf1c775ee17361683f5c95afbcf8deff2a145f6 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 18 Feb 2026 12:13:05 +0000 Subject: [PATCH 013/339] ## 0.25.43-patched-6 ### Fixed - Unlocking the remote database after rebuilding has been fixed. ### Refactored - Now `StorageEventManagerBase` is separated from `StorageEventManagerObsidian` following their concerns. - Now `FileAccessBase` is separated from `FileAccessObsidian` following their concerns. --- manifest.json | 2 +- package-lock.json | 4 +- package.json | 2 +- src/deps.ts | 1 + src/lib | 2 +- src/main.ts | 21 +- src/managers/StorageEventManagerObsidian.ts | 210 ++++++ src/modules/core/ModuleTargetFilter.ts | 8 +- .../storageLib/SerializedFileAccess.ts | 264 -------- .../storageLib/StorageEventManager.ts | 631 ------------------ .../coreObsidian/storageLib/utilObsidian.ts | 4 +- src/serviceModules/FileAccessObsidian.ts | 156 +++++ src/serviceModules/ServiceFileAccessImpl.ts | 6 + .../ServiceFileAccessObsidian.ts | 363 ---------- updates.md | 21 + 15 files changed, 421 insertions(+), 1274 deletions(-) create mode 100644 src/managers/StorageEventManagerObsidian.ts delete mode 100644 src/modules/coreObsidian/storageLib/SerializedFileAccess.ts delete mode 100644 src/modules/coreObsidian/storageLib/StorageEventManager.ts create mode 100644 src/serviceModules/FileAccessObsidian.ts create mode 100644 src/serviceModules/ServiceFileAccessImpl.ts delete mode 100644 src/serviceModules/ServiceFileAccessObsidian.ts diff --git a/manifest.json b/manifest.json index 9a15043..c803f98 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.43-patched-5", + "version": "0.25.43-patched-6", "minAppVersion": "0.9.12", "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 ae871f3..8274cf0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-5", + "version": "0.25.43-patched-6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.43-patched-5", + "version": "0.25.43-patched-6", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index f2b6e94..28fdbea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-5", + "version": "0.25.43-patched-6", "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/deps.ts b/src/deps.ts index 08a78f7..cf8ed5b 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -40,6 +40,7 @@ export type { MarkdownFileInfo, ListedFiles, ValueComponent, + Stat, } from "obsidian"; import { normalizePath as normalizePath_ } from "obsidian"; const normalizePath = normalizePath_ as (from: T) => T; diff --git a/src/lib b/src/lib index 56fc24e..744a1b0 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 56fc24e001347948bafbc85c342f94fd0ee0a0b5 +Subproject commit 744a1b01ebb30acebd3d55a8a99ac3b61d228828 diff --git a/src/main.ts b/src/main.ts index e623df0..2769f8a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -60,12 +60,12 @@ import type { ServiceContext } from "./lib/src/services/base/ServiceBase.ts"; import { ServiceRebuilder } from "@lib/serviceModules/Rebuilder.ts"; import type { IFileHandler } from "@lib/interfaces/FileHandler.ts"; import { ServiceDatabaseFileAccess } from "@/serviceModules/DatabaseFileAccess.ts"; -import { ServiceFileAccessObsidian } from "@/serviceModules/ServiceFileAccessObsidian.ts"; -import { StorageEventManagerObsidian } from "@/modules/coreObsidian/storageLib/StorageEventManager.ts"; -import { ObsidianFileAccess } from "@/modules/coreObsidian/storageLib/SerializedFileAccess.ts"; +import { ServiceFileAccessObsidian } from "@/serviceModules/ServiceFileAccessImpl.ts"; import { StorageAccessManager } from "@lib/managers/StorageProcessingManager.ts"; import { __$checkInstanceBinding } from "./lib/src/dev/checks.ts"; import { ServiceFileHandler } from "./serviceModules/FileHandler.ts"; +import { FileAccessObsidian } from "./serviceModules/FileAccessObsidian.ts"; +import { StorageEventManagerObsidian } from "./managers/StorageEventManagerObsidian.ts"; export default class ObsidianLiveSyncPlugin extends Plugin @@ -302,8 +302,19 @@ export default class ObsidianLiveSyncPlugin private initialiseServiceModules() { const storageAccessManager = new StorageAccessManager(); // If we want to implement to the other platform, implement ObsidianXXXXXService. - const vaultAccess = new ObsidianFileAccess(this.app, this, storageAccessManager); - const storageEventManager = new StorageEventManagerObsidian(this, this, storageAccessManager); + const vaultAccess = new FileAccessObsidian(this.app, { + storageAccessManager: storageAccessManager, + vaultService: this.services.vault, + settingService: this.services.setting, + APIService: this.services.API, + }); + const storageEventManager = new StorageEventManagerObsidian(this, this, { + fileProcessing: this.services.fileProcessing, + setting: this.services.setting, + vaultService: this.services.vault, + storageAccessManager: storageAccessManager, + APIService: this.services.API, + }); const storageAccess = new ServiceFileAccessObsidian({ API: this.services.API, setting: this.services.setting, diff --git a/src/managers/StorageEventManagerObsidian.ts b/src/managers/StorageEventManagerObsidian.ts new file mode 100644 index 0000000..aaedacf --- /dev/null +++ b/src/managers/StorageEventManagerObsidian.ts @@ -0,0 +1,210 @@ +import type { FileEventItem } from "@/common/types"; +import { HiddenFileSync } from "@/features/HiddenFileSync/CmdHiddenFileSync"; +import type { FilePath, UXFileInfoStub, UXFolderInfo, UXInternalFileInfoStub } from "@lib/common/types"; +import type { FileEvent } from "@lib/interfaces/StorageEventManager"; +import { TFile, type TAbstractFile, TFolder } from "@/deps"; +import { LOG_LEVEL_DEBUG } from "octagonal-wheels/common/logger"; +import type ObsidianLiveSyncPlugin from "@/main"; +import type { LiveSyncCore } from "@/main"; +import { + StorageEventManagerBase, + type FileEventItemSentinel, + type StorageEventManagerBaseDependencies, +} from "@lib/managers/StorageEventManager"; +import { InternalFileToUXFileInfoStub, TFileToUXFileInfoStub } from "@/modules/coreObsidian/storageLib/utilObsidian"; + +export class StorageEventManagerObsidian extends StorageEventManagerBase { + plugin: ObsidianLiveSyncPlugin; + core: LiveSyncCore; + + // Necessary evil. + cmdHiddenFileSync: HiddenFileSync; + + override isFile(file: UXFileInfoStub | UXInternalFileInfoStub | UXFolderInfo | TFile): boolean { + if (file instanceof TFile) { + return true; + } + if (super.isFile(file)) { + return true; + } + return !file.isFolder; + } + override isFolder(file: UXFileInfoStub | UXInternalFileInfoStub | UXFolderInfo | TFolder): boolean { + if (file instanceof TFolder) { + return true; + } + if (super.isFolder(file)) { + return true; + } + return !!file.isFolder; + } + + constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore, dependencies: StorageEventManagerBaseDependencies) { + super(dependencies); + this.plugin = plugin; + this.core = core; + this.cmdHiddenFileSync = this.plugin.getAddOn(HiddenFileSync.name) as HiddenFileSync; + } + + async beginWatch() { + await this.snapShotRestored; + const plugin = this.plugin; + this.watchVaultChange = this.watchVaultChange.bind(this); + this.watchVaultCreate = this.watchVaultCreate.bind(this); + this.watchVaultDelete = this.watchVaultDelete.bind(this); + this.watchVaultRename = this.watchVaultRename.bind(this); + this.watchVaultRawEvents = this.watchVaultRawEvents.bind(this); + this.watchEditorChange = this.watchEditorChange.bind(this); + plugin.registerEvent(plugin.app.vault.on("modify", this.watchVaultChange)); + plugin.registerEvent(plugin.app.vault.on("delete", this.watchVaultDelete)); + plugin.registerEvent(plugin.app.vault.on("rename", this.watchVaultRename)); + plugin.registerEvent(plugin.app.vault.on("create", this.watchVaultCreate)); + //@ts-ignore : Internal API + plugin.registerEvent(plugin.app.vault.on("raw", this.watchVaultRawEvents)); + plugin.registerEvent(plugin.app.workspace.on("editor-change", this.watchEditorChange)); + } + watchEditorChange(editor: any, info: any) { + if (!("path" in info)) { + return; + } + if (!this.shouldBatchSave) { + return; + } + const file = info?.file as TFile; + if (!file) return; + if (this.storageAccess.isFileProcessing(file.path as FilePath)) { + // this._log(`Editor change skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE); + return; + } + if (!this.isWaiting(file.path as FilePath)) { + return; + } + const data = info?.data as string; + const fi: FileEvent = { + type: "CHANGED", + file: TFileToUXFileInfoStub(file), + cachedData: data, + }; + void this.appendQueue([fi]); + } + + watchVaultCreate(file: TAbstractFile, ctx?: any) { + if (file instanceof TFolder) return; + if (this.storageAccess.isFileProcessing(file.path as FilePath)) { + // this._log(`File create skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE); + return; + } + const fileInfo = TFileToUXFileInfoStub(file); + void this.appendQueue([{ type: "CREATE", file: fileInfo }], ctx); + } + + watchVaultChange(file: TAbstractFile, ctx?: any) { + if (file instanceof TFolder) return; + if (this.storageAccess.isFileProcessing(file.path as FilePath)) { + // this._log(`File change skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE); + return; + } + const fileInfo = TFileToUXFileInfoStub(file); + void this.appendQueue([{ type: "CHANGED", file: fileInfo }], ctx); + } + + watchVaultDelete(file: TAbstractFile, ctx?: any) { + if (file instanceof TFolder) return; + if (this.storageAccess.isFileProcessing(file.path as FilePath)) { + // this._log(`File delete skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE); + return; + } + const fileInfo = TFileToUXFileInfoStub(file, true); + void this.appendQueue([{ type: "DELETE", file: fileInfo }], ctx); + } + watchVaultRename(file: TAbstractFile, oldFile: string, ctx?: any) { + // vault Rename will not be raised for self-events (Self-hosted LiveSync will not handle 'rename'). + if (file instanceof TFile) { + const fileInfo = TFileToUXFileInfoStub(file); + void this.appendQueue( + [ + { + type: "DELETE", + file: { + path: oldFile as FilePath, + name: file.name, + stat: { + mtime: file.stat.mtime, + ctime: file.stat.ctime, + size: file.stat.size, + type: "file", + }, + deleted: true, + }, + skipBatchWait: true, + }, + { type: "CREATE", file: fileInfo, skipBatchWait: true }, + ], + ctx + ); + } + } + // Watch raw events (Internal API) + watchVaultRawEvents(path: FilePath) { + if (this.storageAccess.isFileProcessing(path)) { + // this._log(`Raw file event skipped because the file is being processed: ${path}`, LOG_LEVEL_VERBOSE); + return; + } + // Only for internal files. + if (!this.settings) return; + // if (this.plugin.settings.useIgnoreFiles && this.plugin.ignoreFiles.some(e => path.endsWith(e.trim()))) { + if (this.settings.useIgnoreFiles) { + // If it is one of ignore files, refresh the cached one. + // (Calling$$isTargetFile will refresh the cache) + void this.vaultService.isTargetFile(path).then(() => this._watchVaultRawEvents(path)); + } else { + void this._watchVaultRawEvents(path); + } + } + + async _watchVaultRawEvents(path: FilePath) { + if (!this.settings.syncInternalFiles && !this.settings.usePluginSync) return; + if (!this.settings.watchInternalFileChanges) return; + if (!path.startsWith(this.plugin.app.vault.configDir)) return; + if (path.endsWith("/")) { + // Folder + return; + } + const isTargetFile = await this.cmdHiddenFileSync.isTargetFile(path); + if (!isTargetFile) return; + + void this.appendQueue( + [ + { + type: "INTERNAL", + file: InternalFileToUXFileInfoStub(path), + skipBatchWait: true, // Internal files should be processed immediately. + }, + ], + null + ); + } + + async _saveSnapshot(snapshot: (FileEventItem | FileEventItemSentinel)[]) { + await this.core.kvDB.set("storage-event-manager-snapshot", snapshot); + this._log(`Storage operation snapshot saved: ${snapshot.length} items`, LOG_LEVEL_DEBUG); + } + + async _loadSnapshot() { + const snapShot = await this.core.kvDB.get<(FileEventItem | FileEventItemSentinel)[]>( + "storage-event-manager-snapshot" + ); + return snapShot; + } + + updateStatus() { + const allFileEventItems = this.bufferedQueuedItems.filter((e): e is FileEventItem => "args" in e); + const allItems = allFileEventItems.filter((e) => !e.cancelled); + const totalItems = allItems.length + this.concurrentProcessing.waiting; + const processing = this.processingCount; + const batchedCount = this._waitingMap.size; + this.core.batched.value = batchedCount; + this.core.processing.value = processing; + this.core.totalQueued.value = totalItems + batchedCount + processing; + } +} diff --git a/src/modules/core/ModuleTargetFilter.ts b/src/modules/core/ModuleTargetFilter.ts index 4014976..956f657 100644 --- a/src/modules/core/ModuleTargetFilter.ts +++ b/src/modules/core/ModuleTargetFilter.ts @@ -141,10 +141,10 @@ export class ModuleTargetFilter extends AbstractModule { services.vault.markFileListPossiblyChanged.setHandler(this._markFileListPossiblyChanged.bind(this)); services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this)); services.vault.isIgnoredByIgnoreFile.setHandler(this._isTargetIgnoredByIgnoreFiles.bind(this)); - services.vault.isTargetFile.addHandler(this._isTargetFileByFileNameDuplication.bind(this)); - services.vault.isTargetFile.addHandler(this._isTargetIgnoredByIgnoreFiles.bind(this)); - services.vault.isTargetFile.addHandler(this._isTargetFileByLocalDB.bind(this)); - services.vault.isTargetFile.addHandler(this._isTargetFileFinal.bind(this)); + services.vault.isTargetFile.addHandler(this._isTargetFileByFileNameDuplication.bind(this), 10); + services.vault.isTargetFile.addHandler(this._isTargetIgnoredByIgnoreFiles.bind(this), 20); + services.vault.isTargetFile.addHandler(this._isTargetFileByLocalDB.bind(this), 30); + services.vault.isTargetFile.addHandler(this._isTargetFileFinal.bind(this), 100); services.setting.onSettingRealised.addHandler(this.refreshSettings.bind(this)); } } diff --git a/src/modules/coreObsidian/storageLib/SerializedFileAccess.ts b/src/modules/coreObsidian/storageLib/SerializedFileAccess.ts deleted file mode 100644 index ac4b2d4..0000000 --- a/src/modules/coreObsidian/storageLib/SerializedFileAccess.ts +++ /dev/null @@ -1,264 +0,0 @@ -import { type App, TFile, type DataWriteOptions, TFolder, TAbstractFile } from "../../../deps.ts"; -import { Logger } from "../../../lib/src/common/logger.ts"; -import { isPlainText } from "../../../lib/src/string_and_binary/path.ts"; -import type { FilePath, UXFileInfoStub } from "../../../lib/src/common/types.ts"; -import { createBinaryBlob, isDocContentSame } from "../../../lib/src/common/utils.ts"; -import type { InternalFileInfo } from "../../../common/types.ts"; -import { markChangesAreSame } from "../../../common/utils.ts"; -import type { IStorageAccessManager } from "@lib/interfaces/StorageAccess.ts"; -import type { LiveSyncCore } from "@/main.ts"; -function toArrayBuffer(arr: Uint8Array | ArrayBuffer | DataView): ArrayBuffer { - if (arr instanceof Uint8Array) { - return arr.buffer; - } - if (arr instanceof DataView) { - return arr.buffer; - } - return arr; -} -// TODO: add abstraction for the file access (as wrapping TFile or something similar) -export abstract class FileAccessBase { - storageAccessManager: IStorageAccessManager; - constructor(storageAccessManager: IStorageAccessManager) { - this.storageAccessManager = storageAccessManager; - } - abstract getPath(file: TNativeFile | string): FilePath; -} - -export class ObsidianFileAccess extends FileAccessBase { - app: App; - plugin: LiveSyncCore; - - getPath(file: string | TFile): FilePath { - return (typeof file === "string" ? file : file.path) as FilePath; - } - - constructor(app: App, plugin: LiveSyncCore, storageAccessManager: IStorageAccessManager) { - super(storageAccessManager); - this.app = app; - this.plugin = plugin; - } - - async tryAdapterStat(file: TFile | string) { - const path = file instanceof TFile ? file.path : file; - return await this.storageAccessManager.processReadFile(path as FilePath, async () => { - if (!(await this.app.vault.adapter.exists(path))) return null; - return this.app.vault.adapter.stat(path); - }); - } - async adapterStat(file: TFile | string) { - const path = file instanceof TFile ? file.path : file; - return await this.storageAccessManager.processReadFile(path as FilePath, () => - this.app.vault.adapter.stat(path) - ); - } - async adapterExists(file: TFile | string) { - const path = file instanceof TFile ? file.path : file; - return await this.storageAccessManager.processReadFile(path as FilePath, () => - this.app.vault.adapter.exists(path) - ); - } - async adapterRemove(file: TFile | string) { - const path = file instanceof TFile ? file.path : file; - return await this.storageAccessManager.processWriteFile(path as FilePath, () => - this.app.vault.adapter.remove(path) - ); - } - - async adapterRead(file: TFile | string) { - const path = file instanceof TFile ? file.path : file; - return await this.storageAccessManager.processReadFile(path as FilePath, () => - this.app.vault.adapter.read(path) - ); - } - async adapterReadBinary(file: TFile | string) { - const path = file instanceof TFile ? file.path : file; - return await this.storageAccessManager.processReadFile(path as FilePath, () => - this.app.vault.adapter.readBinary(path) - ); - } - - async adapterReadAuto(file: TFile | string) { - const path = file instanceof TFile ? file.path : file; - if (isPlainText(path)) { - return await this.storageAccessManager.processReadFile(path as FilePath, () => - this.app.vault.adapter.read(path) - ); - } - return await this.storageAccessManager.processReadFile(path as FilePath, () => - this.app.vault.adapter.readBinary(path) - ); - } - - async adapterWrite( - file: TFile | string, - data: string | ArrayBuffer | Uint8Array, - options?: DataWriteOptions - ) { - const path = file instanceof TFile ? file.path : file; - if (typeof data === "string") { - return await this.storageAccessManager.processWriteFile(path as FilePath, () => - this.app.vault.adapter.write(path, data, options) - ); - } else { - return await this.storageAccessManager.processWriteFile(path as FilePath, () => - this.app.vault.adapter.writeBinary(path, toArrayBuffer(data), options) - ); - } - } - - adapterList(basePath: string): Promise<{ files: string[]; folders: string[] }> { - return Promise.resolve(this.app.vault.adapter.list(basePath)); - } - - async vaultCacheRead(file: TFile) { - return await this.storageAccessManager.processReadFile(file.path as FilePath, () => - this.app.vault.cachedRead(file) - ); - } - - async vaultRead(file: TFile) { - return await this.storageAccessManager.processReadFile(file.path as FilePath, () => this.app.vault.read(file)); - } - - async vaultReadBinary(file: TFile) { - return await this.storageAccessManager.processReadFile(file.path as FilePath, () => - this.app.vault.readBinary(file) - ); - } - - async vaultReadAuto(file: TFile) { - const path = file.path; - if (isPlainText(path)) { - return await this.storageAccessManager.processReadFile(path as FilePath, () => this.app.vault.read(file)); - } - return await this.storageAccessManager.processReadFile(path as FilePath, () => this.app.vault.readBinary(file)); - } - - async vaultModify(file: TFile, data: string | ArrayBuffer | Uint8Array, options?: DataWriteOptions) { - if (typeof data === "string") { - return await this.storageAccessManager.processWriteFile(file.path as FilePath, async () => { - const oldData = await this.app.vault.read(file); - if (data === oldData) { - if (options && options.mtime) markChangesAreSame(file.path, file.stat.mtime, options.mtime); - return true; - } - await this.app.vault.modify(file, data, options); - return true; - }); - } else { - return await this.storageAccessManager.processWriteFile(file.path as FilePath, async () => { - const oldData = await this.app.vault.readBinary(file); - if (await isDocContentSame(createBinaryBlob(oldData), createBinaryBlob(data))) { - if (options && options.mtime) markChangesAreSame(file.path, file.stat.mtime, options.mtime); - return true; - } - await this.app.vault.modifyBinary(file, toArrayBuffer(data), options); - return true; - }); - } - } - async vaultCreate( - path: string, - data: string | ArrayBuffer | Uint8Array, - options?: DataWriteOptions - ): Promise { - if (typeof data === "string") { - return await this.storageAccessManager.processWriteFile(path as FilePath, () => - this.app.vault.create(path, data, options) - ); - } else { - return await this.storageAccessManager.processWriteFile(path as FilePath, () => - this.app.vault.createBinary(path, toArrayBuffer(data), options) - ); - } - } - - trigger(name: string, ...data: any[]) { - return this.app.vault.trigger(name, ...data); - } - async reconcileInternalFile(path: string) { - await (this.app.vault.adapter as any)?.reconcileInternalFile(path); - } - - async adapterAppend(normalizedPath: string, data: string, options?: DataWriteOptions) { - return await this.app.vault.adapter.append(normalizedPath, data, options); - } - - async delete(file: TFile | TFolder, force = false) { - return await this.storageAccessManager.processWriteFile(file.path as FilePath, () => - this.app.vault.delete(file, force) - ); - } - async trash(file: TFile | TFolder, force = false) { - return await this.storageAccessManager.processWriteFile(file.path as FilePath, () => - this.app.vault.trash(file, force) - ); - } - - isStorageInsensitive(): boolean { - return this.plugin.services.vault.isStorageInsensitive(); - } - - getAbstractFileByPathInsensitive(path: FilePath | string): TAbstractFile | null { - //@ts-ignore - return this.app.vault.getAbstractFileByPathInsensitive(path); - } - - getAbstractFileByPath(path: FilePath | string): TAbstractFile | null { - if (!this.plugin.settings.handleFilenameCaseSensitive || this.isStorageInsensitive()) { - return this.getAbstractFileByPathInsensitive(path); - } - return this.app.vault.getAbstractFileByPath(path); - } - - getFiles() { - return this.app.vault.getFiles(); - } - - async ensureDirectory(fullPath: string) { - const pathElements = fullPath.split("/"); - pathElements.pop(); - let c = ""; - for (const v of pathElements) { - c += v; - try { - await this.app.vault.adapter.mkdir(c); - } catch (ex: any) { - if (ex?.message == "Folder already exists.") { - // Skip if already exists. - } else { - Logger("Folder Create Error"); - Logger(ex); - } - } - c += "/"; - } - } - - touchedFiles: string[] = []; - - _statInternal(file: FilePath) { - return this.app.vault.adapter.stat(file); - } - - async touch(file: TFile | FilePath) { - const path = file instanceof TFile ? (file.path as FilePath) : file; - const statOrg = file instanceof TFile ? file.stat : await this._statInternal(path); - const stat = statOrg || { mtime: 0, size: 0 }; - const key = `${path}-${stat.mtime}-${stat.size}`; - this.touchedFiles.unshift(key); - this.touchedFiles = this.touchedFiles.slice(0, 100); - } - recentlyTouched(file: TFile | InternalFileInfo | UXFileInfoStub) { - const key = - "stat" in file - ? `${file.path}-${file.stat.mtime}-${file.stat.size}` - : `${file.path}-${file.mtime}-${file.size}`; - if (this.touchedFiles.indexOf(key) == -1) return false; - return true; - } - clearTouched() { - this.touchedFiles = []; - } -} diff --git a/src/modules/coreObsidian/storageLib/StorageEventManager.ts b/src/modules/coreObsidian/storageLib/StorageEventManager.ts deleted file mode 100644 index 90b1dc6..0000000 --- a/src/modules/coreObsidian/storageLib/StorageEventManager.ts +++ /dev/null @@ -1,631 +0,0 @@ -import { TAbstractFile, TFile, TFolder } from "../../../deps.ts"; -import { Logger } from "../../../lib/src/common/logger.ts"; -import { shouldBeIgnored } from "../../../lib/src/string_and_binary/path.ts"; -import { - DEFAULT_SETTINGS, - LOG_LEVEL_DEBUG, - LOG_LEVEL_INFO, - LOG_LEVEL_NOTICE, - LOG_LEVEL_VERBOSE, - type FileEventType, - type FilePath, - type UXFileInfoStub, -} from "../../../lib/src/common/types.ts"; -import { delay, fireAndForget, throttle } from "../../../lib/src/common/utils.ts"; -import { type FileEventItem } from "../../../common/types.ts"; -import { serialized, skipIfDuplicated } from "octagonal-wheels/concurrency/lock"; -import { isWaitingForTimeout } from "octagonal-wheels/concurrency/task"; -import { Semaphore } from "octagonal-wheels/concurrency/semaphore"; -import type { LiveSyncCore } from "../../../main.ts"; -import { InternalFileToUXFileInfoStub, TFileToUXFileInfoStub } from "./utilObsidian.ts"; -import ObsidianLiveSyncPlugin from "../../../main.ts"; -import type { IStorageAccessManager } from "@lib/interfaces/StorageAccess.ts"; -import { HiddenFileSync } from "../../../features/HiddenFileSync/CmdHiddenFileSync.ts"; -import { promiseWithResolvers, type PromiseWithResolvers } from "octagonal-wheels/promises"; -import { StorageEventManager, type FileEvent } from "@lib/interfaces/StorageEventManager.ts"; - -type WaitInfo = { - since: number; - type: FileEventType; - canProceed: PromiseWithResolvers; - timerHandler: ReturnType; - event: FileEventItem; -}; -const TYPE_SENTINEL_FLUSH = "SENTINEL_FLUSH"; -type FileEventItemSentinelFlush = { - type: typeof TYPE_SENTINEL_FLUSH; -}; -type FileEventItemSentinel = FileEventItemSentinelFlush; - -export class StorageEventManagerObsidian extends StorageEventManager { - plugin: ObsidianLiveSyncPlugin; - core: LiveSyncCore; - storageAccess: IStorageAccessManager; - get services() { - return this.core.services; - } - - get shouldBatchSave() { - return this.core.settings?.batchSave && this.core.settings?.liveSync != true; - } - get batchSaveMinimumDelay(): number { - return this.core.settings?.batchSaveMinimumDelay ?? DEFAULT_SETTINGS.batchSaveMinimumDelay; - } - get batchSaveMaximumDelay(): number { - return this.core.settings?.batchSaveMaximumDelay ?? DEFAULT_SETTINGS.batchSaveMaximumDelay; - } - // Necessary evil. - cmdHiddenFileSync: HiddenFileSync; - - /** - * Snapshot restoration promise. - * Snapshot will be restored before starting to watch vault changes. - * In designed time, this has been called from Initialisation process, which has been implemented on `ModuleInitializerFile.ts`. - */ - snapShotRestored: Promise | null = null; - - constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore, storageAccessManager: IStorageAccessManager) { - super(); - this.storageAccess = storageAccessManager; - this.plugin = plugin; - this.core = core; - this.cmdHiddenFileSync = this.plugin.getAddOn(HiddenFileSync.name) as HiddenFileSync; - } - - /** - * Restore the previous snapshot if exists. - * @returns - */ - restoreState(): Promise { - this.snapShotRestored = this._restoreFromSnapshot(); - return this.snapShotRestored; - } - - async beginWatch() { - await this.snapShotRestored; - const plugin = this.plugin; - this.watchVaultChange = this.watchVaultChange.bind(this); - this.watchVaultCreate = this.watchVaultCreate.bind(this); - this.watchVaultDelete = this.watchVaultDelete.bind(this); - this.watchVaultRename = this.watchVaultRename.bind(this); - this.watchVaultRawEvents = this.watchVaultRawEvents.bind(this); - this.watchEditorChange = this.watchEditorChange.bind(this); - plugin.registerEvent(plugin.app.vault.on("modify", this.watchVaultChange)); - plugin.registerEvent(plugin.app.vault.on("delete", this.watchVaultDelete)); - plugin.registerEvent(plugin.app.vault.on("rename", this.watchVaultRename)); - plugin.registerEvent(plugin.app.vault.on("create", this.watchVaultCreate)); - //@ts-ignore : Internal API - plugin.registerEvent(plugin.app.vault.on("raw", this.watchVaultRawEvents)); - plugin.registerEvent(plugin.app.workspace.on("editor-change", this.watchEditorChange)); - } - watchEditorChange(editor: any, info: any) { - if (!("path" in info)) { - return; - } - if (!this.shouldBatchSave) { - return; - } - const file = info?.file as TFile; - if (!file) return; - if (this.storageAccess.isFileProcessing(file.path as FilePath)) { - // Logger(`Editor change skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE); - return; - } - if (!this.isWaiting(file.path as FilePath)) { - return; - } - const data = info?.data as string; - const fi: FileEvent = { - type: "CHANGED", - file: TFileToUXFileInfoStub(file), - cachedData: data, - }; - void this.appendQueue([fi]); - } - - watchVaultCreate(file: TAbstractFile, ctx?: any) { - if (file instanceof TFolder) return; - if (this.storageAccess.isFileProcessing(file.path as FilePath)) { - // Logger(`File create skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE); - return; - } - const fileInfo = TFileToUXFileInfoStub(file); - void this.appendQueue([{ type: "CREATE", file: fileInfo }], ctx); - } - - watchVaultChange(file: TAbstractFile, ctx?: any) { - if (file instanceof TFolder) return; - if (this.storageAccess.isFileProcessing(file.path as FilePath)) { - // Logger(`File change skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE); - return; - } - const fileInfo = TFileToUXFileInfoStub(file); - void this.appendQueue([{ type: "CHANGED", file: fileInfo }], ctx); - } - - watchVaultDelete(file: TAbstractFile, ctx?: any) { - if (file instanceof TFolder) return; - if (this.storageAccess.isFileProcessing(file.path as FilePath)) { - // Logger(`File delete skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE); - return; - } - const fileInfo = TFileToUXFileInfoStub(file, true); - void this.appendQueue([{ type: "DELETE", file: fileInfo }], ctx); - } - watchVaultRename(file: TAbstractFile, oldFile: string, ctx?: any) { - // vault Rename will not be raised for self-events (Self-hosted LiveSync will not handle 'rename'). - if (file instanceof TFile) { - const fileInfo = TFileToUXFileInfoStub(file); - void this.appendQueue( - [ - { - type: "DELETE", - file: { - path: oldFile as FilePath, - name: file.name, - stat: { - mtime: file.stat.mtime, - ctime: file.stat.ctime, - size: file.stat.size, - type: "file", - }, - deleted: true, - }, - skipBatchWait: true, - }, - { type: "CREATE", file: fileInfo, skipBatchWait: true }, - ], - ctx - ); - } - } - // Watch raw events (Internal API) - watchVaultRawEvents(path: FilePath) { - if (this.storageAccess.isFileProcessing(path)) { - // Logger(`Raw file event skipped because the file is being processed: ${path}`, LOG_LEVEL_VERBOSE); - return; - } - // Only for internal files. - if (!this.plugin.settings) return; - // if (this.plugin.settings.useIgnoreFiles && this.plugin.ignoreFiles.some(e => path.endsWith(e.trim()))) { - if (this.plugin.settings.useIgnoreFiles) { - // If it is one of ignore files, refresh the cached one. - // (Calling$$isTargetFile will refresh the cache) - void this.services.vault.isTargetFile(path).then(() => this._watchVaultRawEvents(path)); - } else { - void this._watchVaultRawEvents(path); - } - } - - async _watchVaultRawEvents(path: FilePath) { - if (!this.plugin.settings.syncInternalFiles && !this.plugin.settings.usePluginSync) return; - if (!this.plugin.settings.watchInternalFileChanges) return; - if (!path.startsWith(this.plugin.app.vault.configDir)) return; - if (path.endsWith("/")) { - // Folder - return; - } - const isTargetFile = await this.cmdHiddenFileSync.isTargetFile(path); - if (!isTargetFile) return; - - void this.appendQueue( - [ - { - type: "INTERNAL", - file: InternalFileToUXFileInfoStub(path), - skipBatchWait: true, // Internal files should be processed immediately. - }, - ], - null - ); - } - - // Cache file and waiting to can be proceed. - async appendQueue(params: FileEvent[], ctx?: any) { - if (!this.core.settings.isConfigured) return; - if (this.core.settings.suspendFileWatching) return; - if (this.core.settings.maxMTimeForReflectEvents > 0) { - return; - } - this.core.services.vault.markFileListPossiblyChanged(); - // Flag up to be reload - for (const param of params) { - if (shouldBeIgnored(param.file.path)) { - continue; - } - const atomicKey = [0, 0, 0, 0, 0, 0].map((e) => `${Math.floor(Math.random() * 100000)}`).join("-"); - const type = param.type; - const file = param.file; - const oldPath = param.oldPath; - if (type !== "INTERNAL") { - const size = (file as UXFileInfoStub).stat.size; - if (this.services.vault.isFileSizeTooLarge(size) && (type == "CREATE" || type == "CHANGED")) { - Logger( - `The storage file has been changed but exceeds the maximum size. Skipping: ${param.file.path}`, - LOG_LEVEL_NOTICE - ); - continue; - } - } - if (file instanceof TFolder) continue; - // TODO: Confirm why only the TFolder skipping - // Possibly following line is needed... - // if (file?.isFolder) continue; - if (!(await this.services.vault.isTargetFile(file.path))) continue; - - // Stop cache using to prevent the corruption; - // let cache: null | string | ArrayBuffer; - // new file or something changed, cache the changes. - // if (file instanceof TFile && (type == "CREATE" || type == "CHANGED")) { - if (file instanceof TFile || !file.isFolder) { - if (type == "CREATE" || type == "CHANGED") { - // Wait for a bit while to let the writer has marked `touched` at the file. - await delay(10); - if (this.core.storageAccess.recentlyTouched(file.path)) { - continue; - } - } - } - - let cache: string | undefined = undefined; - if (param.cachedData) { - cache = param.cachedData; - } - void this.enqueue({ - type, - args: { - file: file, - oldPath, - cache, - ctx, - }, - skipBatchWait: param.skipBatchWait, - key: atomicKey, - }); - } - } - private bufferedQueuedItems = [] as (FileEventItem | FileEventItemSentinel)[]; - - /** - * Immediately take snapshot. - */ - private _triggerTakeSnapshot() { - void this._takeSnapshot(); - } - /** - * Trigger taking snapshot after throttled period. - */ - triggerTakeSnapshot = throttle(() => this._triggerTakeSnapshot(), 100); - - enqueue(newItem: FileEventItem) { - if (newItem.type == "DELETE") { - // If the sentinel pushed, the runQueuedEvents will wait for idle before processing delete. - this.bufferedQueuedItems.push({ - type: TYPE_SENTINEL_FLUSH, - }); - } - this.updateStatus(); - this.bufferedQueuedItems.push(newItem); - - fireAndForget(() => this._takeSnapshot().then(() => this.runQueuedEvents())); - } - - // Limit concurrent processing to reduce the IO load. file-processing + scheduler (1), so file events can be processed in 4 slots. - concurrentProcessing = Semaphore(5); - - private _waitingMap = new Map(); - private _waitForIdle: Promise | null = null; - - /** - * Wait until all queued events are processed. - * Subsequent new events will not be waited, but new events will not be added. - * @returns - */ - waitForIdle(): Promise { - if (this._waitingMap.size === 0) { - return Promise.resolve(); - } - if (this._waitForIdle) { - return this._waitForIdle; - } - const promises = [...this._waitingMap.entries()].map(([key, waitInfo]) => { - return new Promise((resolve) => { - waitInfo.canProceed.promise - .then(() => { - Logger(`Processing ${key}: Wait for idle completed`, LOG_LEVEL_DEBUG); - // No op - }) - .catch((e) => { - Logger(`Processing ${key}: Wait for idle error`, LOG_LEVEL_INFO); - Logger(e, LOG_LEVEL_VERBOSE); - //no op - }) - .finally(() => { - resolve(); - }); - this._proceedWaiting(key); - }); - }); - const waitPromise = Promise.all(promises).then(() => { - this._waitForIdle = null; - Logger(`All wait for idle completed`, LOG_LEVEL_VERBOSE); - }); - this._waitForIdle = waitPromise; - return waitPromise; - } - - /** - * Proceed waiting for the given key immediately. - */ - private _proceedWaiting(key: string) { - const waitInfo = this._waitingMap.get(key); - if (waitInfo) { - waitInfo.canProceed.resolve(true); - clearTimeout(waitInfo.timerHandler); - this._waitingMap.delete(key); - } - this.triggerTakeSnapshot(); - } - /** - * Cancel waiting for the given key. - */ - private _cancelWaiting(key: string) { - const waitInfo = this._waitingMap.get(key); - if (waitInfo) { - waitInfo.canProceed.resolve(false); - clearTimeout(waitInfo.timerHandler); - this._waitingMap.delete(key); - } - this.triggerTakeSnapshot(); - } - /** - * Add waiting for the given key. - * @param key - * @param event - * @param waitedSince Optional waited since timestamp to calculate the remaining delay. - */ - private _addWaiting(key: string, event: FileEventItem, waitedSince?: number): WaitInfo { - if (this._waitingMap.has(key)) { - // Already waiting - throw new Error(`Already waiting for key: ${key}`); - } - const resolver = promiseWithResolvers(); - const now = Date.now(); - const since = waitedSince ?? now; - const elapsed = now - since; - const maxDelay = this.batchSaveMaximumDelay * 1000; - const remainingDelay = Math.max(0, maxDelay - elapsed); - const nextDelay = Math.min(remainingDelay, this.batchSaveMinimumDelay * 1000); - // x*<------- maxDelay --------->* - // x*<-- minDelay -->* - // x* x<-- nextDelay -->* - // x* x<-- Capped-->* - // x* x.......* - // x: event - // *: save - // When at event (x) At least, save (*) within maxDelay, but maintain minimum delay between saves. - - if (elapsed >= maxDelay) { - // Already exceeded maximum delay, do not wait. - Logger(`Processing ${key}: Batch save maximum delay already exceeded: ${event.type}`, LOG_LEVEL_DEBUG); - } else { - Logger(`Processing ${key}: Adding waiting for batch save: ${event.type} (${nextDelay}ms)`, LOG_LEVEL_DEBUG); - } - const waitInfo: WaitInfo = { - since: since, - type: event.type, - event: event, - canProceed: resolver, - timerHandler: setTimeout(() => { - Logger(`Processing ${key}: Batch save timeout reached: ${event.type}`, LOG_LEVEL_DEBUG); - this._proceedWaiting(key); - }, nextDelay), - }; - this._waitingMap.set(key, waitInfo); - this.triggerTakeSnapshot(); - return waitInfo; - } - - /** - * Process the given file event. - */ - async processFileEvent(fei: FileEventItem) { - const releaser = await this.concurrentProcessing.acquire(); - try { - this.updateStatus(); - const filename = fei.args.file.path; - const waitingKey = `${filename}`; - const previous = this._waitingMap.get(waitingKey); - let isShouldBeCancelled = fei.skipBatchWait || false; - let previousPromise: Promise = Promise.resolve(true); - let waitPromise: Promise = Promise.resolve(true); - // 1. Check if there is previous waiting for the same file - if (previous) { - previousPromise = previous.canProceed.promise; - if (isShouldBeCancelled) { - Logger( - `Processing ${filename}: Requested to perform immediately, cancelling previous waiting: ${fei.type}`, - LOG_LEVEL_DEBUG - ); - } - if (!isShouldBeCancelled && fei.type === "DELETE") { - // For DELETE, cancel any previous waiting and proceed immediately - // That because when deleting, we cannot read the file anymore. - Logger( - `Processing ${filename}: DELETE requested, cancelling previous waiting: ${fei.type}`, - LOG_LEVEL_DEBUG - ); - isShouldBeCancelled = true; - } - if (!isShouldBeCancelled && previous.type === fei.type) { - // For the same type, we can cancel the previous waiting and proceed immediately. - Logger(`Processing ${filename}: Cancelling previous waiting: ${fei.type}`, LOG_LEVEL_DEBUG); - isShouldBeCancelled = true; - } - // 2. wait for the previous to complete - if (isShouldBeCancelled) { - this._cancelWaiting(waitingKey); - Logger(`Processing ${filename}: Previous cancelled: ${fei.type}`, LOG_LEVEL_DEBUG); - isShouldBeCancelled = true; - } - if (!isShouldBeCancelled) { - Logger(`Processing ${filename}: Waiting for previous to complete: ${fei.type}`, LOG_LEVEL_DEBUG); - this._proceedWaiting(waitingKey); - Logger(`Processing ${filename}: Previous completed: ${fei.type}`, LOG_LEVEL_DEBUG); - } - } - await previousPromise; - // 3. Check if shouldBatchSave is true - if (this.shouldBatchSave && !fei.skipBatchWait) { - // if type is CREATE or CHANGED, set waiting - if (fei.type == "CREATE" || fei.type == "CHANGED") { - // 3.2. If true, set the queue, and wait for the waiting, or until timeout - // (since is copied from previous waiting if exists to limit the maximum wait time) - // console.warn(`Since:`, previous?.since); - const info = this._addWaiting(waitingKey, fei, previous?.since); - waitPromise = info.canProceed.promise; - } else if (fei.type == "DELETE") { - // For DELETE, cancel any previous waiting and proceed immediately - } - Logger(`Processing ${filename}: Waiting for batch save: ${fei.type}`, LOG_LEVEL_DEBUG); - const canProceed = await waitPromise; - if (!canProceed) { - // 3.2.1. If cancelled by new queue, cancel subsequent process. - Logger(`Processing ${filename}: Cancelled by new queue: ${fei.type}`, LOG_LEVEL_DEBUG); - return; - } - } - // await this.handleFileEvent(fei); - await this.requestProcessQueue(fei); - } finally { - await this._takeSnapshot(); - releaser(); - } - } - async _takeSnapshot() { - const processingEvents = [...this._waitingMap.values()].map((e) => e.event); - const waitingEvents = this.bufferedQueuedItems; - const snapShot = [...processingEvents, ...waitingEvents]; - await this.core.kvDB.set("storage-event-manager-snapshot", snapShot); - Logger(`Storage operation snapshot taken: ${snapShot.length} items`, LOG_LEVEL_DEBUG); - this.updateStatus(); - } - async _restoreFromSnapshot() { - const snapShot = await this.core.kvDB.get<(FileEventItem | FileEventItemSentinel)[]>( - "storage-event-manager-snapshot" - ); - if (snapShot && Array.isArray(snapShot) && snapShot.length > 0) { - // console.warn(`Restoring snapshot: ${snapShot.length} items`); - Logger(`Restoring storage operation snapshot: ${snapShot.length} items`, LOG_LEVEL_VERBOSE); - // Restore the snapshot - // Note: Mark all items as skipBatchWait to prevent apply the off-line batch saving. - this.bufferedQueuedItems = snapShot.map((e) => ({ ...e, skipBatchWait: true })); - this.updateStatus(); - await this.runQueuedEvents(); - } else { - Logger(`No snapshot to restore`, LOG_LEVEL_VERBOSE); - // console.warn(`No snapshot to restore`); - } - } - runQueuedEvents() { - return skipIfDuplicated("storage-event-manager-run-queued-events", async () => { - do { - if (this.bufferedQueuedItems.length === 0) { - break; - } - // 1. Get the first queued item - - const fei = this.bufferedQueuedItems.shift()!; - await this._takeSnapshot(); - this.updateStatus(); - // 2. Consume 1 semaphore slot to enqueue processing. Then release immediately. - // (Just to limit the total concurrent processing count, because skipping batch handles at processFileEvent). - const releaser = await this.concurrentProcessing.acquire(); - releaser(); - this.updateStatus(); - // 3. Check if sentinel flush - // If sentinel, wait for idle and continue. - if (fei.type === TYPE_SENTINEL_FLUSH) { - Logger(`Waiting for idle`, LOG_LEVEL_VERBOSE); - // Flush all waiting batch queues - await this.waitForIdle(); - this.updateStatus(); - continue; - } - // 4. Process the event, this should be fire-and-forget to not block the queue processing in each file. - fireAndForget(() => this.processFileEvent(fei)); - } while (this.bufferedQueuedItems.length > 0); - }); - } - - processingCount = 0; - async requestProcessQueue(fei: FileEventItem) { - try { - this.processingCount++; - // this.bufferedQueuedItems.remove(fei); - this.updateStatus(); - // this.waitedSince.delete(fei.args.file.path); - await this.handleFileEvent(fei); - await this._takeSnapshot(); - } finally { - this.processingCount--; - this.updateStatus(); - } - } - isWaiting(filename: FilePath) { - return isWaitingForTimeout(`storage-event-manager-batchsave-${filename}`); - } - - updateStatus() { - const allFileEventItems = this.bufferedQueuedItems.filter((e): e is FileEventItem => "args" in e); - const allItems = allFileEventItems.filter((e) => !e.cancelled); - const totalItems = allItems.length + this.concurrentProcessing.waiting; - const processing = this.processingCount; - const batchedCount = this._waitingMap.size; - this.core.batched.value = batchedCount; - this.core.processing.value = processing; - this.core.totalQueued.value = totalItems + batchedCount + processing; - } - - async handleFileEvent(queue: FileEventItem): Promise { - const file = queue.args.file; - const lockKey = `handleFile:${file.path}`; - const ret = await serialized(lockKey, async () => { - if (queue.cancelled) { - Logger(`File event cancelled before processing: ${file.path}`, LOG_LEVEL_INFO); - return; - } - if (queue.type == "INTERNAL" || file.isInternal) { - await this.core.services.fileProcessing.processOptionalFileEvent(file.path as unknown as FilePath); - } else { - const key = `file-last-proc-${queue.type}-${file.path}`; - const last = Number((await this.core.kvDB.get(key)) || 0); - if (queue.type == "DELETE") { - await this.core.services.fileProcessing.processFileEvent(queue); - } else { - if (file.stat.mtime == last) { - Logger(`File has been already scanned on ${queue.type}, skip: ${file.path}`, LOG_LEVEL_VERBOSE); - // Should Cancel the relative operations? (e.g. rename) - // this.cancelRelativeEvent(queue); - return; - } - if (!(await this.core.services.fileProcessing.processFileEvent(queue))) { - Logger( - `STORAGE -> DB: Handler failed, cancel the relative operations: ${file.path}`, - LOG_LEVEL_INFO - ); - // cancel running queues and remove one of atomic operation (e.g. rename) - this.cancelRelativeEvent(queue); - return; - } - } - } - }); - this.updateStatus(); - return ret; - } - - cancelRelativeEvent(item: FileEventItem): void { - this._cancelWaiting(item.args.file.path); - } -} diff --git a/src/modules/coreObsidian/storageLib/utilObsidian.ts b/src/modules/coreObsidian/storageLib/utilObsidian.ts index 0158072..c4d4dfe 100644 --- a/src/modules/coreObsidian/storageLib/utilObsidian.ts +++ b/src/modules/coreObsidian/storageLib/utilObsidian.ts @@ -2,7 +2,6 @@ import { TFile, type TAbstractFile, type TFolder } from "../../../deps.ts"; import { ICHeader } from "../../../common/types.ts"; -import type { ObsidianFileAccess } from "./SerializedFileAccess.ts"; import { addPrefix, isPlainText } from "../../../lib/src/string_and_binary/path.ts"; import { LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger"; import { createBlob } from "../../../lib/src/common/utils.ts"; @@ -15,6 +14,7 @@ import type { UXInternalFileInfoStub, } from "../../../lib/src/common/types.ts"; import type { LiveSyncCore } from "../../../main.ts"; +import type { FileAccessObsidian } from "@/serviceModules/FileAccessObsidian.ts"; export async function TFileToUXFileInfo( core: LiveSyncCore, @@ -51,7 +51,7 @@ export async function TFileToUXFileInfo( export async function InternalFileToUXFileInfo( fullPath: string, - vaultAccess: ObsidianFileAccess, + vaultAccess: FileAccessObsidian, prefix: string = ICHeader ): Promise { const name = fullPath.split("/").pop() as string; diff --git a/src/serviceModules/FileAccessObsidian.ts b/src/serviceModules/FileAccessObsidian.ts new file mode 100644 index 0000000..4755717 --- /dev/null +++ b/src/serviceModules/FileAccessObsidian.ts @@ -0,0 +1,156 @@ +import { markChangesAreSame } from "@/common/utils"; +import type { FilePath, UXDataWriteOptions, UXFileInfoStub, UXFolderInfo } from "@lib/common/types"; + +import { TFolder, type TAbstractFile, TFile, type Stat, type App, type DataWriteOptions } from "@/deps"; +import { FileAccessBase, toArrayBuffer, type FileAccessBaseDependencies } from "@lib/serviceModules/FileAccessBase.ts"; +import { TFileToUXFileInfoStub } from "@/modules/coreObsidian/storageLib/utilObsidian"; + +declare module "obsidian" { + interface Vault { + getAbstractFileByPathInsensitive(path: string): TAbstractFile | null; + } + interface DataAdapter { + reconcileInternalFile?(path: string): Promise; + } +} + +export class FileAccessObsidian extends FileAccessBase { + app: App; + + override getPath(file: string | TAbstractFile): FilePath { + return (typeof file === "string" ? file : file.path) as FilePath; + } + + override isFile(file: TAbstractFile | null): file is TFile { + return file instanceof TFile; + } + override isFolder(file: TAbstractFile | null): file is TFolder { + return file instanceof TFolder; + } + override _statFromNative(file: TFile): Promise { + return Promise.resolve(file.stat); + } + + override nativeFileToUXFileInfoStub(file: TFile): UXFileInfoStub { + return TFileToUXFileInfoStub(file); + } + override nativeFolderToUXFolder(folder: TFolder): UXFolderInfo { + if (folder instanceof TFolder) { + return this.nativeFolderToUXFolder(folder); + } else { + throw new Error(`Not a folder: ${(folder as TAbstractFile)?.name}`); + } + } + + constructor(app: App, dependencies: FileAccessBaseDependencies) { + super({ + storageAccessManager: dependencies.storageAccessManager, + vaultService: dependencies.vaultService, + settingService: dependencies.settingService, + APIService: dependencies.APIService, + }); + this.app = app; + } + + protected async _adapterMkdir(path: string) { + await this.app.vault.adapter.mkdir(path); + } + protected _getAbstractFileByPath(path: FilePath) { + return this.app.vault.getAbstractFileByPath(path); + } + protected _getAbstractFileByPathInsensitive(path: FilePath) { + return this.app.vault.getAbstractFileByPathInsensitive(path); + } + + protected async _tryAdapterStat(path: FilePath) { + if (!(await this.app.vault.adapter.exists(path))) return null; + return await this.app.vault.adapter.stat(path); + } + + protected async _adapterStat(path: FilePath) { + return await this.app.vault.adapter.stat(path); + } + + protected async _adapterExists(path: FilePath) { + return await this.app.vault.adapter.exists(path); + } + protected async _adapterRemove(path: FilePath) { + await this.app.vault.adapter.remove(path); + } + + protected async _adapterRead(path: FilePath) { + return await this.app.vault.adapter.read(path); + } + + protected async _adapterReadBinary(path: FilePath) { + return await this.app.vault.adapter.readBinary(path); + } + + _adapterWrite(file: string, data: string, options?: UXDataWriteOptions): Promise { + return this.app.vault.adapter.write(file, data, options); + } + _adapterWriteBinary(file: string, data: ArrayBuffer, options?: UXDataWriteOptions): Promise { + return this.app.vault.adapter.writeBinary(file, toArrayBuffer(data), options); + } + + protected _adapterList(basePath: string): Promise<{ files: string[]; folders: string[] }> { + return Promise.resolve(this.app.vault.adapter.list(basePath)); + } + + async _vaultCacheRead(file: TFile) { + return await this.app.vault.cachedRead(file); + } + + protected async _vaultRead(file: TFile): Promise { + return await this.app.vault.read(file); + } + + protected async _vaultReadBinary(file: TFile): Promise { + return await this.app.vault.readBinary(file); + } + + protected override markChangesAreSame(path: string, mtime: number, newMtime: number) { + return markChangesAreSame(path, mtime, newMtime); + } + + protected override async _vaultModify(file: TFile, data: string, options?: UXDataWriteOptions): Promise { + return await this.app.vault.modify(file, data, options); + } + protected override async _vaultModifyBinary( + file: TFile, + data: ArrayBuffer, + options?: UXDataWriteOptions + ): Promise { + return await this.app.vault.modifyBinary(file, toArrayBuffer(data), options); + } + protected override async _vaultCreate(path: string, data: string, options?: UXDataWriteOptions): Promise { + return await this.app.vault.create(path, data, options); + } + protected override async _vaultCreateBinary( + path: string, + data: ArrayBuffer, + options?: UXDataWriteOptions + ): Promise { + return await this.app.vault.createBinary(path, toArrayBuffer(data), options); + } + + protected override _trigger(name: string, ...data: any[]) { + return this.app.vault.trigger(name, ...data); + } + protected override async _reconcileInternalFile(path: string) { + return await Promise.resolve(this.app.vault.adapter.reconcileInternalFile?.(path)); + } + protected override async _adapterAppend(normalizedPath: string, data: string, options?: DataWriteOptions) { + return await this.app.vault.adapter.append(normalizedPath, data, options); + } + protected override async _delete(file: TFile | TFolder, force = false) { + return await this.app.vault.delete(file, force); + } + protected override async _trash(file: TFile | TFolder, force = false) { + return await this.app.vault.trash(file, force); + } + + protected override _getFiles() { + return this.app.vault.getFiles(); + } +} diff --git a/src/serviceModules/ServiceFileAccessImpl.ts b/src/serviceModules/ServiceFileAccessImpl.ts new file mode 100644 index 0000000..686dd0b --- /dev/null +++ b/src/serviceModules/ServiceFileAccessImpl.ts @@ -0,0 +1,6 @@ +import type { TAbstractFile, TFile, TFolder, Stat } from "@/deps"; + +import { ServiceFileAccessBase } from "@lib/serviceModules/ServiceFileAccessBase"; + +// For typechecking purpose +export class ServiceFileAccessObsidian extends ServiceFileAccessBase {} diff --git a/src/serviceModules/ServiceFileAccessObsidian.ts b/src/serviceModules/ServiceFileAccessObsidian.ts deleted file mode 100644 index 95a9278..0000000 --- a/src/serviceModules/ServiceFileAccessObsidian.ts +++ /dev/null @@ -1,363 +0,0 @@ -import { TFile, TFolder, type ListedFiles } from "@/deps.ts"; -import { LOG_LEVEL_INFO, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger"; -import type { - FilePath, - FilePathWithPrefix, - UXDataWriteOptions, - UXFileInfo, - UXFileInfoStub, - UXFolderInfo, - UXStat, -} from "@lib/common/types"; - -import { ServiceModuleBase } from "@lib/serviceModules/ServiceModuleBase"; -import type { APIService } from "@lib/services/base/APIService"; -import type { IStorageAccessManager, StorageAccess } from "@lib/interfaces/StorageAccess.ts"; -import type { AppLifecycleService } from "@lib/services/base/AppLifecycleService"; -import type { FileProcessingService } from "@lib/services/base/FileProcessingService"; -import { ObsidianFileAccess } from "@/modules/coreObsidian/storageLib/SerializedFileAccess"; -import { StorageEventManager } from "@lib/interfaces/StorageEventManager.ts"; -import { TFileToUXFileInfoStub, TFolderToUXFileInfoStub } from "@/modules/coreObsidian/storageLib/utilObsidian"; -import { createBlob, type CustomRegExp } from "@lib/common/utils"; -import type { VaultService } from "@lib/services/base/VaultService"; -import type { SettingService } from "@lib/services/base/SettingService"; - -export interface StorageAccessObsidianDependencies { - API: APIService; - appLifecycle: AppLifecycleService; - fileProcessing: FileProcessingService; - vault: VaultService; - setting: SettingService; - storageEventManager: StorageEventManager; - storageAccessManager: IStorageAccessManager; - vaultAccess: ObsidianFileAccess; -} - -export class ServiceFileAccessObsidian - extends ServiceModuleBase - implements StorageAccess -{ - private vaultAccess: ObsidianFileAccess; - private vaultManager: StorageEventManager; - private vault: VaultService; - private setting: SettingService; - - constructor(services: StorageAccessObsidianDependencies) { - super(services); - // this.appLifecycle = services.appLifecycle; - this.vault = services.vault; - this.setting = services.setting; - this.vaultManager = services.storageEventManager; - this.vaultAccess = services.vaultAccess; - services.appLifecycle.onFirstInitialise.addHandler(this._everyOnFirstInitialize.bind(this)); - services.fileProcessing.commitPendingFileEvents.addHandler(this._everyCommitPendingFileEvent.bind(this)); - } - - restoreState() { - return this.vaultManager.restoreState(); - } - async _everyOnFirstInitialize(): Promise { - await this.vaultManager.beginWatch(); - return Promise.resolve(true); - } - - async _everyCommitPendingFileEvent(): Promise { - await this.vaultManager.waitForIdle(); - return Promise.resolve(true); - } - - async writeFileAuto(path: string, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise { - const file = this.vaultAccess.getAbstractFileByPath(path); - if (file instanceof TFile) { - return this.vaultAccess.vaultModify(file, data, opt); - } else if (file === null) { - if (!path.endsWith(".md")) { - // Very rare case, we encountered this case with `writing-goals-history.csv` file. - // Indeed, that file not appears in the File Explorer, but it exists in the vault. - // Hence, we cannot retrieve the file from the vault by getAbstractFileByPath, and we cannot write it via vaultModify. - // It makes `File already exists` error. - // Therefore, we need to write it via adapterWrite. - // Maybe there are others like this, so I will write it via adapterWrite. - // This is a workaround for the issue, but I don't know if this is the right solution. - // (So limits to non-md files). - // Has Obsidian been patched?, anyway, writing directly might be a safer approach. - // However, does changes of that file trigger file-change event? - await this.vaultAccess.adapterWrite(path, data, opt); - // For safety, check existence - return await this.vaultAccess.adapterExists(path); - } else { - return (await this.vaultAccess.vaultCreate(path, data, opt)) instanceof TFile; - } - } else { - this._log(`Could not write file (Possibly already exists as a folder): ${path}`, LOG_LEVEL_VERBOSE); - return false; - } - } - readFileAuto(path: string): Promise { - const file = this.vaultAccess.getAbstractFileByPath(path); - if (file instanceof TFile) { - return this.vaultAccess.vaultRead(file); - } else { - throw new Error(`Could not read file (Possibly does not exist): ${path}`); - } - } - readFileText(path: string): Promise { - const file = this.vaultAccess.getAbstractFileByPath(path); - if (file instanceof TFile) { - return this.vaultAccess.vaultRead(file); - } else { - throw new Error(`Could not read file (Possibly does not exist): ${path}`); - } - } - isExists(path: string): Promise { - return Promise.resolve(this.vaultAccess.getAbstractFileByPath(path) instanceof TFile); - } - async writeHiddenFileAuto(path: string, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise { - try { - await this.vaultAccess.adapterWrite(path, data, opt); - return true; - } catch (e) { - this._log(`Could not write hidden file: ${path}`, LOG_LEVEL_VERBOSE); - this._log(e, LOG_LEVEL_VERBOSE); - return false; - } - } - async appendHiddenFile(path: string, data: string, opt?: UXDataWriteOptions): Promise { - try { - await this.vaultAccess.adapterAppend(path, data, opt); - return true; - } catch (e) { - this._log(`Could not append hidden file: ${path}`, LOG_LEVEL_VERBOSE); - this._log(e, LOG_LEVEL_VERBOSE); - return false; - } - } - stat(path: string): Promise { - const file = this.vaultAccess.getAbstractFileByPath(path); - if (file === null) return Promise.resolve(null); - if (file instanceof TFile) { - return Promise.resolve({ - ctime: file.stat.ctime, - mtime: file.stat.mtime, - size: file.stat.size, - type: "file", - }); - } else { - throw new Error(`Could not stat file (Possibly does not exist): ${path}`); - } - } - statHidden(path: string): Promise { - return this.vaultAccess.tryAdapterStat(path); - } - async removeHidden(path: string): Promise { - try { - await this.vaultAccess.adapterRemove(path); - if (this.vaultAccess.tryAdapterStat(path) !== null) { - return false; - } - return true; - } catch (e) { - this._log(`Could not remove hidden file: ${path}`, LOG_LEVEL_VERBOSE); - this._log(e, LOG_LEVEL_VERBOSE); - return false; - } - } - async readHiddenFileAuto(path: string): Promise { - return await this.vaultAccess.adapterReadAuto(path); - } - async readHiddenFileText(path: string): Promise { - return await this.vaultAccess.adapterRead(path); - } - async readHiddenFileBinary(path: string): Promise { - return await this.vaultAccess.adapterReadBinary(path); - } - async isExistsIncludeHidden(path: string): Promise { - return (await this.vaultAccess.tryAdapterStat(path)) !== null; - } - async ensureDir(path: string): Promise { - try { - await this.vaultAccess.ensureDirectory(path); - return true; - } catch (e) { - this._log(`Could not ensure directory: ${path}`, LOG_LEVEL_VERBOSE); - this._log(e, LOG_LEVEL_VERBOSE); - return false; - } - } - triggerFileEvent(event: string, path: string): void { - const file = this.vaultAccess.getAbstractFileByPath(path); - if (file === null) return; - this.vaultAccess.trigger(event, file); - } - async triggerHiddenFile(path: string): Promise { - await this.vaultAccess.reconcileInternalFile(path); - } - // getFileStub(file: TFile): UXFileInfoStub { - // return TFileToUXFileInfoStub(file); - // } - getFileStub(path: string): UXFileInfoStub | null { - const file = this.vaultAccess.getAbstractFileByPath(path); - if (file instanceof TFile) { - return TFileToUXFileInfoStub(file); - } else { - return null; - } - } - - async readStubContent(stub: UXFileInfoStub): Promise { - const file = this.vaultAccess.getAbstractFileByPath(stub.path); - if (!(file instanceof TFile)) { - this._log(`Could not read file (Possibly does not exist or a folder): ${stub.path}`, LOG_LEVEL_VERBOSE); - return false; - } - const data = await this.vaultAccess.vaultReadAuto(file); - return { - ...stub, - ...TFileToUXFileInfoStub(file), - body: createBlob(data), - }; - } - getStub(path: string): UXFileInfoStub | UXFolderInfo | null { - const file = this.vaultAccess.getAbstractFileByPath(path); - if (file instanceof TFile) { - return TFileToUXFileInfoStub(file); - } else if (file instanceof TFolder) { - return TFolderToUXFileInfoStub(file); - } - return null; - } - getFiles(): UXFileInfoStub[] { - return this.vaultAccess.getFiles().map((f) => TFileToUXFileInfoStub(f)); - } - getFileNames(): FilePath[] { - return this.vaultAccess.getFiles().map((f) => f.path as FilePath); - } - - async getFilesIncludeHidden( - basePath: string, - includeFilter?: CustomRegExp[], - excludeFilter?: CustomRegExp[], - skipFolder: string[] = [".git", ".trash", "node_modules"] - ): Promise { - let w: ListedFiles; - try { - w = await this.vaultAccess.adapterList(basePath); - // w = await this.plugin.app.vault.adapter.list(basePath); - } catch (ex) { - this._log(`Could not traverse(getFilesIncludeHidden):${basePath}`, LOG_LEVEL_INFO); - this._log(ex, LOG_LEVEL_VERBOSE); - return []; - } - skipFolder = skipFolder.map((e) => e.toLowerCase()); - - let files = [] as string[]; - for (const file of w.files) { - if (includeFilter && includeFilter.length > 0) { - if (!includeFilter.some((e) => e.test(file))) continue; - } - if (excludeFilter && excludeFilter.some((ee) => ee.test(file))) { - continue; - } - if (await this.vault.isIgnoredByIgnoreFile(file)) continue; - files.push(file); - } - - for (const v of w.folders) { - const folderName = (v.split("/").pop() ?? "").toLowerCase(); - if (skipFolder.some((e) => folderName === e)) { - continue; - } - - if (excludeFilter && excludeFilter.some((e) => e.test(v))) { - continue; - } - if (await this.vault.isIgnoredByIgnoreFile(v)) { - continue; - } - // OK, deep dive! - files = files.concat(await this.getFilesIncludeHidden(v, includeFilter, excludeFilter, skipFolder)); - } - return files as FilePath[]; - } - async touched(file: UXFileInfoStub | FilePathWithPrefix): Promise { - const path = typeof file === "string" ? file : file.path; - await this.vaultAccess.touch(path as FilePath); - } - recentlyTouched(file: UXFileInfoStub | FilePathWithPrefix): boolean { - const xFile = typeof file === "string" ? (this.vaultAccess.getAbstractFileByPath(file) as TFile) : file; - if (xFile === null) return false; - if (xFile instanceof TFolder) return false; - return this.vaultAccess.recentlyTouched(xFile); - } - clearTouched(): void { - this.vaultAccess.clearTouched(); - } - - delete(file: FilePathWithPrefix | UXFileInfoStub | string, force: boolean): Promise { - const xPath = typeof file === "string" ? file : file.path; - const xFile = this.vaultAccess.getAbstractFileByPath(xPath); - if (xFile === null) return Promise.resolve(); - if (!(xFile instanceof TFile) && !(xFile instanceof TFolder)) return Promise.resolve(); - return this.vaultAccess.delete(xFile, force); - } - trash(file: FilePathWithPrefix | UXFileInfoStub | string, system: boolean): Promise { - const xPath = typeof file === "string" ? file : file.path; - const xFile = this.vaultAccess.getAbstractFileByPath(xPath); - if (xFile === null) return Promise.resolve(); - if (!(xFile instanceof TFile) && !(xFile instanceof TFolder)) return Promise.resolve(); - return this.vaultAccess.trash(xFile, system); - } - // $readFileBinary(path: string): Promise { - // const file = this.vaultAccess.getAbstractFileByPath(path); - // if (file instanceof TFile) { - // return this.vaultAccess.vaultReadBinary(file); - // } else { - // throw new Error(`Could not read file (Possibly does not exist): ${path}`); - // } - // } - // async $appendFileAuto(path: string, data: string | ArrayBuffer, opt?: DataWriteOptions): Promise { - // const file = this.vaultAccess.getAbstractFileByPath(path); - // if (file instanceof TFile) { - // return this.vaultAccess.a(file, data, opt); - // } else if (file !== null) { - // return await this.vaultAccess.vaultCreate(path, data, opt) instanceof TFile; - // } else { - // this._log(`Could not append file (Possibly already exists as a folder): ${path}`, LOG_LEVEL_VERBOSE); - // return false; - // } - // } - - async __deleteVaultItem(file: TFile | TFolder) { - if (file instanceof TFile) { - if (!(await this.vault.isTargetFile(file.path))) return; - } - const dir = file.parent; - const settings = this.setting.currentSettings(); - if (settings.trashInsteadDelete) { - await this.vaultAccess.trash(file, false); - } else { - await this.vaultAccess.delete(file, true); - } - this._log(`xxx <- STORAGE (deleted) ${file.path}`); - if (dir) { - this._log(`files: ${dir.children.length}`); - if (dir.children.length == 0) { - if (!settings.doNotDeleteFolder) { - this._log( - `All files under the parent directory (${dir.path}) have been deleted, so delete this one.` - ); - await this.__deleteVaultItem(dir); - } - } - } - } - - async deleteVaultItem(fileSrc: FilePathWithPrefix | UXFileInfoStub | UXFolderInfo): Promise { - const path = typeof fileSrc === "string" ? fileSrc : fileSrc.path; - const file = this.vaultAccess.getAbstractFileByPath(path); - if (file === null) return; - if (file instanceof TFile || file instanceof TFolder) { - return await this.__deleteVaultItem(file); - } - } -} diff --git a/updates.md b/updates.md index ac65b30..76d2a3e 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,27 @@ 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.43-patched-6 + +18th February, 2026 + +Let me confess that I have lied about `now all ambiguous properties`... I have found some more implicit calling. + +Note: I have not checked hidden file sync and customisation sync, yet. Please report if you find any unexpected behaviour on these features. + +### Fixed + +- Now ReplicatorService responds to database reset and database initialisation events to dispose the active replicator. + - Fixes some unlocking issues during rebuilding. + +### Refactored + +- Now `StorageEventManagerBase` is separated from `StorageEventManagerObsidian` following their concerns. + - No longer using `ObsidianFileAccess` indirectly during checking duplicated-file events. + - Last event memorisation is now moved into the StorageAccessManager, just like the file processing interlocking. + - These methods, i.e., `ObsidianFileAccess.touch`. `StorageEventManager.recentlyTouched`, and `StorageEventManager.touch` are still available, but simply call the StorageAccessManager's methods. +- Now `FileAccessBase` is separated from `FileAccessObsidian` following their concerns. + ## 0.25.43-patched-5 17th February, 2026 From 1bde2b2ff14d230d5306e49d5a0124f5bde17c7c Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 19 Feb 2026 04:18:18 +0000 Subject: [PATCH 014/339] Fixed an issue where the StorageEventManager Build by Vite is now testing --- package-lock.json | 393 ++++++++++++++++++++++++++++++++++++++++++ package.json | 3 + src/lib | 2 +- terser_vite.config.ts | 49 ++++++ updates.md | 12 +- vite.config.ts | 166 +++++++++++++++++- 6 files changed, 619 insertions(+), 6 deletions(-) create mode 100644 terser_vite.config.ts diff --git a/package-lock.json b/package-lock.json index 8274cf0..ec37458 100644 --- a/package-lock.json +++ b/package-lock.json @@ -74,6 +74,7 @@ "pouchdb-replication": "^9.0.0", "pouchdb-utils": "^9.0.0", "prettier": "3.5.2", + "rollup-plugin-copy": "^3.5.0", "svelte": "5.41.1", "svelte-check": "^4.3.3", "svelte-preprocess": "^6.0.3", @@ -4701,6 +4702,27 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/fs-extra": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.5.tgz", + "integrity": "sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -4729,6 +4751,13 @@ "@types/lodash": "*" } }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", @@ -6312,6 +6341,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/array.prototype.findlastindex": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", @@ -6939,6 +6978,13 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true, + "license": "MIT" + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -7398,6 +7444,19 @@ "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/dns-over-http-resolver": { "version": "3.0.16", "resolved": "https://registry.npmjs.org/dns-over-http-resolver/-/dns-over-http-resolver-3.0.16.tgz", @@ -8761,6 +8820,38 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-extra/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -9059,6 +9150,61 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/globby": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", + "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/globby/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globby/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -9375,6 +9521,18 @@ "node": ">=0.8.19" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -9709,6 +9867,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-plain-object": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", + "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-reference": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", @@ -10270,6 +10438,16 @@ "json5": "lib/cli.js" } }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -11511,6 +11689,16 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -11554,6 +11742,16 @@ "node": "20 || >=22" } }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -12646,6 +12844,23 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-copy": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.5.0.tgz", + "integrity": "sha512-wI8D5dvYovRMx/YYKtUNt3Yxaw4ORC9xo6Gt9t22kveWz1enG9QrhVlagzwrxSC455xD1dHMKhIJkbsQ7d48BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^8.0.1", + "colorette": "^1.1.0", + "fs-extra": "^8.1.0", + "globby": "10.0.1", + "is-plain-object": "^3.0.0" + }, + "engines": { + "node": ">=8.3" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -13031,6 +13246,16 @@ "node": ">=18" } }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -18646,6 +18871,25 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true }, + "@types/fs-extra": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.5.tgz", + "integrity": "sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, "@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -18671,6 +18915,12 @@ "@types/lodash": "*" } }, + "@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, "@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", @@ -19807,6 +20057,12 @@ "math-intrinsics": "^1.1.0" } }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, "array.prototype.findlastindex": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", @@ -20217,6 +20473,12 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -20523,6 +20785,15 @@ "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, "dns-over-http-resolver": { "version": "3.0.16", "resolved": "https://registry.npmjs.org/dns-over-http-resolver/-/dns-over-http-resolver-3.0.16.tgz", @@ -21483,6 +21754,31 @@ "signal-exit": "^4.0.1" } }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "dependencies": { + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, "fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -21670,6 +21966,47 @@ "gopd": "^1.0.1" } }, + "globby": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", + "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, "gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -21873,6 +22210,16 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -22077,6 +22424,12 @@ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==" }, + "is-plain-object": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", + "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "dev": true + }, "is-reference": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", @@ -22480,6 +22833,15 @@ "minimist": "^1.2.0" } }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, "jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -23364,6 +23726,12 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -23394,6 +23762,12 @@ } } }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, "pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -24185,6 +24559,19 @@ "fsevents": "~2.3.2" } }, + "rollup-plugin-copy": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.5.0.tgz", + "integrity": "sha512-wI8D5dvYovRMx/YYKtUNt3Yxaw4ORC9xo6Gt9t22kveWz1enG9QrhVlagzwrxSC455xD1dHMKhIJkbsQ7d48BA==", + "dev": true, + "requires": { + "@types/fs-extra": "^8.0.1", + "colorette": "^1.1.0", + "fs-extra": "^8.1.0", + "globby": "10.0.1", + "is-plain-object": "^3.0.0" + } + }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -24421,6 +24808,12 @@ "totalist": "^3.0.0" } }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, "smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", diff --git a/package.json b/package.json index 28fdbea..1874541 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ "dev": "node --env-file=.env esbuild.config.mjs", "prebuild": "npm run bakei18n", "build": "node esbuild.config.mjs production", + "buildVite": "npx dotenv-cli -e .env -- vite build --mode production", + "buildViteOriginal": "npx dotenv-cli -e .env -- vite build --mode original", "buildDev": "node esbuild.config.mjs dev", "lint": "eslint src", "svelte-check": "svelte-check --tsconfig ./tsconfig.json", @@ -106,6 +108,7 @@ "pouchdb-replication": "^9.0.0", "pouchdb-utils": "^9.0.0", "prettier": "3.5.2", + "rollup-plugin-copy": "^3.5.0", "svelte": "5.41.1", "svelte-check": "^4.3.3", "svelte-preprocess": "^6.0.3", diff --git a/src/lib b/src/lib index 744a1b0..b59a1de 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 744a1b01ebb30acebd3d55a8a99ac3b61d228828 +Subproject commit b59a1deba8514ca2c67364569995da13f01be741 diff --git a/terser_vite.config.ts b/terser_vite.config.ts new file mode 100644 index 0000000..fce78f0 --- /dev/null +++ b/terser_vite.config.ts @@ -0,0 +1,49 @@ +import type { TerserOptions } from "vite"; + +export const terserOption: TerserOptions = { + mangle: { + // properties: { + // regex: /^_p_/, + // }, + eval: true, + keep_classnames: true, + keep_fnames: true, + // module: true, + // safari10: true, + // toplevel: true, + }, + // mangle: false, + compress: { + defaults: false, + arguments: true, + // drop_console: false, + ecma: 2020, + // keep_classnames: true, + // keep_fnames: false, + // module: true, + passes: 4, + // arrows: true, + // collapse_vars: true, + // comparisons: true, + // computed_props: true, + // conditionals: true, + dead_code: true, + evaluate: true, + // hoist_funs: true, + // hoist_props: true, + // hoist_vars: false, + // if_return: true, + inline: true, + // join_vars: true, + // reduce_funcs: true, + // reduce_vars: true, + // sequences: true, + // side_effects: false, + }, + format: { + // beautify: true, + ecma: 2020, + safari10: true, + webkit: true, + } +} \ No newline at end of file diff --git a/updates.md b/updates.md index 76d2a3e..f1ff090 100644 --- a/updates.md +++ b/updates.md @@ -3,17 +3,25 @@ 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.43-patched-7 + +-- Unreleased -- + +### Fixed + +- Fixed an issue where the StorageEventManager was not correctly the loading the settings. + ## 0.25.43-patched-6 18th February, 2026 Let me confess that I have lied about `now all ambiguous properties`... I have found some more implicit calling. -Note: I have not checked hidden file sync and customisation sync, yet. Please report if you find any unexpected behaviour on these features. +Note: I have not checked hidden file sync and customisation sync yet. Please report if you find any unexpected behaviour in these features. ### Fixed -- Now ReplicatorService responds to database reset and database initialisation events to dispose the active replicator. +- Now ReplicatorService responds to database reset and database initialisation events to dispose of the active replicator. - Fixes some unlocking issues during rebuilding. ### Refactored diff --git a/vite.config.ts b/vite.config.ts index 85ea529..79f9156 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,4 +1,164 @@ -import { defineConfig, mergeConfig } from "vitest/config"; -import viteConfig from "./vitest.config.common"; +import { defineConfig } from "vitest/config"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import { sveltePreprocess } from "svelte-preprocess"; +import inlineWorkerPlugin from "esbuild-plugin-inline-worker"; +import copy from "rollup-plugin-copy"; +import path from "path"; +import { fileURLToPath } from "node:url"; +import fs from "node:fs"; +import { platform } from "node:process"; -export default mergeConfig(viteConfig, defineConfig({})); +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const manifestJson = JSON.parse(fs.readFileSync("./manifest.json") + ""); +const packageJson = JSON.parse(fs.readFileSync("./package.json") + ""); +const updateInfo = JSON.stringify(fs.readFileSync("./updates.md") + ""); + +// const moduleAliasPlugin = { +// name: "module-alias", +// setup(build: any) { +// build.onResolve({ filter: /.(dev)(.ts|)$/ }, (args: any) => { +// // console.log(args.path); +// if (prod) { +// const prodTs = args.path.replace(".dev", ".prod"); +// const statFile = prodTs.endsWith(".ts") ? prodTs : prodTs + ".ts"; +// const realPath = path.join(args.resolveDir, statFile); +// console.log(`Checking ${statFile}`); +// if (fs.existsSync(realPath)) { +// console.log(`Replaced ${args.path} with ${prodTs}`); +// return { +// path: realPath, +// namespace: "file", +// }; +// } +// } +// return null; +// }); +// build.onResolve({ filter: /.(platform)(.ts|)$/ }, (args: any) => { +// // console.log(args.path); +// if (prod) { +// const prodTs = args.path.replace(".platform", ".obsidian"); +// const statFile = prodTs.endsWith(".ts") ? prodTs : prodTs + ".ts"; +// const realPath = path.join(args.resolveDir, statFile); +// console.log(`Checking ${statFile}`); +// if (fs.existsSync(realPath)) { +// console.log(`Replaced ${args.path} with ${prodTs}`); +// return { +// path: realPath, +// namespace: "file", +// }; +// } +// } +// return null; +// }); +// }, +// }; +const externals = [ + "obsidian", + "electron", + "crypto", + "@codemirror/autocomplete", + "@codemirror/collab", + "@codemirror/commands", + "@codemirror/language", + "@codemirror/lint", + "@codemirror/search", + "@codemirror/state", + "@codemirror/view", + "@lezer/common", + "@lezer/highlight", + "@lezer/lr", +]; +const define = { + MANIFEST_VERSION: `"${manifestJson.version}"`, + PACKAGE_VERSION: `"${packageJson.version}"`, + UPDATE_INFO: `${updateInfo}`, + global: "globalThis", + hostPlatform: `"${platform}"`, +}; +const PATHS_TEST_INSTALL = process.env?.PATHS_TEST_INSTALL || ""; +const PATH_TEST_INSTALL = PATHS_TEST_INSTALL.split(path.delimiter) + .map((p) => p.trim()) + .filter((p) => p.length); +if (PATH_TEST_INSTALL) { + console.log(`Built files will be copied to ${PATH_TEST_INSTALL}`); +} else { + console.log( + "Development build: You can install the plug-in to Obsidian for testing by exporting the PATHS_TEST_INSTALL environment variable with the paths to your vault plugins directories separated by your system path delimiter (':' on Unix, ';' on Windows)." + ); +} +import { terserOption } from "./terser_vite.config"; +export default defineConfig(({ mode }) => { + + const prod = mode === "production" || mode === "original"; + let minify = prod ? "terser" : false; + let outFile = `main_vite.${prod ? "prod" : "dev"}.js`; + if (mode == "original") { + console.log("Building original unminified version"); + minify = false; + outFile = `main_vite.original.js`; + } + outFile = `main.js`; + return { + plugins: [ + // moduleAliasPlugin, + inlineWorkerPlugin({ + external: externals, + treeShaking: true, + }), + svelte({ + preprocess: sveltePreprocess(), + compilerOptions: { css: "injected", preserveComments: false }, + }), + + copy({ + targets: ["manifest.json", "main.js", "styles.css"] + .map((file) => PATH_TEST_INSTALL.map((dest) => ({ src: file, dest: dest }))) + .flat(), + // Copy after the build is complete + hook: "writeBundle", + verbose: true, + }), + ], + + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + "@lib": path.resolve(__dirname, "./src/lib/src"), + src: path.resolve(__dirname, "./src"), + }, + }, + build: { + target: 'es2018', + commonjsOptions: {}, + lib: { + entry: path.resolve(__dirname, "src/main.ts"), + name: "main", + fileName: () => outFile, + formats: ["cjs"], // + }, + rollupOptions: { + external: externals, + output: { + globals: { + obsidian: "obsidian", + electron: "electron", + }, + entryFileNames: outFile, + inlineDynamicImports: true, + manualChunks: undefined, + }, + }, + minify: minify ? "terser" : false, + // minify:false, + terserOptions: terserOption, + outDir: ".", + emptyOutDir: false, + sourcemap: prod ? false : "hidden", + }, + define: define, + worker: { + format: "iife", + }, + } +}) From 203dd1742173cb3d24c19393b1b1497c2cd239f0 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 19 Feb 2026 10:23:45 +0000 Subject: [PATCH 015/339] for 0.25.43-patched-7, please refer to the updates.md --- manifest.json | 2 +- package-lock.json | 4 +- package.json | 2 +- src/apps/webpeer/src/P2PReplicatorShim.ts | 18 +- src/lib | 2 +- src/main.ts | 156 +++++--- src/managers/StorageEventManagerObsidian.ts | 6 +- src/modules/AbstractModule.ts | 2 +- src/modules/core/ModuleReplicator.ts | 8 +- src/modules/core/ReplicateResultProcessor.ts | 13 +- .../coreFeatures/ModuleConflictChecker.ts | 2 +- src/modules/coreFeatures/ModuleRedFlag.ts | 4 +- .../essentialObsidian/ModuleObsidianAPI.ts | 14 +- .../essentialObsidian/ModuleObsidianEvents.ts | 31 +- .../essentialObsidian/ModuleObsidianMenu.ts | 4 +- src/modules/features/ModuleLog.ts | 24 +- src/modules/features/ModuleObsidianSetting.ts | 356 ------------------ .../ModuleObsidianSettingAsMarkdown.ts | 4 +- .../ObsidianLiveSyncSettingTab.ts | 2 +- .../SettingDialogue/PaneSyncSettings.ts | 8 +- src/modules/main/ModuleLiveSyncMain.ts | 36 +- src/modules/services/ObsidianAPIService.ts | 20 +- src/modules/services/ObsidianServiceHub.ts | 25 +- src/modules/services/ObsidianServices.ts | 8 +- .../services/ObsidianSettingService.ts | 35 ++ src/modules/services/ObsidianUIService.ts | 11 +- src/serviceFeatures/onLayoutReady.ts | 3 + .../onLayoutReady/enablei18n.ts | 41 ++ src/serviceFeatures/types.ts | 50 +++ src/types.ts | 27 ++ test/harness/harness.ts | 12 +- updates.md | 21 +- 32 files changed, 426 insertions(+), 525 deletions(-) delete mode 100644 src/modules/features/ModuleObsidianSetting.ts create mode 100644 src/modules/services/ObsidianSettingService.ts create mode 100644 src/serviceFeatures/onLayoutReady.ts create mode 100644 src/serviceFeatures/onLayoutReady/enablei18n.ts create mode 100644 src/serviceFeatures/types.ts create mode 100644 src/types.ts diff --git a/manifest.json b/manifest.json index c803f98..e2df7e3 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.43-patched-6", + "version": "0.25.43-patched-7", "minAppVersion": "0.9.12", "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 ec37458..7f77fe8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-6", + "version": "0.25.43-patched-7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.43-patched-6", + "version": "0.25.43-patched-7", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 1874541..096b37b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-6", + "version": "0.25.43-patched-7", "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/apps/webpeer/src/P2PReplicatorShim.ts b/src/apps/webpeer/src/P2PReplicatorShim.ts index 004ef5e..3168558 100644 --- a/src/apps/webpeer/src/P2PReplicatorShim.ts +++ b/src/apps/webpeer/src/P2PReplicatorShim.ts @@ -34,9 +34,11 @@ import { TrysteroReplicator } from "@lib/replication/trystero/TrysteroReplicator import { SETTING_KEY_P2P_DEVICE_NAME } from "@lib/common/types"; import { ServiceContext } from "@lib/services/base/ServiceBase"; import type { InjectableServiceHub } from "@lib/services/InjectableServices"; -import { Menu } from "@/lib/src/services/implements/browser/Menu"; -import type { InjectableVaultServiceCompat } from "@/lib/src/services/implements/injectable/InjectableVaultService"; +import { Menu } from "@lib/services/implements/browser/Menu"; +import type { InjectableVaultServiceCompat } from "@lib/services/implements/injectable/InjectableVaultService"; import { SimpleStoreIDBv2 } from "octagonal-wheels/databases/SimpleStoreIDBv2"; +import type { InjectableAPIService } from "@/lib/src/services/implements/injectable/InjectableAPIService"; +import type { BrowserAPIService } from "@/lib/src/services/implements/browser/BrowserAPIService"; function addToList(item: string, list: string) { return unique( @@ -81,13 +83,13 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { constructor() { const browserServiceHub = new BrowserServiceHub(); this.services = browserServiceHub; - (this.services.vault as InjectableVaultServiceCompat).vaultName.setHandler( + + (this.services.API as BrowserAPIService).getSystemVaultName.setHandler( () => "p2p-livesync-web-peer" ); - - this.services.setting.currentSettings.setHandler(() => { - return this.settings as any; - }); + // this.services.setting.currentSettings.setHandler(() => { + // return this.settings as any; + // }); } async init() { // const { simpleStoreAPI } = await getWrappedSynchromesh(); @@ -106,7 +108,7 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { const repStore = SimpleStoreIDBv2.open("p2p-livesync-web-peer"); this._simpleStore = repStore; let _settings = (await repStore.get("settings")) || ({ ...P2P_DEFAULT_SETTINGS } as P2PSyncSetting); - + this.services.setting.settings = _settings as any; this.plugin = { saveSettings: async () => { await repStore.set("settings", _settings); diff --git a/src/lib b/src/lib index b59a1de..b2bb669 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit b59a1deba8514ca2c67364569995da13f01be741 +Subproject commit b2bb66970c8732892c238951dd3572ea849b3890 diff --git a/src/main.ts b/src/main.ts index 2769f8a..4ea2d17 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,7 +2,6 @@ import { Plugin, type App, type PluginManifest } from "./deps"; import { type EntryDoc, type ObsidianLiveSyncSettings, - type DatabaseConnectingStatus, type HasSettings, LOG_LEVEL_INFO, } from "./lib/src/common/types.ts"; @@ -12,7 +11,6 @@ import { type LiveSyncReplicatorEnv } from "./lib/src/replication/LiveSyncAbstra import { LiveSyncCommands } from "./features/LiveSyncCommands.ts"; import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts"; import { ConfigSync } from "./features/ConfigSync/CmdConfigSync.ts"; -import { reactiveSource, type ReactiveValue } from "octagonal-wheels/dataobject/reactive"; import { type LiveSyncJournalReplicatorEnv } from "./lib/src/replication/journal/LiveSyncJournalReplicator.js"; import { type LiveSyncCouchDBReplicatorEnv } from "./lib/src/replication/couchdb/LiveSyncReplicator.js"; import type { CheckPointInfo } from "./lib/src/replication/journal/JournalSyncTypes.js"; @@ -24,7 +22,6 @@ import { ModuleCheckRemoteSize } from "./modules/essentialObsidian/ModuleCheckRe import { ModuleConflictResolver } from "./modules/coreFeatures/ModuleConflictResolver.ts"; import { ModuleInteractiveConflictResolver } from "./modules/features/ModuleInteractiveConflictResolver.ts"; import { ModuleLog } from "./modules/features/ModuleLog.ts"; -import { ModuleObsidianSettings } from "./modules/features/ModuleObsidianSetting.ts"; import { ModuleRedFlag } from "./modules/coreFeatures/ModuleRedFlag.ts"; import { ModuleObsidianMenu } from "./modules/essentialObsidian/ModuleObsidianMenu.ts"; import { ModuleSetupObsidian } from "./modules/features/ModuleSetupObsidian.ts"; @@ -66,6 +63,8 @@ import { __$checkInstanceBinding } from "./lib/src/dev/checks.ts"; import { ServiceFileHandler } from "./serviceModules/FileHandler.ts"; import { FileAccessObsidian } from "./serviceModules/FileAccessObsidian.ts"; import { StorageEventManagerObsidian } from "./managers/StorageEventManagerObsidian.ts"; +import { onLayoutReadyFeatures } from "./serviceFeatures/onLayoutReady.ts"; +import type { ServiceModules } from "./types.ts"; export default class ObsidianLiveSyncPlugin extends Plugin @@ -88,12 +87,27 @@ export default class ObsidianLiveSyncPlugin return this._services; } - private initialiseServices() { - this._services = new ObsidianServiceHub(this); + /** + * Service Modules + */ + protected _serviceModules: ServiceModules; + + get serviceModules() { + return this._serviceModules; } + /** + * addOns: Non-essential and graphically features + */ addOns = [] as LiveSyncCommands[]; + /** + * The modules of the plug-in. Modules are responsible for specific features or functionalities of the plug-in, such as file handling, conflict resolution, replication, etc. + */ + private modules = [ + // Move to registerModules + ] as (IObsidianModule | AbstractModule)[]; + /** * register an add-onn to the plug-in. * Add-ons are features that are not essential to the core functionality of the plugin, @@ -122,13 +136,6 @@ export default class ObsidianLiveSyncPlugin return undefined; } - /** - * The modules of the plug-in. Modules are responsible for specific features or functionalities of the plug-in, such as file handling, conflict resolution, replication, etc. - */ - private modules = [ - // Move to registerModules - ] as (IObsidianModule | AbstractModule)[]; - /** * Get a module by its class. Throws an error if not found. * Mostly used for getting SetupManager. @@ -162,7 +169,6 @@ export default class ObsidianLiveSyncPlugin this._registerModule(new ModuleInitializerFile(this)); this._registerModule(new ModuleObsidianAPI(this, this)); this._registerModule(new ModuleObsidianEvents(this, this)); - this._registerModule(new ModuleObsidianSettings(this)); this._registerModule(new ModuleResolvingMismatchedTweaks(this)); this._registerModule(new ModuleObsidianSettingsAsMarkdown(this)); this._registerModule(new ModuleObsidianSettingDialogue(this, this)); @@ -206,9 +212,24 @@ export default class ObsidianLiveSyncPlugin return this.services.UI.confirm; } - // This property will be changed from outside often, so will be set later. - settings!: ObsidianLiveSyncSettings; + /** + * @obsolete Use services.setting.currentSettings instead. The current settings of the plug-in. + */ + get settings() { + return this.services.setting.settings; + } + /** + * @obsolete Use services.setting.settings instead. Set the settings of the plug-in. + */ + set settings(value: ObsidianLiveSyncSettings) { + this.services.setting.settings = value; + } + + /** + * @obsolete Use services.setting.currentSettings instead. Get the settings of the plug-in. + * @returns The current settings of the plug-in. + */ getSettings(): ObsidianLiveSyncSettings { return this.settings; } @@ -259,46 +280,63 @@ export default class ObsidianLiveSyncPlugin /// Modules which were relied on services /** * Storage Accessor for handling file operations. + * @obsolete Use serviceModules.storageAccess instead. */ - storageAccess: StorageAccess; + get storageAccess(): StorageAccess { + return this.serviceModules.storageAccess; + } /** * Database File Accessor for handling file operations related to the database, such as exporting the database, importing from a file, etc. + * @obsolete Use serviceModules.databaseFileAccess instead. */ - databaseFileAccess: DatabaseFileAccess; - + get databaseFileAccess(): DatabaseFileAccess { + return this.serviceModules.databaseFileAccess; + } /** * File Handler for handling file operations related to replication, such as resolving conflicts, applying changes from replication, etc. + * @obsolete Use serviceModules.fileHandler instead. */ - fileHandler: IFileHandler; + get fileHandler(): IFileHandler { + return this.serviceModules.fileHandler; + } /** * Rebuilder for handling database rebuilding operations. + * @obsolete Use serviceModules.rebuilder instead. */ - rebuilder: Rebuilder; + get rebuilder(): Rebuilder { + return this.serviceModules.rebuilder; + } - requestCount = reactiveSource(0); - responseCount = reactiveSource(0); - totalQueued = reactiveSource(0); - batched = reactiveSource(0); - processing = reactiveSource(0); - databaseQueueCount = reactiveSource(0); - storageApplyingCount = reactiveSource(0); - replicationResultCount = reactiveSource(0); - conflictProcessQueueCount = reactiveSource(0); - pendingFileEventCount = reactiveSource(0); - processingFileEventCount = reactiveSource(0); + // requestCount = reactiveSource(0); + // responseCount = reactiveSource(0); + // totalQueued = reactiveSource(0); + // batched = reactiveSource(0); + // processing = reactiveSource(0); + // databaseQueueCount = reactiveSource(0); + // storageApplyingCount = reactiveSource(0); + // replicationResultCount = reactiveSource(0); - _totalProcessingCount?: ReactiveValue; + // pendingFileEventCount = reactiveSource(0); + // processingFileEventCount = reactiveSource(0); - replicationStat = reactiveSource({ - sent: 0, - arrived: 0, - maxPullSeq: 0, - maxPushSeq: 0, - lastSyncPullSeq: 0, - lastSyncPushSeq: 0, - syncStatus: "CLOSED" as DatabaseConnectingStatus, - }); + // _totalProcessingCount?: ReactiveValue; + // replicationStat = reactiveSource({ + // sent: 0, + // arrived: 0, + // maxPullSeq: 0, + // maxPushSeq: 0, + // lastSyncPullSeq: 0, + // lastSyncPushSeq: 0, + // syncStatus: "CLOSED" as DatabaseConnectingStatus, + // }); + + private initialiseServices() { + this._services = new ObsidianServiceHub(this); + } + /** + * Initialise service modules. + */ private initialiseServiceModules() { const storageAccessManager = new StorageAccessManager(); // If we want to implement to the other platform, implement ObsidianXXXXXService. @@ -358,6 +396,7 @@ export default class ObsidianLiveSyncPlugin vault: this.services.vault, fileHandler: fileHandler, storageAccess: storageAccess, + control: this.services.control, }); return { rebuilder, @@ -367,34 +406,45 @@ export default class ObsidianLiveSyncPlugin }; } + /** + * @obsolete Use services.setting.saveSettingData instead. Save the settings to the disk. This is usually called after changing the settings in the code, to persist the changes. + */ + async saveSettings() { + await this.services.setting.saveSettingData(); + } + + /** + * Initialise ServiceFeatures. + * (Please refer `serviceFeatures` for more details) + */ + initialiseServiceFeatures() { + for (const feature of onLayoutReadyFeatures) { + const curriedFeature = () => feature(this); + this.services.appLifecycle.onLayoutReady.addHandler(curriedFeature); + } + } + constructor(app: App, manifest: PluginManifest) { super(app, manifest); this.initialiseServices(); this.registerModules(); this.registerAddOns(); - const instances = this.initialiseServiceModules(); - this.rebuilder = instances.rebuilder; - this.fileHandler = instances.fileHandler; - this.databaseFileAccess = instances.databaseFileAccess; - this.storageAccess = instances.storageAccess; + this._serviceModules = this.initialiseServiceModules(); + this.initialiseServiceFeatures(); this.bindModuleFunctions(); } private async _startUp() { - await this.services.appLifecycle.onLoad(); - const onReady = this.services.appLifecycle.onReady.bind(this.services.appLifecycle); + if (!(await this.services.control.onLoad())) return; + const onReady = this.services.control.onReady.bind(this.services.control); this.app.workspace.onLayoutReady(onReady); } onload() { void this._startUp(); } - async saveSettings() { - await this.services.setting.saveSettingData(); - } onunload() { - return void this.services.appLifecycle.onAppUnload(); + return void this.services.control.onUnload(); } - // <-- Plug-in's overrideable functions } // For now, diff --git a/src/managers/StorageEventManagerObsidian.ts b/src/managers/StorageEventManagerObsidian.ts index aaedacf..b48081b 100644 --- a/src/managers/StorageEventManagerObsidian.ts +++ b/src/managers/StorageEventManagerObsidian.ts @@ -203,8 +203,8 @@ export class StorageEventManagerObsidian extends StorageEventManagerBase { const totalItems = allItems.length + this.concurrentProcessing.waiting; const processing = this.processingCount; const batchedCount = this._waitingMap.size; - this.core.batched.value = batchedCount; - this.core.processing.value = processing; - this.core.totalQueued.value = totalItems + batchedCount + processing; + this.fileProcessing.batched.value = batchedCount; + this.fileProcessing.processing.value = processing; + this.fileProcessing.totalQueued.value = totalItems + batchedCount + processing; } } diff --git a/src/modules/AbstractModule.ts b/src/modules/AbstractModule.ts index d208bc7..0b4a2ba 100644 --- a/src/modules/AbstractModule.ts +++ b/src/modules/AbstractModule.ts @@ -2,7 +2,7 @@ import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/co import type { AnyEntry, FilePathWithPrefix } from "@lib/common/types"; import type { LiveSyncCore } from "@/main"; import { stripAllPrefixes } from "@lib/string_and_binary/path"; -import { createInstanceLogFunction } from "@/lib/src/services/lib/logUtils"; +import { createInstanceLogFunction } from "@lib/services/lib/logUtils"; export abstract class AbstractModule { _log = createInstanceLogFunction(this.constructor.name, this.services.API); diff --git a/src/modules/core/ModuleReplicator.ts b/src/modules/core/ModuleReplicator.ts index 6a6165f..84fb728 100644 --- a/src/modules/core/ModuleReplicator.ts +++ b/src/modules/core/ModuleReplicator.ts @@ -2,8 +2,8 @@ import { fireAndForget } from "octagonal-wheels/promises"; import { AbstractModule } from "../AbstractModule"; import { Logger, LOG_LEVEL_NOTICE, LOG_LEVEL_INFO, LEVEL_NOTICE, type LOG_LEVEL } from "octagonal-wheels/common/logger"; import { isLockAcquired, shareRunningResult, skipIfDuplicated } from "octagonal-wheels/concurrency/lock"; -import { balanceChunkPurgedDBs } from "@/lib/src/pouchdb/chunks"; -import { purgeUnreferencedChunks } from "@/lib/src/pouchdb/chunks"; +import { balanceChunkPurgedDBs } from "@lib/pouchdb/chunks"; +import { purgeUnreferencedChunks } from "@lib/pouchdb/chunks"; import { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator"; import { type EntryDoc, type RemoteType } from "../../lib/src/common/types"; import { rateLimitedSharedExecution, scheduleTask, updatePreviousExecutionTime } from "../../common/utils"; @@ -12,8 +12,8 @@ import { EVENT_FILE_SAVED, EVENT_SETTING_SAVED, eventHub } from "../../common/ev import { $msg } from "../../lib/src/common/i18n"; import type { LiveSyncCore } from "../../main"; import { ReplicateResultProcessor } from "./ReplicateResultProcessor"; -import { UnresolvedErrorManager } from "@/lib/src/services/base/UnresolvedErrorManager"; -import { clearHandlers } from "@/lib/src/replication/SyncParamsHandler"; +import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager"; +import { clearHandlers } from "@lib/replication/SyncParamsHandler"; const KEY_REPLICATION_ON_EVENT = "replicationOnEvent"; const REPLICATION_ON_EVENT_FORECASTED_TIME = 5000; diff --git a/src/modules/core/ReplicateResultProcessor.ts b/src/modules/core/ReplicateResultProcessor.ts index 6b77a88..1fe9a53 100644 --- a/src/modules/core/ReplicateResultProcessor.ts +++ b/src/modules/core/ReplicateResultProcessor.ts @@ -6,7 +6,7 @@ import { type EntryLeaf, type LoadedEntry, type MetaEntry, -} from "@/lib/src/common/types"; +} from "@lib/common/types"; import type { ModuleReplicator } from "./ModuleReplicator"; import { isChunk, isValidPath } from "@/common/utils"; import type { LiveSyncCore } from "@/main"; @@ -17,8 +17,8 @@ import { LOG_LEVEL_VERBOSE, Logger, type LOG_LEVEL, -} from "@/lib/src/common/logger"; -import { fireAndForget, isAnyNote, throttle } from "@/lib/src/common/utils"; +} from "@lib/common/logger"; +import { fireAndForget, isAnyNote, throttle } from "@lib/common/utils"; import { Semaphore } from "octagonal-wheels/concurrency/semaphore_v2"; import { serialized } from "octagonal-wheels/concurrency/lock"; import type { ReactiveSource } from "octagonal-wheels/dataobject/reactive_v2"; @@ -162,7 +162,8 @@ export class ReplicateResultProcessor { * Report the current status. */ protected reportStatus() { - this.core.replicationResultCount.value = this._queuedChanges.length + this._processingChanges.length; + this.services.replication.replicationResultCount.value = + this._queuedChanges.length + this._processingChanges.length; } /** @@ -381,7 +382,7 @@ export class ReplicateResultProcessor { releaser(); } } - }, this.replicator.core.databaseQueueCount); + }, this.services.replication.databaseQueueCount); } // Phase 2.1: process the document and apply to storage // This function is serialized per document to avoid race-condition for the same document. @@ -432,7 +433,7 @@ export class ReplicateResultProcessor { protected applyToStorage(entry: MetaEntry) { return this.withCounting(async () => { await this.services.replication.processSynchroniseResult(entry); - }, this.replicator.core.storageApplyingCount); + }, this.services.replication.storageApplyingCount); } /** diff --git a/src/modules/coreFeatures/ModuleConflictChecker.ts b/src/modules/coreFeatures/ModuleConflictChecker.ts index b004c0e..d3a53d5 100644 --- a/src/modules/coreFeatures/ModuleConflictChecker.ts +++ b/src/modules/coreFeatures/ModuleConflictChecker.ts @@ -71,7 +71,7 @@ export class ModuleConflictChecker extends AbstractModule { delay: 0, keepResultUntilDownstreamConnected: true, pipeTo: this.conflictResolveQueue, - totalRemainingReactiveSource: this.core.conflictProcessQueueCount, + totalRemainingReactiveSource: this.services.conflict.conflictProcessQueueCount, } ); onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { diff --git a/src/modules/coreFeatures/ModuleRedFlag.ts b/src/modules/coreFeatures/ModuleRedFlag.ts index 5ed6e1e..7ebdb0b 100644 --- a/src/modules/coreFeatures/ModuleRedFlag.ts +++ b/src/modules/coreFeatures/ModuleRedFlag.ts @@ -12,8 +12,8 @@ import type { LiveSyncCore } from "../../main.ts"; import FetchEverything from "../features/SetupWizard/dialogs/FetchEverything.svelte"; import RebuildEverything from "../features/SetupWizard/dialogs/RebuildEverything.svelte"; import { extractObject } from "octagonal-wheels/object"; -import { SvelteDialogManagerBase } from "@/lib/src/UI/svelteDialog.ts"; -import type { ServiceContext } from "@/lib/src/services/base/ServiceBase.ts"; +import { SvelteDialogManagerBase } from "@lib/UI/svelteDialog.ts"; +import type { ServiceContext } from "@lib/services/base/ServiceBase.ts"; export class ModuleRedFlag extends AbstractModule { async isFlagFileExist(path: string) { diff --git a/src/modules/essentialObsidian/ModuleObsidianAPI.ts b/src/modules/essentialObsidian/ModuleObsidianAPI.ts index 3396bb4..9de2ad3 100644 --- a/src/modules/essentialObsidian/ModuleObsidianAPI.ts +++ b/src/modules/essentialObsidian/ModuleObsidianAPI.ts @@ -10,9 +10,9 @@ import { import { Notice, requestUrl, type RequestUrlParam, type RequestUrlResponse } from "../../deps.ts"; import { type CouchDBCredentials, type EntryDoc } from "../../lib/src/common/types.ts"; import { isCloudantURI, isValidRemoteCouchDBURI } from "../../lib/src/pouchdb/utils_couchdb.ts"; -import { replicationFilter } from "@/lib/src/pouchdb/compress.ts"; -import { disableEncryption } from "@/lib/src/pouchdb/encryption.ts"; -import { enableEncryption } from "@/lib/src/pouchdb/encryption.ts"; +import { replicationFilter } from "@lib/pouchdb/compress.ts"; +import { disableEncryption } from "@lib/pouchdb/encryption.ts"; +import { enableEncryption } from "@lib/pouchdb/encryption.ts"; import { setNoticeClass } from "../../lib/src/mock_and_interop/wrapper.ts"; import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.ts"; import { AuthorizationHeaderGenerator } from "../../lib/src/replication/httplib.ts"; @@ -96,7 +96,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule { const size = body ? ` (${body.length})` : ""; try { const r = await this.__fetchByAPI(url, authHeader, opts); - this.plugin.requestCount.value = this.plugin.requestCount.value + 1; + this.services.API.requestCount.value = this.services.API.requestCount.value + 1; if (method == "POST" || method == "PUT") { this.last_successful_post = r.status - (r.status % 100) == 200; } else { @@ -113,7 +113,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule { this._log(ex); throw ex; } finally { - this.plugin.responseCount.value = this.plugin.responseCount.value + 1; + this.services.API.responseCount.value = this.services.API.responseCount.value + 1; } } @@ -171,7 +171,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule { headers.append("authorization", authHeader); } try { - this.plugin.requestCount.value = this.plugin.requestCount.value + 1; + this.services.API.requestCount.value = this.services.API.requestCount.value + 1; const response: Response = await (useRequestAPI ? this.__fetchByAPI(url.toString(), authHeader, { ...opts, headers }) : fetch(url, { ...opts, headers })); @@ -245,7 +245,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule { this._log(ex); throw ex; } finally { - this.plugin.responseCount.value = this.plugin.responseCount.value + 1; + this.services.API.responseCount.value = this.services.API.responseCount.value + 1; } // return await fetch(url, opts); diff --git a/src/modules/essentialObsidian/ModuleObsidianEvents.ts b/src/modules/essentialObsidian/ModuleObsidianEvents.ts index 7db93a2..63814ff 100644 --- a/src/modules/essentialObsidian/ModuleObsidianEvents.ts +++ b/src/modules/essentialObsidian/ModuleObsidianEvents.ts @@ -5,7 +5,7 @@ import { scheduleTask } from "octagonal-wheels/concurrency/task"; import { type TFile } from "../../deps.ts"; import { fireAndForget } from "octagonal-wheels/promises"; import { type FilePathWithPrefix } from "../../lib/src/common/types.ts"; -import { reactive, reactiveSource } from "octagonal-wheels/dataobject/reactive"; +import { reactive, reactiveSource, type ReactiveSource } from "octagonal-wheels/dataobject/reactive"; import { collectingChunks, pluginScanningCount, @@ -188,20 +188,25 @@ export class ModuleObsidianEvents extends AbstractObsidianModule { } }); } - // TODO: separate + + // Process counting for app reload scheduling + _totalProcessingCount?: ReactiveSource = undefined; private _scheduleAppReload() { - if (!this.core._totalProcessingCount) { + if (!this._totalProcessingCount) { const __tick = reactiveSource(0); - this.core._totalProcessingCount = reactive(() => { - const dbCount = this.core.databaseQueueCount.value; - const replicationCount = this.core.replicationResultCount.value; - const storageApplyingCount = this.core.storageApplyingCount.value; + this._totalProcessingCount = reactive(() => { + const dbCount = this.services.replication.databaseQueueCount.value; + const replicationCount = this.services.replication.replicationResultCount.value; + const storageApplyingCount = this.services.replication.storageApplyingCount.value; const chunkCount = collectingChunks.value; const pluginScanCount = pluginScanningCount.value; const hiddenFilesCount = hiddenFilesEventCount.value + hiddenFilesProcessingCount.value; - const conflictProcessCount = this.core.conflictProcessQueueCount.value; - const e = this.core.pendingFileEventCount.value; - const proc = this.core.processingFileEventCount.value; + const conflictProcessCount = this.services.conflict.conflictProcessQueueCount.value; + // Now no longer `pendingFileEventCount` and `processingFileEventCount` is used + // const e = this.core.pendingFileEventCount.value; + // const proc = this.core.processingFileEventCount.value; + const e = 0; + const proc = 0; // eslint-disable-next-line @typescript-eslint/no-unused-vars const __ = __tick.value; return ( @@ -223,7 +228,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule { ); let stableCheck = 3; - this.core._totalProcessingCount.onChanged((e) => { + this._totalProcessingCount.onChanged((e) => { if (e.value == 0) { if (stableCheck-- <= 0) { this.__performAppReload(); @@ -239,10 +244,14 @@ export class ModuleObsidianEvents extends AbstractObsidianModule { }); } } + _isReloadingScheduled(): boolean { + return this._totalProcessingCount !== undefined; + } onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.appLifecycle.onLayoutReady.addHandler(this._everyOnLayoutReady.bind(this)); services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); services.appLifecycle.askRestart.setHandler(this._askReload.bind(this)); services.appLifecycle.scheduleRestart.setHandler(this._scheduleAppReload.bind(this)); + services.appLifecycle.isReloadingScheduled.setHandler(this._isReloadingScheduled.bind(this)); } } diff --git a/src/modules/essentialObsidian/ModuleObsidianMenu.ts b/src/modules/essentialObsidian/ModuleObsidianMenu.ts index 0e63e9e..905e0bf 100644 --- a/src/modules/essentialObsidian/ModuleObsidianMenu.ts +++ b/src/modules/essentialObsidian/ModuleObsidianMenu.ts @@ -59,7 +59,7 @@ export class ModuleObsidianMenu extends AbstractModule { this.settings.liveSync = true; this._log("LiveSync Enabled.", LOG_LEVEL_NOTICE); } - await this.services.setting.realiseSetting(); + await this.services.control.applySettings(); await this.services.setting.saveSettingData(); }, }); @@ -74,7 +74,7 @@ export class ModuleObsidianMenu extends AbstractModule { this.services.appLifecycle.setSuspended(true); this._log("Self-hosted LiveSync suspended", LOG_LEVEL_NOTICE); } - await this.services.setting.realiseSetting(); + await this.services.control.applySettings(); await this.services.setting.saveSettingData(); }, }); diff --git a/src/modules/features/ModuleLog.ts b/src/modules/features/ModuleLog.ts index 6664ae0..e82cd1d 100644 --- a/src/modules/features/ModuleLog.ts +++ b/src/modules/features/ModuleLog.ts @@ -32,14 +32,14 @@ import { serialized } from "octagonal-wheels/concurrency/lock"; import { $msg } from "src/lib/src/common/i18n.ts"; import { P2PLogCollector } from "../../lib/src/replication/trystero/P2PReplicatorCore.ts"; import type { LiveSyncCore } from "../../main.ts"; -import { LiveSyncError } from "@/lib/src/common/LSError.ts"; +import { LiveSyncError } from "@lib/common/LSError.ts"; import { isValidPath } from "@/common/utils.ts"; import { isValidFilenameInAndroid, isValidFilenameInDarwin, isValidFilenameInWidows, -} from "@/lib/src/string_and_binary/path.ts"; -import { MARK_LOG_SEPARATOR } from "@/lib/src/services/lib/logUtils.ts"; +} from "@lib/string_and_binary/path.ts"; +import { MARK_LOG_SEPARATOR } from "@lib/services/lib/logUtils.ts"; // This module cannot be a core module because it depends on the Obsidian UI. @@ -102,12 +102,12 @@ export class ModuleLog extends AbstractObsidianModule { }); return computed(() => formatted.value); } - const labelReplication = padLeftSpComputed(this.core.replicationResultCount, `📥`); - const labelDBCount = padLeftSpComputed(this.core.databaseQueueCount, `📄`); - const labelStorageCount = padLeftSpComputed(this.core.storageApplyingCount, `💾`); + const labelReplication = padLeftSpComputed(this.services.replication.replicationResultCount, `📥`); + const labelDBCount = padLeftSpComputed(this.services.replication.databaseQueueCount, `📄`); + const labelStorageCount = padLeftSpComputed(this.services.replication.storageApplyingCount, `💾`); const labelChunkCount = padLeftSpComputed(collectingChunks, `🧩`); const labelPluginScanCount = padLeftSpComputed(pluginScanningCount, `🔌`); - const labelConflictProcessCount = padLeftSpComputed(this.core.conflictProcessQueueCount, `🔩`); + const labelConflictProcessCount = padLeftSpComputed(this.services.conflict.conflictProcessQueueCount, `🔩`); const hiddenFilesCount = reactive(() => hiddenFilesEventCount.value - hiddenFilesProcessingCount.value); const labelHiddenFilesCount = padLeftSpComputed(hiddenFilesCount, `⚙️`); const queueCountLabelX = reactive(() => { @@ -116,12 +116,12 @@ export class ModuleLog extends AbstractObsidianModule { const queueCountLabel = () => queueCountLabelX.value; const requestingStatLabel = computed(() => { - const diff = this.core.requestCount.value - this.core.responseCount.value; + const diff = this.services.API.requestCount.value - this.services.API.responseCount.value; return diff != 0 ? "📲 " : ""; }); const replicationStatLabel = computed(() => { - const e = this.core.replicationStat.value; + const e = this.services.replicator.replicationStatics.value; const sent = e.sent; const arrived = e.arrived; const maxPullSeq = e.maxPullSeq; @@ -173,9 +173,9 @@ export class ModuleLog extends AbstractObsidianModule { } return { w, sent, pushLast, arrived, pullLast }; }); - const labelProc = padLeftSpComputed(this.core.processing, `⏳`); - const labelPend = padLeftSpComputed(this.core.totalQueued, `🛫`); - const labelInBatchDelay = padLeftSpComputed(this.core.batched, `📬`); + const labelProc = padLeftSpComputed(this.services.fileProcessing.processing, `⏳`); + const labelPend = padLeftSpComputed(this.services.fileProcessing.totalQueued, `🛫`); + const labelInBatchDelay = padLeftSpComputed(this.services.fileProcessing.batched, `📬`); const waitingLabel = computed(() => { return `${labelProc()}${labelPend()}${labelInBatchDelay()}`; }); diff --git a/src/modules/features/ModuleObsidianSetting.ts b/src/modules/features/ModuleObsidianSetting.ts deleted file mode 100644 index ee27279..0000000 --- a/src/modules/features/ModuleObsidianSetting.ts +++ /dev/null @@ -1,356 +0,0 @@ -// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser"; -import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts"; -import { - type BucketSyncSetting, - ChunkAlgorithmNames, - type ConfigPassphraseStore, - type CouchDBConnection, - DEFAULT_SETTINGS, - type ObsidianLiveSyncSettings, - SALT_OF_PASSPHRASE, - SETTING_KEY_P2P_DEVICE_NAME, -} from "../../lib/src/common/types"; -import { LOG_LEVEL_NOTICE, LOG_LEVEL_URGENT } from "octagonal-wheels/common/logger"; -import { $msg, setLang } from "../../lib/src/common/i18n.ts"; -import { isCloudantURI } from "../../lib/src/pouchdb/utils_couchdb.ts"; -import { getLanguage } from "@/deps.ts"; -import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "../../lib/src/common/rosetta.ts"; -import { decryptString, encryptString } from "@/lib/src/encryption/stringEncryption.ts"; -import type { LiveSyncCore } from "../../main.ts"; -import { AbstractModule } from "../AbstractModule.ts"; -export class ModuleObsidianSettings extends AbstractModule { - async _everyOnLayoutReady(): Promise { - let isChanged = false; - if (this.settings.displayLanguage == "") { - const obsidianLanguage = getLanguage(); - if ( - SUPPORTED_I18N_LANGS.indexOf(obsidianLanguage) !== -1 && // Check if the language is supported - obsidianLanguage != this.settings.displayLanguage // Check if the language is different from the current setting - ) { - // Check if the current setting is not empty (Means migrated or installed). - this.settings.displayLanguage = obsidianLanguage as I18N_LANGS; - isChanged = true; - setLang(this.settings.displayLanguage); - } else if (this.settings.displayLanguage == "") { - this.settings.displayLanguage = "def"; - setLang(this.settings.displayLanguage); - await this.services.setting.saveSettingData(); - } - } - if (isChanged) { - const revert = $msg("dialog.yourLanguageAvailable.btnRevertToDefault"); - if ( - (await this.core.confirm.askSelectStringDialogue($msg(`dialog.yourLanguageAvailable`), ["OK", revert], { - defaultAction: "OK", - title: $msg(`dialog.yourLanguageAvailable.Title`), - })) == revert - ) { - this.settings.displayLanguage = "def"; - setLang(this.settings.displayLanguage); - } - await this.services.setting.saveSettingData(); - } - return true; - } - getPassphrase(settings: ObsidianLiveSyncSettings) { - const methods: Record Promise> = { - "": () => Promise.resolve("*"), - LOCALSTORAGE: () => Promise.resolve(localStorage.getItem("ls-setting-passphrase") ?? false), - ASK_AT_LAUNCH: () => this.core.confirm.askString("Passphrase", "passphrase", ""), - }; - const method = settings.configPassphraseStore; - const methodFunc = method in methods ? methods[method] : methods[""]; - return methodFunc(); - } - - _saveDeviceAndVaultName(): void { - const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.services.vault.getVaultName(); - localStorage.setItem(lsKey, this.services.setting.getDeviceAndVaultName() || ""); - } - - usedPassphrase = ""; - private _clearUsedPassphrase(): void { - this.usedPassphrase = ""; - } - - async decryptConfigurationItem(encrypted: string, passphrase: string) { - const dec = await decryptString(encrypted, passphrase + SALT_OF_PASSPHRASE); - if (dec) { - this.usedPassphrase = passphrase; - return dec; - } - return false; - } - - async encryptConfigurationItem(src: string, settings: ObsidianLiveSyncSettings) { - if (this.usedPassphrase != "") { - return await encryptString(src, this.usedPassphrase + SALT_OF_PASSPHRASE); - } - - const passphrase = await this.getPassphrase(settings); - if (passphrase === false) { - this._log( - "Failed to obtain passphrase when saving data.json! Please verify the configuration.", - LOG_LEVEL_URGENT - ); - return ""; - } - const dec = await encryptString(src, passphrase + SALT_OF_PASSPHRASE); - if (dec) { - this.usedPassphrase = passphrase; - return dec; - } - - return ""; - } - - get appId() { - return this.services.API.getAppID(); - } - - async _saveSettingData() { - this.services.setting.saveDeviceAndVaultName(); - const settings = { ...this.settings }; - settings.deviceAndVaultName = ""; - if (settings.P2P_DevicePeerName && settings.P2P_DevicePeerName.trim() !== "") { - console.log("Saving device peer name to small config"); - this.services.config.setSmallConfig(SETTING_KEY_P2P_DEVICE_NAME, settings.P2P_DevicePeerName.trim()); - settings.P2P_DevicePeerName = ""; - } - if (this.usedPassphrase == "" && !(await this.getPassphrase(settings))) { - this._log("Failed to retrieve passphrase. data.json contains unencrypted items!", LOG_LEVEL_NOTICE); - } else { - if ( - settings.couchDB_PASSWORD != "" || - settings.couchDB_URI != "" || - settings.couchDB_USER != "" || - settings.couchDB_DBNAME - ) { - const connectionSetting: CouchDBConnection & BucketSyncSetting = { - couchDB_DBNAME: settings.couchDB_DBNAME, - couchDB_PASSWORD: settings.couchDB_PASSWORD, - couchDB_URI: settings.couchDB_URI, - couchDB_USER: settings.couchDB_USER, - accessKey: settings.accessKey, - bucket: settings.bucket, - endpoint: settings.endpoint, - region: settings.region, - secretKey: settings.secretKey, - useCustomRequestHandler: settings.useCustomRequestHandler, - bucketCustomHeaders: settings.bucketCustomHeaders, - couchDB_CustomHeaders: settings.couchDB_CustomHeaders, - useJWT: settings.useJWT, - jwtKey: settings.jwtKey, - jwtAlgorithm: settings.jwtAlgorithm, - jwtKid: settings.jwtKid, - jwtExpDuration: settings.jwtExpDuration, - jwtSub: settings.jwtSub, - useRequestAPI: settings.useRequestAPI, - bucketPrefix: settings.bucketPrefix, - forcePathStyle: settings.forcePathStyle, - }; - settings.encryptedCouchDBConnection = await this.encryptConfigurationItem( - JSON.stringify(connectionSetting), - settings - ); - settings.couchDB_PASSWORD = ""; - settings.couchDB_DBNAME = ""; - settings.couchDB_URI = ""; - settings.couchDB_USER = ""; - settings.accessKey = ""; - settings.bucket = ""; - settings.region = ""; - settings.secretKey = ""; - settings.endpoint = ""; - } - if (settings.encrypt && settings.passphrase != "") { - settings.encryptedPassphrase = await this.encryptConfigurationItem(settings.passphrase, settings); - settings.passphrase = ""; - } - } - await this.core.saveData(settings); - eventHub.emitEvent(EVENT_SETTING_SAVED, settings); - } - - tryDecodeJson(encoded: string | false): object | false { - try { - if (!encoded) return false; - return JSON.parse(encoded); - } catch { - return false; - } - } - - async _decryptSettings(settings: ObsidianLiveSyncSettings): Promise { - const passphrase = await this.getPassphrase(settings); - if (passphrase === false) { - this._log("No passphrase found for data.json! Verify configuration before syncing.", LOG_LEVEL_URGENT); - } else { - if (settings.encryptedCouchDBConnection) { - const keys = [ - "couchDB_URI", - "couchDB_USER", - "couchDB_PASSWORD", - "couchDB_DBNAME", - "accessKey", - "bucket", - "endpoint", - "region", - "secretKey", - ] as (keyof CouchDBConnection | keyof BucketSyncSetting)[]; - const decrypted = this.tryDecodeJson( - await this.decryptConfigurationItem(settings.encryptedCouchDBConnection, passphrase) - ) as CouchDBConnection & BucketSyncSetting; - if (decrypted) { - for (const key of keys) { - if (key in decrypted) { - //@ts-ignore - settings[key] = decrypted[key]; - } - } - } else { - this._log( - "Failed to decrypt passphrase from data.json! Ensure configuration is correct before syncing with remote.", - LOG_LEVEL_URGENT - ); - for (const key of keys) { - //@ts-ignore - settings[key] = ""; - } - } - } - if (settings.encrypt && settings.encryptedPassphrase) { - const encrypted = settings.encryptedPassphrase; - const decrypted = await this.decryptConfigurationItem(encrypted, passphrase); - if (decrypted) { - settings.passphrase = decrypted; - } else { - this._log( - "Failed to decrypt passphrase from data.json! Ensure configuration is correct before syncing with remote.", - LOG_LEVEL_URGENT - ); - settings.passphrase = ""; - } - } - } - return settings; - } - - /** - * This method mutates the settings object. - * @param settings - * @returns - */ - _adjustSettings(settings: ObsidianLiveSyncSettings): Promise { - // Adjust settings as needed - - // Delete this feature to avoid problems on mobile. - settings.disableRequestURI = true; - - // GC is disabled. - settings.gcDelay = 0; - // So, use history is always enabled. - settings.useHistory = true; - - if ("workingEncrypt" in settings) delete settings.workingEncrypt; - if ("workingPassphrase" in settings) delete settings.workingPassphrase; - // Splitter configurations have been replaced with chunkSplitterVersion. - if (settings.chunkSplitterVersion == "") { - if (settings.enableChunkSplitterV2) { - if (settings.useSegmenter) { - settings.chunkSplitterVersion = "v2-segmenter"; - } else { - settings.chunkSplitterVersion = "v2"; - } - } else { - settings.chunkSplitterVersion = ""; - } - } else if (!(settings.chunkSplitterVersion in ChunkAlgorithmNames)) { - settings.chunkSplitterVersion = ""; - } - return Promise.resolve(settings); - } - - async _loadSettings(): Promise { - const settings = Object.assign({}, DEFAULT_SETTINGS, await this.core.loadData()) as ObsidianLiveSyncSettings; - - if (typeof settings.isConfigured == "undefined") { - // If migrated, mark true - if (JSON.stringify(settings) !== JSON.stringify(DEFAULT_SETTINGS)) { - settings.isConfigured = true; - } else { - settings.additionalSuffixOfDatabaseName = this.appId; - settings.isConfigured = false; - } - } - - this.settings = await this.services.setting.decryptSettings(settings); - - setLang(this.settings.displayLanguage); - - await this.services.setting.adjustSettings(this.settings); - - const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.services.vault.getVaultName(); - if (this.settings.deviceAndVaultName != "") { - if (!localStorage.getItem(lsKey)) { - this.services.setting.setDeviceAndVaultName(this.settings.deviceAndVaultName); - this.services.setting.saveDeviceAndVaultName(); - this.settings.deviceAndVaultName = ""; - } - } - if (isCloudantURI(this.settings.couchDB_URI) && this.settings.customChunkSize != 0) { - this._log( - "Configuration issues detected and automatically resolved. However, unsynchronized data may exist. Consider rebuilding if necessary.", - LOG_LEVEL_NOTICE - ); - this.settings.customChunkSize = 0; - } - this.services.setting.setDeviceAndVaultName(localStorage.getItem(lsKey) || ""); - if (this.services.setting.getDeviceAndVaultName() == "") { - if (this.settings.usePluginSync) { - this._log("Device name missing. Disabling plug-in sync.", LOG_LEVEL_NOTICE); - this.settings.usePluginSync = false; - } - } - - // this.core.ignoreFiles = this.settings.ignoreFiles.split(",").map(e => e.trim()); - eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB); - } - - private _currentSettings(): ObsidianLiveSyncSettings { - return this.settings; - } - private _updateSettings(updateFn: (settings: ObsidianLiveSyncSettings) => ObsidianLiveSyncSettings): Promise { - try { - const updated = updateFn(this.settings); - this.settings = updated; - } catch (ex) { - this._log("Error in update function: " + ex, LOG_LEVEL_URGENT); - return Promise.reject(ex); - } - return Promise.resolve(); - } - private _applyPartial(partial: Partial): Promise { - try { - this.settings = { ...this.settings, ...partial }; - } catch (ex) { - this._log("Error in applying partial settings: " + ex, LOG_LEVEL_URGENT); - return Promise.reject(ex); - } - return Promise.resolve(); - } - - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { - super.onBindFunction(core, services); - services.appLifecycle.onLayoutReady.addHandler(this._everyOnLayoutReady.bind(this)); - services.setting.clearUsedPassphrase.setHandler(this._clearUsedPassphrase.bind(this)); - services.setting.decryptSettings.setHandler(this._decryptSettings.bind(this)); - services.setting.adjustSettings.setHandler(this._adjustSettings.bind(this)); - services.setting.loadSettings.setHandler(this._loadSettings.bind(this)); - services.setting.currentSettings.setHandler(this._currentSettings.bind(this)); - services.setting.updateSettings.setHandler(this._updateSettings.bind(this)); - services.setting.applyPartial.setHandler(this._applyPartial.bind(this)); - services.setting.saveDeviceAndVaultName.setHandler(this._saveDeviceAndVaultName.bind(this)); - services.setting.saveSettingData.setHandler(this._saveSettingData.bind(this)); - } -} diff --git a/src/modules/features/ModuleObsidianSettingAsMarkdown.ts b/src/modules/features/ModuleObsidianSettingAsMarkdown.ts index 12dd74e..cf6bd90 100644 --- a/src/modules/features/ModuleObsidianSettingAsMarkdown.ts +++ b/src/modules/features/ModuleObsidianSettingAsMarkdown.ts @@ -6,8 +6,8 @@ import { DEFAULT_SETTINGS, type FilePathWithPrefix, type ObsidianLiveSyncSetting import { parseYaml, stringifyYaml } from "../../deps"; import { LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger"; import { AbstractModule } from "../AbstractModule.ts"; -import type { ServiceContext } from "@/lib/src/services/base/ServiceBase.ts"; -import type { InjectableServiceHub } from "@/lib/src/services/InjectableServices.ts"; +import type { ServiceContext } from "@lib/services/base/ServiceBase.ts"; +import type { InjectableServiceHub } from "@lib/services/InjectableServices.ts"; import type { LiveSyncCore } from "@/main.ts"; const SETTING_HEADER = "````yaml:livesync-setting\n"; const SETTING_FOOTER = "\n````"; diff --git a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts index f2ae1e4..ce9f71c 100644 --- a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts +++ b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts @@ -16,7 +16,7 @@ import { import { delay, isObjectDifferent, sizeToHumanReadable } from "../../../lib/src/common/utils.ts"; import { versionNumberString2Number } from "../../../lib/src/string_and_binary/convert.ts"; import { Logger } from "../../../lib/src/common/logger.ts"; -import { checkSyncInfo } from "@/lib/src/pouchdb/negotiation.ts"; +import { checkSyncInfo } from "@lib/pouchdb/negotiation.ts"; import { testCrypt } from "octagonal-wheels/encryption/encryption"; import ObsidianLiveSyncPlugin from "../../../main.ts"; import { scheduleTask } from "../../../common/utils.ts"; diff --git a/src/modules/features/SettingDialogue/PaneSyncSettings.ts b/src/modules/features/SettingDialogue/PaneSyncSettings.ts index c7e2da4..aba3296 100644 --- a/src/modules/features/SettingDialogue/PaneSyncSettings.ts +++ b/src/modules/features/SettingDialogue/PaneSyncSettings.ts @@ -105,7 +105,7 @@ export function paneSyncSettings( if (!this.editingSettings.isConfigured) { this.editingSettings.isConfigured = true; await this.saveAllDirtySettings(); - await this.services.setting.realiseSetting(); + await this.services.control.applySettings(); await this.rebuildDB("localOnly"); // this.resetEditingSettings(); if ( @@ -124,13 +124,13 @@ export function paneSyncSettings( await this.confirmRebuild(); } else { await this.saveAllDirtySettings(); - await this.services.setting.realiseSetting(); + await this.services.control.applySettings(); this.services.appLifecycle.askRestart(); } } } else { await this.saveAllDirtySettings(); - await this.services.setting.realiseSetting(); + await this.services.control.applySettings(); } }); }); @@ -169,7 +169,7 @@ export function paneSyncSettings( } await this.saveSettings(["liveSync", "periodicReplication"]); - await this.services.setting.realiseSetting(); + await this.services.control.applySettings(); }); new Setting(paneEl) diff --git a/src/modules/main/ModuleLiveSyncMain.ts b/src/modules/main/ModuleLiveSyncMain.ts index 34dc3f6..af645e7 100644 --- a/src/modules/main/ModuleLiveSyncMain.ts +++ b/src/modules/main/ModuleLiveSyncMain.ts @@ -49,7 +49,7 @@ export class ModuleLiveSyncMain extends AbstractModule { } if (!(await this.core.services.appLifecycle.onFirstInitialise())) return false; // await this.core.$$realizeSettingSyncMode(); - await this.services.setting.realiseSetting(); + await this.services.control.applySettings(); fireAndForget(async () => { this._log($msg("moduleLiveSyncMain.logAdditionalSafetyScan"), LOG_LEVEL_VERBOSE); if (!(await this.services.appLifecycle.onScanningStartupIssues())) { @@ -65,7 +65,7 @@ export class ModuleLiveSyncMain extends AbstractModule { eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => { fireAndForget(async () => { try { - await this.core.services.setting.realiseSetting(); + await this.core.services.control.applySettings(); const lang = this.core.services.setting.currentSettings()?.displayLanguage ?? undefined; if (lang !== undefined) { setLang(this.core.services.setting.currentSettings()?.displayLanguage); @@ -163,23 +163,19 @@ export class ModuleLiveSyncMain extends AbstractModule { return; } - private async _realizeSettingSyncMode(): Promise { - await this.services.appLifecycle.onSuspending(); - await this.services.setting.onBeforeRealiseSetting(); - this.localDatabase.refreshSettings(); - await this.services.fileProcessing.commitPendingFileEvents(); - await this.services.setting.onRealiseSetting(); - // disable all sync temporary. - if (this.services.appLifecycle.isSuspended()) return; - await this.services.appLifecycle.onResuming(); - await this.services.appLifecycle.onResumed(); - await this.services.setting.onSettingRealised(); - return; - } - - _isReloadingScheduled(): boolean { - return this.core._totalProcessingCount !== undefined; - } + // private async _realizeSettingSyncMode(): Promise { + // await this.services.appLifecycle.onSuspending(); + // await this.services.setting.onBeforeRealiseSetting(); + // this.localDatabase.refreshSettings(); + // await this.services.fileProcessing.commitPendingFileEvents(); + // await this.services.setting.onRealiseSetting(); + // // disable all sync temporary. + // if (this.services.appLifecycle.isSuspended()) return; + // await this.services.appLifecycle.onResuming(); + // await this.services.appLifecycle.onResumed(); + // await this.services.setting.onSettingRealised(); + // return; + // } isReady = false; @@ -217,11 +213,9 @@ export class ModuleLiveSyncMain extends AbstractModule { services.appLifecycle.markIsReady.setHandler(this._markIsReady.bind(this)); services.appLifecycle.resetIsReady.setHandler(this._resetIsReady.bind(this)); services.appLifecycle.hasUnloaded.setHandler(this._isUnloaded.bind(this)); - services.appLifecycle.isReloadingScheduled.setHandler(this._isReloadingScheduled.bind(this)); services.appLifecycle.onReady.addHandler(this._onLiveSyncReady.bind(this)); services.appLifecycle.onWireUpEvents.addHandler(this._wireUpEvents.bind(this)); services.appLifecycle.onLoad.addHandler(this._onLiveSyncLoad.bind(this)); services.appLifecycle.onAppUnload.addHandler(this._onLiveSyncUnload.bind(this)); - services.setting.realiseSetting.setHandler(this._realizeSettingSyncMode.bind(this)); } } diff --git a/src/modules/services/ObsidianAPIService.ts b/src/modules/services/ObsidianAPIService.ts index b771c29..a1e3f1f 100644 --- a/src/modules/services/ObsidianAPIService.ts +++ b/src/modules/services/ObsidianAPIService.ts @@ -1,13 +1,20 @@ -import { InjectableAPIService } from "@/lib/src/services/implements/injectable/InjectableAPIService"; -import type { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext"; +import { InjectableAPIService } from "@lib/services/implements/injectable/InjectableAPIService"; +import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext"; import { Platform, type Command, type ViewCreator } from "obsidian"; import { ObsHttpHandler } from "../essentialObsidian/APILib/ObsHttpHandler"; +import { ObsidianConfirm } from "./ObsidianConfirm"; +import type { Confirm } from "@lib/interfaces/Confirm"; // All Services will be migrated to be based on Plain Services, not Injectable Services. // This is a migration step. export class ObsidianAPIService extends InjectableAPIService { _customHandler: ObsHttpHandler | undefined; + _confirmInstance: Confirm; + constructor(context: ObsidianServiceContext) { + super(context); + this._confirmInstance = new ObsidianConfirm(context); + } getCustomFetchHandler(): ObsHttpHandler { if (!this._customHandler) this._customHandler = new ObsHttpHandler(undefined, undefined); return this._customHandler; @@ -63,6 +70,11 @@ export class ObsidianAPIService extends InjectableAPIService(command: TCommand): TCommand { return this.context.plugin.addCommand(command) as TCommand; } diff --git a/src/modules/services/ObsidianServiceHub.ts b/src/modules/services/ObsidianServiceHub.ts index 8e8969a..8df8284 100644 --- a/src/modules/services/ObsidianServiceHub.ts +++ b/src/modules/services/ObsidianServiceHub.ts @@ -1,6 +1,6 @@ -import { InjectableServiceHub } from "@/lib/src/services/implements/injectable/InjectableServiceHub"; -import { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext"; -import type { ServiceInstances } from "@/lib/src/services/ServiceHub"; +import { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub"; +import { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext"; +import type { ServiceInstances } from "@lib/services/ServiceHub"; import type ObsidianLiveSyncPlugin from "@/main"; import { ObsidianConflictService, @@ -8,13 +8,14 @@ import { ObsidianReplicationService, ObsidianReplicatorService, ObsidianRemoteService, - ObsidianSettingService, ObsidianTweakValueService, ObsidianTestService, ObsidianDatabaseEventService, ObsidianConfigService, ObsidianKeyValueDBService, + ObsidianControlService, } from "./ObsidianServices"; +import { ObsidianSettingService } from "./ObsidianSettingService"; import { ObsidianDatabaseService } from "./ObsidianDatabaseService"; import { ObsidianAPIService } from "./ObsidianAPIService"; import { ObsidianAppLifecycleService } from "./ObsidianAppLifecycleService"; @@ -35,10 +36,14 @@ export class ObsidianServiceHub extends InjectableServiceHub>; super(context, serviceInstancesToInit); diff --git a/src/modules/services/ObsidianServices.ts b/src/modules/services/ObsidianServices.ts index 9ac3129..c6812b9 100644 --- a/src/modules/services/ObsidianServices.ts +++ b/src/modules/services/ObsidianServices.ts @@ -4,12 +4,12 @@ import { InjectableFileProcessingService } from "@lib/services/implements/inject import { InjectableRemoteService } from "@lib/services/implements/injectable/InjectableRemoteService"; import { InjectableReplicationService } from "@lib/services/implements/injectable/InjectableReplicationService"; import { InjectableReplicatorService } from "@lib/services/implements/injectable/InjectableReplicatorService"; -import { InjectableSettingService } from "@lib/services/implements/injectable/InjectableSettingService"; import { InjectableTestService } from "@lib/services/implements/injectable/InjectableTestService"; import { InjectableTweakValueService } from "@lib/services/implements/injectable/InjectableTweakValueService"; import { ConfigServiceBrowserCompat } from "@lib/services/implements/browser/ConfigServiceBrowserCompat"; import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext.ts"; -import { KeyValueDBService } from "@/lib/src/services/base/KeyValueDBService"; +import { KeyValueDBService } from "@lib/services/base/KeyValueDBService"; +import { ControlService } from "@lib/services/base/ControlService"; export class ObsidianDatabaseEventService extends InjectableDatabaseEventService {} @@ -23,8 +23,6 @@ export class ObsidianReplicationService extends InjectableReplicationService {} // InjectableConflictService export class ObsidianConflictService extends InjectableConflictService {} -// InjectableSettingService -export class ObsidianSettingService extends InjectableSettingService {} // InjectableTweakValueService export class ObsidianTweakValueService extends InjectableTweakValueService {} // InjectableTestService @@ -32,3 +30,5 @@ export class ObsidianTestService extends InjectableTestService {} export class ObsidianKeyValueDBService extends KeyValueDBService {} + +export class ObsidianControlService extends ControlService {} diff --git a/src/modules/services/ObsidianSettingService.ts b/src/modules/services/ObsidianSettingService.ts new file mode 100644 index 0000000..a9dfbb4 --- /dev/null +++ b/src/modules/services/ObsidianSettingService.ts @@ -0,0 +1,35 @@ +import { type ObsidianLiveSyncSettings } from "@lib/common/types"; +import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED } from "@lib/events/coreEvents"; +import { eventHub } from "@lib/hub/hub"; +import { SettingService, type SettingServiceDependencies } from "@lib/services/base/SettingService"; +import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext"; + +export class ObsidianSettingService extends SettingService { + constructor(context: T, dependencies: SettingServiceDependencies) { + super(context, dependencies); + this.onSettingSaved.addHandler((settings) => { + eventHub.emitEvent(EVENT_SETTING_SAVED, settings); + return Promise.resolve(true); + }); + this.onSettingLoaded.addHandler((settings) => { + eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB); + return Promise.resolve(true); + }); + } + protected setItem(key: string, value: string) { + return localStorage.setItem(key, value); + } + protected getItem(key: string): string { + return localStorage.getItem(key) ?? ""; + } + protected deleteItem(key: string): void { + localStorage.removeItem(key); + } + + protected override async saveData(data: ObsidianLiveSyncSettings): Promise { + return await this.context.liveSyncPlugin.saveData(data); + } + protected override async loadData(): Promise { + return await this.context.liveSyncPlugin.loadData(); + } +} diff --git a/src/modules/services/ObsidianUIService.ts b/src/modules/services/ObsidianUIService.ts index 37b7230..d053f25 100644 --- a/src/modules/services/ObsidianUIService.ts +++ b/src/modules/services/ObsidianUIService.ts @@ -2,14 +2,15 @@ import type { ConfigService } from "@lib/services/base/ConfigService"; import type { AppLifecycleService } from "@lib/services/base/AppLifecycleService"; import type { ReplicatorService } from "@lib/services/base/ReplicatorService"; import { UIService } from "@lib/services//implements/base/UIService"; -import { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext"; +import { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext"; import { ObsidianSvelteDialogManager } from "./SvelteDialogObsidian"; -import { ObsidianConfirm } from "./ObsidianConfirm"; -import DialogToCopy from "@/lib/src/UI/dialogues/DialogueToCopy.svelte"; +import DialogToCopy from "@lib/UI/dialogues/DialogueToCopy.svelte"; +import type { IAPIService } from "@lib/services/base/IService"; export type ObsidianUIServiceDependencies = { appLifecycle: AppLifecycleService; config: ConfigService; replicator: ReplicatorService; + APIService: IAPIService; }; export class ObsidianUIService extends UIService { @@ -17,7 +18,7 @@ export class ObsidianUIService extends UIService { return DialogToCopy; } constructor(context: ObsidianServiceContext, dependents: ObsidianUIServiceDependencies) { - const obsidianConfirm = new ObsidianConfirm(context); + const obsidianConfirm = dependents.APIService.confirm; const obsidianSvelteDialogManager = new ObsidianSvelteDialogManager(context, { appLifecycle: dependents.appLifecycle, config: dependents.config, @@ -27,7 +28,7 @@ export class ObsidianUIService extends UIService { super(context, { appLifecycle: dependents.appLifecycle, dialogManager: obsidianSvelteDialogManager, - confirm: obsidianConfirm, + APIService: dependents.APIService, }); } } diff --git a/src/serviceFeatures/onLayoutReady.ts b/src/serviceFeatures/onLayoutReady.ts new file mode 100644 index 0000000..3ddfa0e --- /dev/null +++ b/src/serviceFeatures/onLayoutReady.ts @@ -0,0 +1,3 @@ +import { enableI18nFeature } from "./onLayoutReady/enablei18n"; + +export const onLayoutReadyFeatures = [enableI18nFeature]; diff --git a/src/serviceFeatures/onLayoutReady/enablei18n.ts b/src/serviceFeatures/onLayoutReady/enablei18n.ts new file mode 100644 index 0000000..d908597 --- /dev/null +++ b/src/serviceFeatures/onLayoutReady/enablei18n.ts @@ -0,0 +1,41 @@ +import { getLanguage } from "@/deps"; +import { createServiceFeature } from "../types.ts"; +import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "@lib/common/rosetta"; +import { $msg, setLang } from "@lib/common/i18n"; + +export const enableI18nFeature = createServiceFeature(async ({ services: { setting, API } }) => { + let isChanged = false; + const settings = setting.currentSettings(); + if (settings.displayLanguage == "") { + const obsidianLanguage = getLanguage(); + 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 + ) { + // Check if the current setting is not empty (Means migrated or installed). + // settings.displayLanguage = obsidianLanguage as I18N_LANGS; + await setting.applyPartial({ displayLanguage: obsidianLanguage as I18N_LANGS }); + isChanged = true; + setLang(settings.displayLanguage); + } else if (settings.displayLanguage == "") { + // settings.displayLanguage = "def"; + await setting.applyPartial({ displayLanguage: "def" }); + setLang(settings.displayLanguage); + await setting.saveSettingData(); + } + } + if (isChanged) { + const revert = $msg("dialog.yourLanguageAvailable.btnRevertToDefault"); + if ( + (await API.confirm.askSelectStringDialogue($msg(`dialog.yourLanguageAvailable`), ["OK", revert], { + defaultAction: "OK", + title: $msg(`dialog.yourLanguageAvailable.Title`), + })) == revert + ) { + await setting.applyPartial({ displayLanguage: "def" }); + setLang(settings.displayLanguage); + } + await setting.saveSettingData(); + } + return true; +}); diff --git a/src/serviceFeatures/types.ts b/src/serviceFeatures/types.ts new file mode 100644 index 0000000..bc18fd2 --- /dev/null +++ b/src/serviceFeatures/types.ts @@ -0,0 +1,50 @@ +import type { IServiceHub } from "@lib/services/base/IService"; +import type { DatabaseFileAccess } from "@lib/interfaces/DatabaseFileAccess"; +import type { Rebuilder } from "@lib/interfaces/DatabaseRebuilder"; +import type { IFileHandler } from "@lib/interfaces/FileHandler"; +import type { StorageAccess } from "@lib/interfaces/StorageAccess"; + +export interface ServiceModules { + storageAccess: StorageAccess; + /** + * Database File Accessor for handling file operations related to the database, such as exporting the database, importing from a file, etc. + */ + databaseFileAccess: DatabaseFileAccess; + + /** + * File Handler for handling file operations related to replication, such as resolving conflicts, applying changes from replication, etc. + */ + fileHandler: IFileHandler; + /** + * Rebuilder for handling database rebuilding operations. + */ + rebuilder: Rebuilder; +} +export type RequiredServices = Pick; +export type RequiredServiceModules = Pick; + +export type NecessaryServices = { + services: RequiredServices; + serviceModules: RequiredServiceModules; +}; + +export type ServiceFeatureFunction = ( + host: NecessaryServices +) => TR; + +/** + * Helper function to create a service feature with proper typing. + * @param featureFunction The feature function to be wrapped. + * @returns The same feature function with proper typing. + * @example + * const myFeatureDef = createServiceFeature(({ services: { API }, serviceModules: { storageAccess } }) => { + * // ... + * }); + * const myFeature = myFeatureDef.bind(null, this); // <- `this` may `ObsidianLiveSyncPlugin` or a custom context object + * appLifecycle.onLayoutReady(myFeature); + */ +export function createServiceFeature( + featureFunction: ServiceFeatureFunction +): ServiceFeatureFunction { + return featureFunction; +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..22ece98 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,27 @@ +import type { DatabaseFileAccess } from "@/lib/src/interfaces/DatabaseFileAccess"; +import type { Rebuilder } from "@/lib/src/interfaces/DatabaseRebuilder"; +import type { IFileHandler } from "@/lib/src/interfaces/FileHandler"; +import type { StorageAccess } from "@/lib/src/interfaces/StorageAccess"; +import type { IServiceHub } from "./lib/src/services/base/IService"; + +export interface ServiceModules { + storageAccess: StorageAccess; + /** + * Database File Accessor for handling file operations related to the database, such as exporting the database, importing from a file, etc. + */ + databaseFileAccess: DatabaseFileAccess; + + /** + * File Handler for handling file operations related to replication, such as resolving conflicts, applying changes from replication, etc. + */ + fileHandler: IFileHandler; + /** + * Rebuilder for handling database rebuilding operations. + */ + rebuilder: Rebuilder; +} + +export interface LiveSyncHost { + services: IServiceHub; + serviceModules: ServiceModules; +} diff --git a/test/harness/harness.ts b/test/harness/harness.ts index 390934f..42f3f3e 100644 --- a/test/harness/harness.ts +++ b/test/harness/harness.ts @@ -122,13 +122,11 @@ export async function waitForIdle(harness: LiveSyncHarness): Promise { for (let i = 0; i < 20; i++) { await delay(25); const processing = - harness.plugin.databaseQueueCount.value + - harness.plugin.processingFileEventCount.value + - harness.plugin.pendingFileEventCount.value + - harness.plugin.totalQueued.value + - harness.plugin.batched.value + - harness.plugin.processing.value + - harness.plugin.storageApplyingCount.value; + harness.plugin.services.replication.databaseQueueCount.value + + harness.plugin.services.fileProcessing.totalQueued.value + + harness.plugin.services.fileProcessing.batched.value + + harness.plugin.services.fileProcessing.processing.value + + harness.plugin.services.replication.storageApplyingCount.value; if (processing === 0) { if (i > 0) { diff --git a/updates.md b/updates.md index f1ff090..52e5783 100644 --- a/updates.md +++ b/updates.md @@ -5,11 +5,28 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid ## 0.25.43-patched-7 --- Unreleased -- +19th February, 2026 + +Right then, let us make a decision already. + +Last time, since I found a bug, I ended up doing a few other things as well, but next time I intend to release it with just the bug fix. It is quite substantial, after all. + +Customisation Sync has mostly been verified. Hidden file synchronisation has not been done yet. ### Fixed -- Fixed an issue where the StorageEventManager was not correctly the loading the settings. +- Fixed an issue where the StorageEventManager was not correctly loading the settings. +- Replication statistics are now correctly reset after switching replicators. + +### Refactored + +- Now, many reactive values which keep the state or statistics of the plugin are moved to the services which have the responsibility for these states. +- `serviceFeatures` are now able to be added to the services; this is not a class module, but a function which accepts dependencies and returns an addHandler-able function. This is for better separation of concerns, better maintainability, and testability. +- `control` service; is a meta-service which is responsible for orchestrating services has been added. + - Don't you think stopping replication or something occurs during `settingService.realiseSetting` is quite weird? It may be done by the control service, which can orchestrate the setting service and the replicator service. + - +- Some functions on services have been moved. e.g., `getSystemVaultName` is now on the API service. +- Setting Service is now responsible for the setting, no longer using dynamic binding for the modules. ## 0.25.43-patched-6 From e0e72fae723bbf9d1f50003779eded49e1811075 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 19 Feb 2026 10:37:19 +0000 Subject: [PATCH 016/339] Fixed: saving device name --- src/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib b/src/lib index b2bb669..820a266 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit b2bb66970c8732892c238951dd3572ea849b3890 +Subproject commit 820a26641a92242ae2939467ee9cf857afee3ed2 From 32b6717114a4e4d156ea1e4a52193606fc91cd10 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 19 Feb 2026 10:38:42 +0000 Subject: [PATCH 017/339] keep a note --- updates.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/updates.md b/updates.md index 52e5783..78e7dc2 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,12 @@ 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. +## --next -- + +### Fixed + +- Now device name is saved correctly. + ## 0.25.43-patched-7 19th February, 2026 @@ -13,6 +19,10 @@ Last time, since I found a bug, I ended up doing a few other things as well, but Customisation Sync has mostly been verified. Hidden file synchronisation has not been done yet. +Vite's build system is not in the production. However, I possibly migrate to it in the future. + +And, the `daily-progress` will be tidied on releasing 0.25.44. Do not worry! + ### Fixed - Fixed an issue where the StorageEventManager was not correctly loading the settings. From 556ce471f8bbb21559d276ba602499cd9a385141 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Fri, 20 Feb 2026 14:28:28 +0000 Subject: [PATCH 018/339] ## 0.25.43-patched-8 --- manifest.json | 2 +- package-lock.json | 4 +- package.json | 2 +- src/apps/webpeer/src/P2PReplicatorShim.ts | 7 +- src/common/SvelteItemView.ts | 4 +- src/common/utils.ts | 47 +-- src/features/ConfigSync/CmdConfigSync.ts | 2 +- src/features/ConfigSync/PluginDialogModal.ts | 4 +- .../HiddenFileCommon/JsonResolveModal.ts | 4 +- .../HiddenFileSync/CmdHiddenFileSync.ts | 2 +- src/features/P2PSync/CmdP2PReplicator.ts | 5 +- .../P2PReplicator/P2PReplicatorPaneView.ts | 6 +- src/lib | 2 +- src/main.ts | 7 +- src/modules/AbstractObsidianModule.ts | 2 +- src/modules/core/ModulePeriodicProcess.ts | 2 +- src/modules/core/ModuleReplicator.ts | 314 ++++++++---------- src/modules/core/ModuleReplicatorCouchDB.ts | 2 +- src/modules/core/ModuleReplicatorMinIO.ts | 2 +- src/modules/core/ModuleReplicatorP2P.ts | 2 +- src/modules/core/ModuleTargetFilter.ts | 2 +- .../coreFeatures/ModuleConflictChecker.ts | 2 +- .../coreFeatures/ModuleConflictResolver.ts | 2 +- src/modules/coreFeatures/ModuleRedFlag.ts | 2 +- .../coreFeatures/ModuleRemoteGovernor.ts | 22 -- .../ModuleResolveMismatchedTweaks.ts | 2 +- src/modules/coreObsidian/UILib/dialogs.ts | 16 +- .../essential/ModuleInitializerFile.ts | 2 +- src/modules/essential/ModuleMigration.ts | 2 +- .../APILib/ObsHttpHandler.ts | 5 +- .../ModuleCheckRemoteSize.ts | 2 +- .../essentialObsidian/ModuleObsidianAPI.ts | 2 +- .../essentialObsidian/ModuleObsidianEvents.ts | 4 +- .../essentialObsidian/ModuleObsidianMenu.ts | 2 +- src/modules/extras/ModuleDev.ts | 2 +- src/modules/extras/ModuleIntegratedTest.ts | 2 +- src/modules/extras/ModuleReplicateTest.ts | 2 +- src/modules/extras/devUtil/TestPaneView.ts | 10 +- .../DocumentHistory/DocumentHistoryModal.ts | 4 +- .../GlobalHistory/GlobalHistoryView.ts | 6 +- .../ConflictResolveModal.ts | 4 +- src/modules/features/Log/LogPaneView.ts | 6 +- src/modules/features/ModuleGlobalHistory.ts | 2 +- .../ModuleInteractiveConflictResolver.ts | 2 +- src/modules/features/ModuleLog.ts | 2 +- .../features/ModuleObsidianDocumentHistory.ts | 2 +- .../ModuleObsidianSettingAsMarkdown.ts | 2 +- .../features/ModuleObsidianSettingTab.ts | 2 +- src/modules/features/ModuleSetupObsidian.ts | 2 +- .../SettingDialogue/LiveSyncSetting.ts | 4 +- .../ObsidianLiveSyncSettingTab.ts | 2 +- .../SettingDialogue/PaneMaintenance.ts | 6 +- src/modules/main/ModuleLiveSyncMain.ts | 107 +++--- src/modules/services/ObsidianAPIService.ts | 2 +- src/modules/services/ObsidianServiceHub.ts | 26 +- src/modules/services/ObsidianUIService.ts | 4 +- src/modules/services/ObsidianVaultService.ts | 2 +- src/serviceFeatures/types.ts | 28 ++ src/serviceModules/FileAccessObsidian.ts | 6 +- test/harness/harness.ts | 4 +- tsconfig.json | 1 + updates.md | 16 +- 62 files changed, 355 insertions(+), 392 deletions(-) delete mode 100644 src/modules/coreFeatures/ModuleRemoteGovernor.ts diff --git a/manifest.json b/manifest.json index e2df7e3..9f9365d 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.43-patched-7", + "version": "0.25.43-patched-8", "minAppVersion": "0.9.12", "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 7f77fe8..9137591 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-7", + "version": "0.25.43-patched-8", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.43-patched-7", + "version": "0.25.43-patched-8", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 096b37b..d3858a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-7", + "version": "0.25.43-patched-8", "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/apps/webpeer/src/P2PReplicatorShim.ts b/src/apps/webpeer/src/P2PReplicatorShim.ts index 3168558..73fe39a 100644 --- a/src/apps/webpeer/src/P2PReplicatorShim.ts +++ b/src/apps/webpeer/src/P2PReplicatorShim.ts @@ -87,9 +87,6 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { (this.services.API as BrowserAPIService).getSystemVaultName.setHandler( () => "p2p-livesync-web-peer" ); - // this.services.setting.currentSettings.setHandler(() => { - // return this.settings as any; - // }); } async init() { // const { simpleStoreAPI } = await getWrappedSynchromesh(); @@ -150,9 +147,9 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { simpleStore(): SimpleStore { return this._simpleStore; } - handleReplicatedDocuments(docs: EntryDoc[]): Promise { + handleReplicatedDocuments(docs: EntryDoc[]): Promise { // No op. This is a client and does not need to process the docs - return Promise.resolve(); + return Promise.resolve(true); } getPluginShim() { diff --git a/src/common/SvelteItemView.ts b/src/common/SvelteItemView.ts index 3b8f788..fb421e4 100644 --- a/src/common/SvelteItemView.ts +++ b/src/common/SvelteItemView.ts @@ -4,7 +4,7 @@ import { type mount, unmount } from "svelte"; export abstract class SvelteItemView extends ItemView { abstract instantiateComponent(target: HTMLElement): ReturnType | Promise>; component?: ReturnType; - async onOpen() { + override async onOpen() { await super.onOpen(); this.contentEl.empty(); await this._dismountComponent(); @@ -17,7 +17,7 @@ export abstract class SvelteItemView extends ItemView { this.component = undefined; } } - async onClose() { + override async onClose() { await super.onClose(); if (this.component) { await unmount(this.component); diff --git a/src/common/utils.ts b/src/common/utils.ts index 3ef6d80..ef1eba1 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -31,7 +31,6 @@ import { sameChangePairs } from "./stores.ts"; import { scheduleTask } from "octagonal-wheels/concurrency/task"; import { EVENT_PLUGIN_UNLOADED, eventHub } from "./events.ts"; -import { promiseWithResolver, type PromiseWithResolvers } from "octagonal-wheels/promises"; import { AuthorizationHeaderGenerator } from "../lib/src/replication/httplib.ts"; import type { KeyValueDatabase } from "../lib/src/interfaces/KeyValueDatabase.ts"; @@ -152,7 +151,7 @@ export class PeriodicProcessor { () => fireAndForget(async () => { await this.process(); - if (this._plugin.services?.appLifecycle?.hasUnloaded()) { + if (this._plugin.services?.control?.hasUnloaded()) { this.disable(); } }), @@ -459,47 +458,3 @@ export function onlyInNTimes(n: number, proc: (progress: number) => any) { } }; } - -const waitingTasks = {} as Record; previous: number; leastNext: number }>; - -export function rateLimitedSharedExecution(key: string, interval: number, proc: () => Promise): Promise { - if (!(key in waitingTasks)) { - waitingTasks[key] = { task: undefined, previous: 0, leastNext: 0 }; - } - if (waitingTasks[key].task) { - // Extend the previous execution time. - waitingTasks[key].leastNext = Date.now() + interval; - return waitingTasks[key].task.promise; - } - - const previous = waitingTasks[key].previous; - - const delay = previous == 0 ? 0 : Math.max(interval - (Date.now() - previous), 0); - - const task = promiseWithResolver(); - void task.promise.finally(() => { - if (waitingTasks[key].task === task) { - waitingTasks[key].task = undefined; - waitingTasks[key].previous = Math.max(Date.now(), waitingTasks[key].leastNext); - } - }); - waitingTasks[key] = { - task, - previous: Date.now(), - leastNext: Date.now() + interval, - }; - void scheduleTask("thin-out-" + key, delay, async () => { - try { - task.resolve(await proc()); - } catch (ex) { - task.reject(ex); - } - }); - return task.promise; -} -export function updatePreviousExecutionTime(key: string, timeDelta: number = 0) { - if (!(key in waitingTasks)) { - waitingTasks[key] = { task: undefined, previous: 0, leastNext: 0 }; - } - waitingTasks[key].leastNext = Math.max(Date.now() + timeDelta, waitingTasks[key].leastNext); -} diff --git a/src/features/ConfigSync/CmdConfigSync.ts b/src/features/ConfigSync/CmdConfigSync.ts index 2a0d046..e51af06 100644 --- a/src/features/ConfigSync/CmdConfigSync.ts +++ b/src/features/ConfigSync/CmdConfigSync.ts @@ -1802,7 +1802,7 @@ export class ConfigSync extends LiveSyncCommands { } return files; } - onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { + override onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { services.fileProcessing.processOptionalFileEvent.addHandler(this._anyProcessOptionalFileEvent.bind(this)); services.conflict.getOptionalConflictCheckMethod.addHandler(this._anyGetOptionalConflictCheckMethod.bind(this)); services.replication.processVirtualDocument.addHandler(this._anyModuleParsedReplicationResultItem.bind(this)); diff --git a/src/features/ConfigSync/PluginDialogModal.ts b/src/features/ConfigSync/PluginDialogModal.ts index 3b1d8c1..d75043b 100644 --- a/src/features/ConfigSync/PluginDialogModal.ts +++ b/src/features/ConfigSync/PluginDialogModal.ts @@ -14,7 +14,7 @@ export class PluginDialogModal extends Modal { this.plugin = plugin; } - onOpen() { + override onOpen() { const { contentEl } = this; this.contentEl.style.overflow = "auto"; this.contentEl.style.display = "flex"; @@ -28,7 +28,7 @@ export class PluginDialogModal extends Modal { } } - onClose() { + override onClose() { if (this.component) { void unmount(this.component); this.component = undefined; diff --git a/src/features/HiddenFileCommon/JsonResolveModal.ts b/src/features/HiddenFileCommon/JsonResolveModal.ts index 309e5cc..c10baab 100644 --- a/src/features/HiddenFileCommon/JsonResolveModal.ts +++ b/src/features/HiddenFileCommon/JsonResolveModal.ts @@ -50,7 +50,7 @@ export class JsonResolveModal extends Modal { this.callback = undefined; } - onOpen() { + override onOpen() { const { contentEl } = this; this.titleEl.setText(this.title); contentEl.empty(); @@ -74,7 +74,7 @@ export class JsonResolveModal extends Modal { return; } - onClose() { + override onClose() { const { contentEl } = this; contentEl.empty(); // contentEl.empty(); diff --git a/src/features/HiddenFileSync/CmdHiddenFileSync.ts b/src/features/HiddenFileSync/CmdHiddenFileSync.ts index 8d50d74..d8c4d60 100644 --- a/src/features/HiddenFileSync/CmdHiddenFileSync.ts +++ b/src/features/HiddenFileSync/CmdHiddenFileSync.ts @@ -1934,7 +1934,7 @@ ${messageFetch}${messageOverwrite}${messageMerge} */ // <-- Local Storage SubFunctions - onBindFunction(core: LiveSyncCore, services: typeof core.services) { + override onBindFunction(core: LiveSyncCore, services: typeof core.services) { // No longer needed on initialisation // services.databaseEvents.handleOnDatabaseInitialisation(this._everyOnInitializeDatabase.bind(this)); services.appLifecycle.onSettingLoaded.addHandler(this._everyOnloadAfterLoadSettings.bind(this)); diff --git a/src/features/P2PSync/CmdP2PReplicator.ts b/src/features/P2PSync/CmdP2PReplicator.ts index 4a691b3..e9b2125 100644 --- a/src/features/P2PSync/CmdP2PReplicator.ts +++ b/src/features/P2PSync/CmdP2PReplicator.ts @@ -40,9 +40,6 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase getSettings(): P2PSyncSetting { return this.plugin.settings; } - get settings() { - return this.plugin.settings; - } getDB() { return this.plugin.localDatabase.localDatabase; } @@ -65,7 +62,7 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase // this.onBindFunction(plugin, plugin.services); } - async handleReplicatedDocuments(docs: EntryDoc[]): Promise { + async handleReplicatedDocuments(docs: EntryDoc[]): Promise { // console.log("Processing Replicated Docs", docs); return await this.services.replication.parseSynchroniseResult( docs as PouchDB.Core.ExistingDocument[] diff --git a/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts b/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts index b22c4bc..806da90 100644 --- a/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts +++ b/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts @@ -35,11 +35,11 @@ function removeFromList(item: string, list: string) { export class P2PReplicatorPaneView extends SvelteItemView { plugin: ObsidianLiveSyncPlugin; - icon = "waypoints"; + override icon = "waypoints"; title: string = ""; - navigation = false; + override navigation = false; - getIcon(): string { + override getIcon(): string { return "waypoints"; } get replicator() { diff --git a/src/lib b/src/lib index 820a266..d402f2d 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 820a26641a92242ae2939467ee9cf857afee3ed2 +Subproject commit d402f2d7f3d5677bee4ca11846b0c4f2259840f1 diff --git a/src/main.ts b/src/main.ts index 4ea2d17..4f335df 100644 --- a/src/main.ts +++ b/src/main.ts @@ -43,7 +43,6 @@ import { ModuleReplicatorCouchDB } from "./modules/core/ModuleReplicatorCouchDB. import { ModuleReplicatorMinIO } from "./modules/core/ModuleReplicatorMinIO.ts"; import { ModuleTargetFilter } from "./modules/core/ModuleTargetFilter.ts"; import { ModulePeriodicProcess } from "./modules/core/ModulePeriodicProcess.ts"; -import { ModuleRemoteGovernor } from "./modules/coreFeatures/ModuleRemoteGovernor.ts"; import { ModuleConflictChecker } from "./modules/coreFeatures/ModuleConflictChecker.ts"; import { ModuleResolvingMismatchedTweaks } from "./modules/coreFeatures/ModuleResolveMismatchedTweaks.ts"; import { ModuleIntegratedTest } from "./modules/extras/ModuleIntegratedTest.ts"; @@ -115,6 +114,7 @@ export default class ObsidianLiveSyncPlugin */ private _registerAddOn(addOn: LiveSyncCommands) { this.addOns.push(addOn); + this.services.appLifecycle.onUnload.addHandler(() => Promise.resolve(addOn.onunload()).then(() => true)); } private registerAddOns() { @@ -163,7 +163,6 @@ export default class ObsidianLiveSyncPlugin this._registerModule(new ModuleReplicatorCouchDB(this)); this._registerModule(new ModuleReplicator(this)); this._registerModule(new ModuleConflictResolver(this)); - this._registerModule(new ModuleRemoteGovernor(this)); this._registerModule(new ModuleTargetFilter(this)); this._registerModule(new ModulePeriodicProcess(this)); this._registerModule(new ModuleInitializerFile(this)); @@ -439,10 +438,10 @@ export default class ObsidianLiveSyncPlugin const onReady = this.services.control.onReady.bind(this.services.control); this.app.workspace.onLayoutReady(onReady); } - onload() { + override onload() { void this._startUp(); } - onunload() { + override onunload() { return void this.services.control.onUnload(); } } diff --git a/src/modules/AbstractObsidianModule.ts b/src/modules/AbstractObsidianModule.ts index cf2ab2e..0d188a3 100644 --- a/src/modules/AbstractObsidianModule.ts +++ b/src/modules/AbstractObsidianModule.ts @@ -16,7 +16,7 @@ export abstract class AbstractObsidianModule extends AbstractModule { constructor( public plugin: ObsidianLiveSyncPlugin, - public core: LiveSyncCore + core: LiveSyncCore ) { super(core); } diff --git a/src/modules/core/ModulePeriodicProcess.ts b/src/modules/core/ModulePeriodicProcess.ts index 30e2039..a50a95e 100644 --- a/src/modules/core/ModulePeriodicProcess.ts +++ b/src/modules/core/ModulePeriodicProcess.ts @@ -31,7 +31,7 @@ export class ModulePeriodicProcess extends AbstractModule { return this.resumePeriodic(); } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.appLifecycle.onUnload.addHandler(this._allOnUnload.bind(this)); services.setting.onBeforeRealiseSetting.addHandler(this._everyBeforeRealizeSetting.bind(this)); services.setting.onSettingRealised.addHandler(this._everyAfterRealizeSetting.bind(this)); diff --git a/src/modules/core/ModuleReplicator.ts b/src/modules/core/ModuleReplicator.ts index 84fb728..622d13f 100644 --- a/src/modules/core/ModuleReplicator.ts +++ b/src/modules/core/ModuleReplicator.ts @@ -1,12 +1,12 @@ import { fireAndForget } from "octagonal-wheels/promises"; import { AbstractModule } from "../AbstractModule"; -import { Logger, LOG_LEVEL_NOTICE, LOG_LEVEL_INFO, LEVEL_NOTICE, type LOG_LEVEL } from "octagonal-wheels/common/logger"; -import { isLockAcquired, shareRunningResult, skipIfDuplicated } from "octagonal-wheels/concurrency/lock"; +import { Logger, LOG_LEVEL_NOTICE, LOG_LEVEL_INFO } from "octagonal-wheels/common/logger"; +import { skipIfDuplicated } from "octagonal-wheels/concurrency/lock"; import { balanceChunkPurgedDBs } from "@lib/pouchdb/chunks"; import { purgeUnreferencedChunks } from "@lib/pouchdb/chunks"; import { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator"; import { type EntryDoc, type RemoteType } from "../../lib/src/common/types"; -import { rateLimitedSharedExecution, scheduleTask, updatePreviousExecutionTime } from "../../common/utils"; +import { scheduleTask } from "../../common/utils"; import { EVENT_FILE_SAVED, EVENT_SETTING_SAVED, eventHub } from "../../common/events"; import { $msg } from "../../lib/src/common/i18n"; @@ -14,9 +14,45 @@ import type { LiveSyncCore } from "../../main"; import { ReplicateResultProcessor } from "./ReplicateResultProcessor"; import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager"; import { clearHandlers } from "@lib/replication/SyncParamsHandler"; +import type { NecessaryServices } from "@/serviceFeatures/types"; -const KEY_REPLICATION_ON_EVENT = "replicationOnEvent"; -const REPLICATION_ON_EVENT_FORECASTED_TIME = 5000; +function isOnlineAndCanReplicate( + errorManager: UnresolvedErrorManager, + host: NecessaryServices<"database", any>, + showMessage: boolean +): Promise { + const errorMessage = "Network is offline"; + const manager = host.services.database.managers.networkManager; + if (!manager.isOnline) { + errorManager.showError(errorMessage, showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO); + return Promise.resolve(false); + } + errorManager.clearError(errorMessage); + return Promise.resolve(true); +} +async function canReplicateWithPBKDF2( + errorManager: UnresolvedErrorManager, + host: NecessaryServices<"replicator" | "setting", any>, + showMessage: boolean +): Promise { + const currentSettings = host.services.setting.currentSettings(); + // TODO: check using PBKDF2 salt? + const errorMessage = $msg("Replicator.Message.InitialiseFatalError"); + const replicator = host.services.replicator.getActiveReplicator(); + if (!replicator) { + errorManager.showError(errorMessage, showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO); + return false; + } + errorManager.clearError(errorMessage); + const ensureMessage = "Failed to initialise the encryption key, preventing replication."; + const ensureResult = await replicator.ensurePBKDF2Salt(currentSettings, showMessage, true); + if (!ensureResult) { + errorManager.showError(ensureMessage, showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO); + return false; + } + errorManager.clearError(ensureMessage); + return ensureResult; // is true. +} export class ModuleReplicator extends AbstractModule { _replicatorType?: RemoteType; @@ -26,9 +62,6 @@ export class ModuleReplicator extends AbstractModule { this.core.services.appLifecycle ); - showError(msg: string, max_log_level: LOG_LEVEL = LEVEL_NOTICE) { - this._unresolvedErrorManager.showError(msg, max_log_level); - } clearErrors() { this._unresolvedErrorManager.clearErrors(); } @@ -40,10 +73,6 @@ export class ModuleReplicator extends AbstractModule { } }); eventHub.onEvent(EVENT_SETTING_SAVED, (setting) => { - // ReplicatorService responds to `settingService.onRealiseSetting`. - // if (this._replicatorType !== setting.remoteType) { - // void this.setReplicator(); - // } if (this.core.settings.suspendParseReplicationResult) { this.processor.suspend(); } else { @@ -65,41 +94,12 @@ export class ModuleReplicator extends AbstractModule { return Promise.resolve(true); } - async ensureReplicatorPBKDF2Salt(showMessage: boolean = false): Promise { - // Checking salt - const replicator = this.services.replicator.getActiveReplicator(); - if (!replicator) { - this.showError($msg("Replicator.Message.InitialiseFatalError"), LOG_LEVEL_NOTICE); - return false; - } - return await replicator.ensurePBKDF2Salt(this.settings, showMessage, true); - } - async _everyBeforeReplicate(showMessage: boolean): Promise { - // Checking salt - if (!this.core.managers.networkManager.isOnline) { - this.showError("Network is offline", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO); - return false; - } - // Showing message is false: that because be shown here. (And it is a fatal error, no way to hide it). - if (!(await this.ensureReplicatorPBKDF2Salt(false))) { - this.showError("Failed to initialise the encryption key, preventing replication."); - return false; - } await this.processor.restoreFromSnapshotOnce(); this.clearErrors(); return true; } - private async _replicate(showMessage: boolean = false): Promise { - try { - updatePreviousExecutionTime(KEY_REPLICATION_ON_EVENT, REPLICATION_ON_EVENT_FORECASTED_TIME); - return await this.$$_replicate(showMessage); - } finally { - updatePreviousExecutionTime(KEY_REPLICATION_ON_EVENT); - } - } - /** * obsolete method. No longer maintained and will be removed in the future. * @deprecated v0.24.17 @@ -159,149 +159,129 @@ Even if you choose to clean up, you will see this option again if you exit Obsid }); } - async _canReplicate(showMessage: boolean = false): Promise { - if (!this.services.appLifecycle.isReady()) { - Logger(`Not ready`); + private async onReplicationFailed(showMessage: boolean = false): Promise { + const activeReplicator = this.services.replicator.getActiveReplicator(); + if (!activeReplicator) { + Logger(`No active replicator found`, LOG_LEVEL_INFO); return false; } - - if (isLockAcquired("cleanup")) { - Logger($msg("Replicator.Message.Cleaned"), LOG_LEVEL_NOTICE); - return false; - } - - if (this.settings.versionUpFlash != "") { - Logger($msg("Replicator.Message.VersionUpFlash"), LOG_LEVEL_NOTICE); - return false; - } - - if (!(await this.services.fileProcessing.commitPendingFileEvents())) { - this.showError($msg("Replicator.Message.Pending"), LOG_LEVEL_NOTICE); - return false; - } - - if (!this.core.managers.networkManager.isOnline) { - this.showError("Network is offline", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO); - return false; - } - if (!(await this.services.replication.onBeforeReplicate(showMessage))) { - this.showError($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE); - return false; - } - this.clearErrors(); - return true; - } - - async $$_replicate(showMessage: boolean = false): Promise { - const checkBeforeReplicate = await this.services.replication.isReplicationReady(showMessage); - if (!checkBeforeReplicate) return false; - - //<-- Here could be an module. - const ret = await this.core.replicator.openReplication(this.settings, false, showMessage, false); - if (!ret) { - if (this.core.replicator.tweakSettingsMismatched && this.core.replicator.preferredTweakValue) { - await this.services.tweakValue.askResolvingMismatched(this.core.replicator.preferredTweakValue); - } else { - if (this.core.replicator?.remoteLockedAndDeviceNotAccepted) { - if (this.core.replicator.remoteCleaned && this.settings.useIndexedDBAdapter) { - await this.cleaned(showMessage); - } else { - const message = $msg("Replicator.Dialogue.Locked.Message"); - const CHOICE_FETCH = $msg("Replicator.Dialogue.Locked.Action.Fetch"); - const CHOICE_DISMISS = $msg("Replicator.Dialogue.Locked.Action.Dismiss"); - const CHOICE_UNLOCK = $msg("Replicator.Dialogue.Locked.Action.Unlock"); - const ret = await this.core.confirm.askSelectStringDialogue( - message, - [CHOICE_FETCH, CHOICE_UNLOCK, CHOICE_DISMISS], - { - title: $msg("Replicator.Dialogue.Locked.Title"), - defaultAction: CHOICE_DISMISS, - timeout: 60, - } - ); - if (ret == CHOICE_FETCH) { - this._log($msg("Replicator.Dialogue.Locked.Message.Fetch"), LOG_LEVEL_NOTICE); - await this.core.rebuilder.scheduleFetch(); - this.services.appLifecycle.scheduleRestart(); - return; - } else if (ret == CHOICE_UNLOCK) { - await this.core.replicator.markRemoteResolved(this.settings); - this._log($msg("Replicator.Dialogue.Locked.Message.Unlocked"), LOG_LEVEL_NOTICE); - return; + if (activeReplicator.tweakSettingsMismatched && activeReplicator.preferredTweakValue) { + await this.services.tweakValue.askResolvingMismatched(activeReplicator.preferredTweakValue); + } else { + if (activeReplicator.remoteLockedAndDeviceNotAccepted) { + if (activeReplicator.remoteCleaned && this.settings.useIndexedDBAdapter) { + await this.cleaned(showMessage); + } else { + const message = $msg("Replicator.Dialogue.Locked.Message"); + const CHOICE_FETCH = $msg("Replicator.Dialogue.Locked.Action.Fetch"); + const CHOICE_DISMISS = $msg("Replicator.Dialogue.Locked.Action.Dismiss"); + const CHOICE_UNLOCK = $msg("Replicator.Dialogue.Locked.Action.Unlock"); + const ret = await this.core.confirm.askSelectStringDialogue( + message, + [CHOICE_FETCH, CHOICE_UNLOCK, CHOICE_DISMISS], + { + title: $msg("Replicator.Dialogue.Locked.Title"), + defaultAction: CHOICE_DISMISS, + timeout: 60, } + ); + if (ret == CHOICE_FETCH) { + this._log($msg("Replicator.Dialogue.Locked.Message.Fetch"), LOG_LEVEL_NOTICE); + await this.core.rebuilder.scheduleFetch(); + this.services.appLifecycle.scheduleRestart(); + return false; + } else if (ret == CHOICE_UNLOCK) { + await activeReplicator.markRemoteResolved(this.settings); + this._log($msg("Replicator.Dialogue.Locked.Message.Unlocked"), LOG_LEVEL_NOTICE); + return false; } } } } - return ret; + // TODO: Check again and true/false return. This will be the result for performReplication. + return false; } - private async _replicateByEvent(): Promise { - const least = this.settings.syncMinimumInterval; - if (least > 0) { - return rateLimitedSharedExecution(KEY_REPLICATION_ON_EVENT, least, async () => { - return await this.services.replication.replicate(); - }); - } - return await shareRunningResult(`replication`, () => this.services.replication.replicate()); - } + // private async _replicateByEvent(): Promise { + // const least = this.settings.syncMinimumInterval; + // if (least > 0) { + // return rateLimitedSharedExecution(KEY_REPLICATION_ON_EVENT, least, async () => { + // return await this.services.replication.replicate(); + // }); + // } + // return await shareRunningResult(`replication`, () => this.services.replication.replicate()); + // } - _parseReplicationResult(docs: Array>): void { + _parseReplicationResult(docs: Array>): Promise { this.processor.enqueueAll(docs); - } - - _everyBeforeSuspendProcess(): Promise { - this.core.replicator?.closeReplication(); return Promise.resolve(true); } - private async _replicateAllToServer( - showingNotice: boolean = false, - sendChunksInBulkDisabled: boolean = false - ): Promise { - if (!this.services.appLifecycle.isReady()) return false; - if (!(await this.services.replication.onBeforeReplicate(showingNotice))) { - Logger($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE); - return false; - } - if (!sendChunksInBulkDisabled) { - if (this.core.replicator instanceof LiveSyncCouchDBReplicator) { - if ( - (await this.core.confirm.askYesNoDialog("Do you want to send all chunks before replication?", { - defaultOption: "No", - timeout: 20, - })) == "yes" - ) { - await this.core.replicator.sendChunks(this.core.settings, undefined, true, 0); - } - } - } - const ret = await this.core.replicator.replicateAllToServer(this.settings, showingNotice); - if (ret) return true; - const checkResult = await this.services.replication.checkConnectionFailure(); - if (checkResult == "CHECKAGAIN") return await this.services.remote.replicateAllToRemote(showingNotice); - return !checkResult; - } - async _replicateAllFromServer(showingNotice: boolean = false): Promise { - if (!this.services.appLifecycle.isReady()) return false; - const ret = await this.core.replicator.replicateAllFromServer(this.settings, showingNotice); - if (ret) return true; - const checkResult = await this.services.replication.checkConnectionFailure(); - if (checkResult == "CHECKAGAIN") return await this.services.remote.replicateAllFromRemote(showingNotice); - return !checkResult; - } + // _everyBeforeSuspendProcess(): Promise { + // this.core.replicator?.closeReplication(); + // return Promise.resolve(true); + // } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + // private async _replicateAllToServer( + // showingNotice: boolean = false, + // sendChunksInBulkDisabled: boolean = false + // ): Promise { + // if (!this.services.appLifecycle.isReady()) return false; + // if (!(await this.services.replication.onBeforeReplicate(showingNotice))) { + // Logger($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE); + // return false; + // } + // if (!sendChunksInBulkDisabled) { + // if (this.core.replicator instanceof LiveSyncCouchDBReplicator) { + // if ( + // (await this.core.confirm.askYesNoDialog("Do you want to send all chunks before replication?", { + // defaultOption: "No", + // timeout: 20, + // })) == "yes" + // ) { + // await this.core.replicator.sendChunks(this.core.settings, undefined, true, 0); + // } + // } + // } + // const ret = await this.core.replicator.replicateAllToServer(this.settings, showingNotice); + // if (ret) return true; + // const checkResult = await this.services.replication.checkConnectionFailure(); + // if (checkResult == "CHECKAGAIN") return await this.services.remote.replicateAllToRemote(showingNotice); + // return !checkResult; + // } + // async _replicateAllFromServer(showingNotice: boolean = false): Promise { + // if (!this.services.appLifecycle.isReady()) return false; + // const ret = await this.core.replicator.replicateAllFromServer(this.settings, showingNotice); + // if (ret) return true; + // const checkResult = await this.services.replication.checkConnectionFailure(); + // if (checkResult == "CHECKAGAIN") return await this.services.remote.replicateAllFromRemote(showingNotice); + // return !checkResult; + // } + + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.replicator.onReplicatorInitialised.addHandler(this._onReplicatorInitialised.bind(this)); services.databaseEvents.onDatabaseInitialised.addHandler(this._everyOnDatabaseInitialized.bind(this)); services.appLifecycle.onSettingLoaded.addHandler(this._everyOnloadAfterLoadSettings.bind(this)); - services.replication.parseSynchroniseResult.setHandler(this._parseReplicationResult.bind(this)); - services.appLifecycle.onSuspending.addHandler(this._everyBeforeSuspendProcess.bind(this)); - services.replication.onBeforeReplicate.addHandler(this._everyBeforeReplicate.bind(this)); - services.replication.isReplicationReady.setHandler(this._canReplicate.bind(this)); - services.replication.replicate.setHandler(this._replicate.bind(this)); - services.replication.replicateByEvent.setHandler(this._replicateByEvent.bind(this)); - services.remote.replicateAllToRemote.setHandler(this._replicateAllToServer.bind(this)); - services.remote.replicateAllFromRemote.setHandler(this._replicateAllFromServer.bind(this)); + services.replication.parseSynchroniseResult.addHandler(this._parseReplicationResult.bind(this)); + + // --> These handlers can be separated. + const isOnlineAndCanReplicateWithHost = isOnlineAndCanReplicate.bind(null, this._unresolvedErrorManager, { + services: { + database: services.database, + }, + serviceModules: {}, + }); + const canReplicateWithPBKDF2WithHost = canReplicateWithPBKDF2.bind(null, this._unresolvedErrorManager, { + services: { + replicator: services.replicator, + setting: services.setting, + }, + serviceModules: {}, + }); + services.replication.onBeforeReplicate.addHandler(isOnlineAndCanReplicateWithHost, 10); + services.replication.onBeforeReplicate.addHandler(canReplicateWithPBKDF2WithHost, 20); + // <-- End of handlers that can be separated. + services.replication.onBeforeReplicate.addHandler(this._everyBeforeReplicate.bind(this), 100); + services.replication.onReplicationFailed.addHandler(this.onReplicationFailed.bind(this)); } } diff --git a/src/modules/core/ModuleReplicatorCouchDB.ts b/src/modules/core/ModuleReplicatorCouchDB.ts index 97b5ef1..9fdc6d7 100644 --- a/src/modules/core/ModuleReplicatorCouchDB.ts +++ b/src/modules/core/ModuleReplicatorCouchDB.ts @@ -35,7 +35,7 @@ export class ModuleReplicatorCouchDB extends AbstractModule { return Promise.resolve(true); } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.replicator.getNewReplicator.addHandler(this._anyNewReplicator.bind(this)); services.appLifecycle.onResumed.addHandler(this._everyAfterResumeProcess.bind(this)); } diff --git a/src/modules/core/ModuleReplicatorMinIO.ts b/src/modules/core/ModuleReplicatorMinIO.ts index fb00330..ab6800e 100644 --- a/src/modules/core/ModuleReplicatorMinIO.ts +++ b/src/modules/core/ModuleReplicatorMinIO.ts @@ -12,7 +12,7 @@ export class ModuleReplicatorMinIO extends AbstractModule { } return Promise.resolve(false); } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.replicator.getNewReplicator.addHandler(this._anyNewReplicator.bind(this)); } } diff --git a/src/modules/core/ModuleReplicatorP2P.ts b/src/modules/core/ModuleReplicatorP2P.ts index 0817367..1a40eca 100644 --- a/src/modules/core/ModuleReplicatorP2P.ts +++ b/src/modules/core/ModuleReplicatorP2P.ts @@ -27,7 +27,7 @@ export class ModuleReplicatorP2P extends AbstractModule { return Promise.resolve(true); } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.replicator.getNewReplicator.addHandler(this._anyNewReplicator.bind(this)); services.appLifecycle.onResumed.addHandler(this._everyAfterResumeProcess.bind(this)); } diff --git a/src/modules/core/ModuleTargetFilter.ts b/src/modules/core/ModuleTargetFilter.ts index 956f657..6c5af7e 100644 --- a/src/modules/core/ModuleTargetFilter.ts +++ b/src/modules/core/ModuleTargetFilter.ts @@ -137,7 +137,7 @@ export class ModuleTargetFilter extends AbstractModule { return true; } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.vault.markFileListPossiblyChanged.setHandler(this._markFileListPossiblyChanged.bind(this)); services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this)); services.vault.isIgnoredByIgnoreFile.setHandler(this._isTargetIgnoredByIgnoreFiles.bind(this)); diff --git a/src/modules/coreFeatures/ModuleConflictChecker.ts b/src/modules/coreFeatures/ModuleConflictChecker.ts index d3a53d5..d81e40d 100644 --- a/src/modules/coreFeatures/ModuleConflictChecker.ts +++ b/src/modules/coreFeatures/ModuleConflictChecker.ts @@ -74,7 +74,7 @@ export class ModuleConflictChecker extends AbstractModule { totalRemainingReactiveSource: this.services.conflict.conflictProcessQueueCount, } ); - onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { + override onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { services.conflict.queueCheckForIfOpen.setHandler(this._queueConflictCheckIfOpen.bind(this)); services.conflict.queueCheckFor.setHandler(this._queueConflictCheck.bind(this)); services.conflict.ensureAllProcessed.setHandler(this._waitForAllConflictProcessed.bind(this)); diff --git a/src/modules/coreFeatures/ModuleConflictResolver.ts b/src/modules/coreFeatures/ModuleConflictResolver.ts index 7111665..f09271a 100644 --- a/src/modules/coreFeatures/ModuleConflictResolver.ts +++ b/src/modules/coreFeatures/ModuleConflictResolver.ts @@ -229,7 +229,7 @@ export class ModuleConflictResolver extends AbstractModule { this._log(`Done!`, LOG_LEVEL_NOTICE, "resolveAllConflictedFilesByNewerOnes"); } - onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { + override onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { services.conflict.resolveByDeletingRevision.setHandler(this._resolveConflictByDeletingRev.bind(this)); services.conflict.resolve.setHandler(this._resolveConflict.bind(this)); services.conflict.resolveByNewest.setHandler(this._anyResolveConflictByNewest.bind(this)); diff --git a/src/modules/coreFeatures/ModuleRedFlag.ts b/src/modules/coreFeatures/ModuleRedFlag.ts index 7ebdb0b..d57cb0f 100644 --- a/src/modules/coreFeatures/ModuleRedFlag.ts +++ b/src/modules/coreFeatures/ModuleRedFlag.ts @@ -324,7 +324,7 @@ export class ModuleRedFlag extends AbstractModule { } return true; } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { super.onBindFunction(core, services); services.appLifecycle.onLayoutReady.addHandler(this._everyOnLayoutReady.bind(this)); } diff --git a/src/modules/coreFeatures/ModuleRemoteGovernor.ts b/src/modules/coreFeatures/ModuleRemoteGovernor.ts deleted file mode 100644 index 4252643..0000000 --- a/src/modules/coreFeatures/ModuleRemoteGovernor.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts"; -import type { LiveSyncCore } from "../../main.ts"; -import { AbstractModule } from "../AbstractModule.ts"; - -export class ModuleRemoteGovernor extends AbstractModule { - private async _markRemoteLocked(lockByClean: boolean = false): Promise { - return await this.core.replicator.markRemoteLocked(this.settings, true, lockByClean); - } - - private async _markRemoteUnlocked(): Promise { - return await this.core.replicator.markRemoteLocked(this.settings, false, false); - } - - private async _markRemoteResolved(): Promise { - return await this.core.replicator.markRemoteResolved(this.settings); - } - onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { - services.remote.markLocked.setHandler(this._markRemoteLocked.bind(this)); - services.remote.markUnlocked.setHandler(this._markRemoteUnlocked.bind(this)); - services.remote.markResolved.setHandler(this._markRemoteResolved.bind(this)); - } -} diff --git a/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.ts b/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.ts index f184922..fab0091 100644 --- a/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.ts +++ b/src/modules/coreFeatures/ModuleResolveMismatchedTweaks.ts @@ -284,7 +284,7 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule { return { result: false, requireFetch: false }; } - onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { + override onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { services.tweakValue.fetchRemotePreferred.setHandler(this._fetchRemotePreferredTweakValues.bind(this)); services.tweakValue.checkAndAskResolvingMismatched.setHandler( this._checkAndAskResolvingMismatchedTweaks.bind(this) diff --git a/src/modules/coreObsidian/UILib/dialogs.ts b/src/modules/coreObsidian/UILib/dialogs.ts index 832fea6..ce77bd9 100644 --- a/src/modules/coreObsidian/UILib/dialogs.ts +++ b/src/modules/coreObsidian/UILib/dialogs.ts @@ -13,7 +13,7 @@ class AutoClosableModal extends Modal { this._closeByUnload = this._closeByUnload.bind(this); eventHub.once(EVENT_PLUGIN_UNLOADED, this._closeByUnload); } - onClose() { + override onClose() { eventHub.off(EVENT_PLUGIN_UNLOADED, this._closeByUnload); } } @@ -43,7 +43,7 @@ export class InputStringDialog extends AutoClosableModal { this.isPassword = isPassword; } - onOpen() { + override onOpen() { const { contentEl } = this; this.titleEl.setText(this.title); const formEl = contentEl.createDiv(); @@ -75,7 +75,7 @@ export class InputStringDialog extends AutoClosableModal { ); } - onClose() { + override onClose() { super.onClose(); const { contentEl } = this; contentEl.empty(); @@ -87,7 +87,7 @@ export class InputStringDialog extends AutoClosableModal { } } export class PopoverSelectString extends FuzzySuggestModal { - app: App; + _app: App; callback: ((e: string) => void) | undefined = () => {}; getItemsFun: () => string[] = () => { return ["yes", "no"]; @@ -101,7 +101,7 @@ export class PopoverSelectString extends FuzzySuggestModal { callback: (e: string) => void ) { super(app); - this.app = app; + this._app = app; this.setPlaceholder((placeholder ?? "y/n) ") + note); if (getItemsFun) this.getItemsFun = getItemsFun; this.callback = callback; @@ -120,7 +120,7 @@ export class PopoverSelectString extends FuzzySuggestModal { this.callback?.(item); this.callback = undefined; } - onClose(): void { + override onClose(): void { setTimeout(() => { if (this.callback) { this.callback(""); @@ -184,7 +184,7 @@ export class MessageBox extends AutoClosableModal { } } - onOpen() { + override onOpen() { const { contentEl } = this; this.titleEl.setText(this.title); const div = contentEl.createDiv(); @@ -242,7 +242,7 @@ export class MessageBox extends AutoClosableModal { } } - onClose() { + override onClose() { super.onClose(); const { contentEl } = this; contentEl.empty(); diff --git a/src/modules/essential/ModuleInitializerFile.ts b/src/modules/essential/ModuleInitializerFile.ts index e246ca4..8112bfe 100644 --- a/src/modules/essential/ModuleInitializerFile.ts +++ b/src/modules/essential/ModuleInitializerFile.ts @@ -421,7 +421,7 @@ export class ModuleInitializerFile extends AbstractModule { private _reportDetectedErrors(): Promise { return Promise.resolve(Array.from(this._detectedErrors)); } - onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { + override onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { services.appLifecycle.getUnresolvedMessages.addHandler(this._reportDetectedErrors.bind(this)); services.databaseEvents.initialiseDatabase.setHandler(this._initializeDatabase.bind(this)); services.vault.scanVault.setHandler(this._performFullScan.bind(this)); diff --git a/src/modules/essential/ModuleMigration.ts b/src/modules/essential/ModuleMigration.ts index e6c03df..8ff2227 100644 --- a/src/modules/essential/ModuleMigration.ts +++ b/src/modules/essential/ModuleMigration.ts @@ -353,7 +353,7 @@ export class ModuleMigration extends AbstractModule { }); return Promise.resolve(true); } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { super.onBindFunction(core, services); services.appLifecycle.onLayoutReady.addHandler(this._everyOnLayoutReady.bind(this)); services.appLifecycle.onFirstInitialise.addHandler(this._everyOnFirstInitialize.bind(this)); diff --git a/src/modules/essentialObsidian/APILib/ObsHttpHandler.ts b/src/modules/essentialObsidian/APILib/ObsHttpHandler.ts index 45002d9..5504918 100644 --- a/src/modules/essentialObsidian/APILib/ObsHttpHandler.ts +++ b/src/modules/essentialObsidian/APILib/ObsHttpHandler.ts @@ -28,7 +28,10 @@ export class ObsHttpHandler extends FetchHttpHandler { this.reverseProxyNoSignUrl = reverseProxyNoSignUrl; } // eslint-disable-next-line require-await - async handle(request: HttpRequest, { abortSignal }: HttpHandlerOptions = {}): Promise<{ response: HttpResponse }> { + override async handle( + request: HttpRequest, + { abortSignal }: HttpHandlerOptions = {} + ): Promise<{ response: HttpResponse }> { if (abortSignal?.aborted) { const abortError = new Error("Request aborted"); abortError.name = "AbortError"; diff --git a/src/modules/essentialObsidian/ModuleCheckRemoteSize.ts b/src/modules/essentialObsidian/ModuleCheckRemoteSize.ts index af67bf3..3dedf56 100644 --- a/src/modules/essentialObsidian/ModuleCheckRemoteSize.ts +++ b/src/modules/essentialObsidian/ModuleCheckRemoteSize.ts @@ -127,7 +127,7 @@ export class ModuleCheckRemoteSize extends AbstractModule { eventHub.onEvent(EVENT_REQUEST_CHECK_REMOTE_SIZE, () => this.checkRemoteSize()); return Promise.resolve(true); } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.appLifecycle.onScanningStartupIssues.addHandler(this._allScanStat.bind(this)); services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); } diff --git a/src/modules/essentialObsidian/ModuleObsidianAPI.ts b/src/modules/essentialObsidian/ModuleObsidianAPI.ts index 9de2ad3..ac52c1b 100644 --- a/src/modules/essentialObsidian/ModuleObsidianAPI.ts +++ b/src/modules/essentialObsidian/ModuleObsidianAPI.ts @@ -282,7 +282,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule { return Promise.resolve([...this._previousErrors]); } - onBindFunction(core: LiveSyncCore, services: typeof core.services) { + override onBindFunction(core: LiveSyncCore, services: typeof core.services) { services.API.isLastPostFailedDueToPayloadSize.setHandler(this._getLastPostFailedBySize.bind(this)); services.remote.connect.setHandler(this._connectRemoteCouchDB.bind(this)); services.appLifecycle.getUnresolvedMessages.addHandler(this._reportUnresolvedMessages.bind(this)); diff --git a/src/modules/essentialObsidian/ModuleObsidianEvents.ts b/src/modules/essentialObsidian/ModuleObsidianEvents.ts index 63814ff..7445e11 100644 --- a/src/modules/essentialObsidian/ModuleObsidianEvents.ts +++ b/src/modules/essentialObsidian/ModuleObsidianEvents.ts @@ -45,7 +45,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule { this.initialCallback = save; saveCommandDefinition.callback = () => { scheduleTask("syncOnEditorSave", 250, () => { - if (this.services.appLifecycle.hasUnloaded()) { + if (this.services.control.hasUnloaded()) { this._log("Unload and remove the handler.", LOG_LEVEL_VERBOSE); saveCommandDefinition.callback = this.initialCallback; this.initialCallback = undefined; @@ -247,7 +247,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule { _isReloadingScheduled(): boolean { return this._totalProcessingCount !== undefined; } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.appLifecycle.onLayoutReady.addHandler(this._everyOnLayoutReady.bind(this)); services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); services.appLifecycle.askRestart.setHandler(this._askReload.bind(this)); diff --git a/src/modules/essentialObsidian/ModuleObsidianMenu.ts b/src/modules/essentialObsidian/ModuleObsidianMenu.ts index 905e0bf..cab9093 100644 --- a/src/modules/essentialObsidian/ModuleObsidianMenu.ts +++ b/src/modules/essentialObsidian/ModuleObsidianMenu.ts @@ -106,7 +106,7 @@ export class ModuleObsidianMenu extends AbstractModule { return Promise.resolve(true); } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); } } diff --git a/src/modules/extras/ModuleDev.ts b/src/modules/extras/ModuleDev.ts index 06f12e9..a69776a 100644 --- a/src/modules/extras/ModuleDev.ts +++ b/src/modules/extras/ModuleDev.ts @@ -156,7 +156,7 @@ export class ModuleDev extends AbstractObsidianModule { // this.addTestResult("Test of test3", true); return this.testDone(); } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.appLifecycle.onLayoutReady.addHandler(this._everyOnLayoutReady.bind(this)); services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); services.appLifecycle.onSettingLoaded.addHandler(this._everyOnloadAfterLoadSettings.bind(this)); diff --git a/src/modules/extras/ModuleIntegratedTest.ts b/src/modules/extras/ModuleIntegratedTest.ts index a5b724f..df5cc3a 100644 --- a/src/modules/extras/ModuleIntegratedTest.ts +++ b/src/modules/extras/ModuleIntegratedTest.ts @@ -440,7 +440,7 @@ Line4:D`; return Promise.resolve(true); } - onBindFunction(core: typeof this.core, services: typeof core.services): void { + override onBindFunction(core: typeof this.core, services: typeof core.services): void { services.test.testMultiDevice.addHandler(this._everyModuleTestMultiDevice.bind(this)); } } diff --git a/src/modules/extras/ModuleReplicateTest.ts b/src/modules/extras/ModuleReplicateTest.ts index 4de3492..999bdce 100644 --- a/src/modules/extras/ModuleReplicateTest.ts +++ b/src/modules/extras/ModuleReplicateTest.ts @@ -581,7 +581,7 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`; await this._test("Conflict resolution", async () => await this.checkConflictResolution()); return this.testDone(); } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.appLifecycle.onSettingLoaded.addHandler(this._everyOnloadAfterLoadSettings.bind(this)); services.replication.onBeforeReplicate.addHandler(this._everyBeforeReplicate.bind(this)); services.test.testMultiDevice.addHandler(this._everyModuleTestMultiDevice.bind(this)); diff --git a/src/modules/extras/devUtil/TestPaneView.ts b/src/modules/extras/devUtil/TestPaneView.ts index 9b15fa6..32e29d4 100644 --- a/src/modules/extras/devUtil/TestPaneView.ts +++ b/src/modules/extras/devUtil/TestPaneView.ts @@ -8,11 +8,11 @@ export class TestPaneView extends ItemView { component?: TestPaneComponent; plugin: ObsidianLiveSyncPlugin; moduleDev: ModuleDev; - icon = "view-log"; + override icon = "view-log"; title: string = "Self-hosted LiveSync Test and Results"; - navigation = true; + override navigation = true; - getIcon(): string { + override getIcon(): string { return "view-log"; } @@ -30,7 +30,7 @@ export class TestPaneView extends ItemView { return "Self-hosted LiveSync Test and Results"; } - async onOpen() { + override async onOpen() { this.component = new TestPaneComponent({ target: this.contentEl, props: { @@ -41,7 +41,7 @@ export class TestPaneView extends ItemView { await Promise.resolve(); } - async onClose() { + override async onClose() { this.component?.$destroy(); await Promise.resolve(); } diff --git a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts index 35e53cb..00bcf64 100644 --- a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts +++ b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts @@ -214,7 +214,7 @@ export class DocumentHistoryModal extends Modal { } } - onOpen() { + override onOpen() { const { contentEl } = this; this.titleEl.setText("Document History"); contentEl.empty(); @@ -299,7 +299,7 @@ export class DocumentHistoryModal extends Modal { }); }); } - onClose() { + override onClose() { const { contentEl } = this; contentEl.empty(); this.BlobURLs.forEach((value) => { diff --git a/src/modules/features/GlobalHistory/GlobalHistoryView.ts b/src/modules/features/GlobalHistory/GlobalHistoryView.ts index 1b95821..0edcfe4 100644 --- a/src/modules/features/GlobalHistory/GlobalHistoryView.ts +++ b/src/modules/features/GlobalHistory/GlobalHistoryView.ts @@ -16,11 +16,11 @@ export class GlobalHistoryView extends SvelteItemView { } plugin: ObsidianLiveSyncPlugin; - icon = "clock"; + override icon = "clock"; title: string = ""; - navigation = true; + override navigation = true; - getIcon(): string { + override getIcon(): string { return "clock"; } diff --git a/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts b/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts index 6a14488..826fbec 100644 --- a/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts +++ b/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts @@ -44,7 +44,7 @@ export class ConflictResolveModal extends Modal { // sendValue("close-resolve-conflict:" + this.filename, false); } - onOpen() { + override onOpen() { const { contentEl } = this; // Send cancel signal for the previous merge dialogue // if not there, simply be ignored. @@ -119,7 +119,7 @@ export class ConflictResolveModal extends Modal { this.close(); } - onClose() { + override onClose() { const { contentEl } = this; contentEl.empty(); if (this.offEvent) { diff --git a/src/modules/features/Log/LogPaneView.ts b/src/modules/features/Log/LogPaneView.ts index b68c491..5af45f6 100644 --- a/src/modules/features/Log/LogPaneView.ts +++ b/src/modules/features/Log/LogPaneView.ts @@ -19,11 +19,11 @@ export class LogPaneView extends SvelteItemView { } plugin: ObsidianLiveSyncPlugin; - icon = "view-log"; + override icon = "view-log"; title: string = ""; - navigation = false; + override navigation = false; - getIcon(): string { + override getIcon(): string { return "view-log"; } diff --git a/src/modules/features/ModuleGlobalHistory.ts b/src/modules/features/ModuleGlobalHistory.ts index 5dc6374..3e955c9 100644 --- a/src/modules/features/ModuleGlobalHistory.ts +++ b/src/modules/features/ModuleGlobalHistory.ts @@ -19,7 +19,7 @@ export class ModuleObsidianGlobalHistory extends AbstractObsidianModule { showGlobalHistory() { void this.services.API.showWindow(VIEW_TYPE_GLOBAL_HISTORY); } - onBindFunction(core: typeof this.core, services: typeof core.services): void { + override onBindFunction(core: typeof this.core, services: typeof core.services): void { services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); } } diff --git a/src/modules/features/ModuleInteractiveConflictResolver.ts b/src/modules/features/ModuleInteractiveConflictResolver.ts index aea1fdf..0c04092 100644 --- a/src/modules/features/ModuleInteractiveConflictResolver.ts +++ b/src/modules/features/ModuleInteractiveConflictResolver.ts @@ -169,7 +169,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule { } return true; } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.appLifecycle.onScanningStartupIssues.addHandler(this._allScanStat.bind(this)); services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); services.conflict.resolveByUserInteraction.addHandler(this._anyResolveConflictByUI.bind(this)); diff --git a/src/modules/features/ModuleLog.ts b/src/modules/features/ModuleLog.ts index e82cd1d..ce674bf 100644 --- a/src/modules/features/ModuleLog.ts +++ b/src/modules/features/ModuleLog.ts @@ -495,7 +495,7 @@ export class ModuleLog extends AbstractObsidianModule { } } } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.API.addLog.setHandler(globalLogFunction); services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); services.appLifecycle.onSettingLoaded.addHandler(this._everyOnloadAfterLoadSettings.bind(this)); diff --git a/src/modules/features/ModuleObsidianDocumentHistory.ts b/src/modules/features/ModuleObsidianDocumentHistory.ts index 7a49dd7..d1182fc 100644 --- a/src/modules/features/ModuleObsidianDocumentHistory.ts +++ b/src/modules/features/ModuleObsidianDocumentHistory.ts @@ -50,7 +50,7 @@ export class ModuleObsidianDocumentHistory extends AbstractObsidianModule { this.showHistory(targetId.path, targetId.id); } } - onBindFunction(core: typeof this.core, services: typeof core.services): void { + override onBindFunction(core: typeof this.core, services: typeof core.services): void { services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); } } diff --git a/src/modules/features/ModuleObsidianSettingAsMarkdown.ts b/src/modules/features/ModuleObsidianSettingAsMarkdown.ts index cf6bd90..21f1008 100644 --- a/src/modules/features/ModuleObsidianSettingAsMarkdown.ts +++ b/src/modules/features/ModuleObsidianSettingAsMarkdown.ts @@ -246,7 +246,7 @@ We can perform a command in this file. } } - onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { + override onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); } } diff --git a/src/modules/features/ModuleObsidianSettingTab.ts b/src/modules/features/ModuleObsidianSettingTab.ts index e150bd8..52e8ccd 100644 --- a/src/modules/features/ModuleObsidianSettingTab.ts +++ b/src/modules/features/ModuleObsidianSettingTab.ts @@ -29,7 +29,7 @@ export class ModuleObsidianSettingDialogue extends AbstractObsidianModule { get appId() { return `${"appId" in this.app ? this.app.appId : ""}`; } - onBindFunction(core: typeof this.plugin, services: typeof core.services): void { + override onBindFunction(core: typeof this.plugin, services: typeof core.services): void { services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); } } diff --git a/src/modules/features/ModuleSetupObsidian.ts b/src/modules/features/ModuleSetupObsidian.ts index b66d244..d09dbf4 100644 --- a/src/modules/features/ModuleSetupObsidian.ts +++ b/src/modules/features/ModuleSetupObsidian.ts @@ -194,7 +194,7 @@ export class ModuleSetupObsidian extends AbstractModule { // } // } - onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this)); } } diff --git a/src/modules/features/SettingDialogue/LiveSyncSetting.ts b/src/modules/features/SettingDialogue/LiveSyncSetting.ts index 714ad88..0a7f90e 100644 --- a/src/modules/features/SettingDialogue/LiveSyncSetting.ts +++ b/src/modules/features/SettingDialogue/LiveSyncSetting.ts @@ -58,7 +58,7 @@ export class LiveSyncSetting extends Setting { } } - setDesc(desc: string | DocumentFragment): this { + override setDesc(desc: string | DocumentFragment): this { this.descBuf = desc; DEV: { this._createDocStub("desc", desc); @@ -66,7 +66,7 @@ export class LiveSyncSetting extends Setting { super.setDesc(desc); return this; } - setName(name: string | DocumentFragment): this { + override setName(name: string | DocumentFragment): this { this.nameBuf = name; DEV: { this._createDocStub("name", name); diff --git a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts index ce9f71c..23c65bc 100644 --- a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts +++ b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts @@ -374,7 +374,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { this.initialSettings = undefined; } - hide() { + override hide() { this.isShown = false; } isShown: boolean = false; diff --git a/src/modules/features/SettingDialogue/PaneMaintenance.ts b/src/modules/features/SettingDialogue/PaneMaintenance.ts index 7ea5968..4a9838a 100644 --- a/src/modules/features/SettingDialogue/PaneMaintenance.ts +++ b/src/modules/features/SettingDialogue/PaneMaintenance.ts @@ -32,7 +32,7 @@ export function paneMaintenance( (e) => { e.addEventListener("click", () => { fireAndForget(async () => { - await this.services.remote.markResolved(); + await this.services.replication.markResolved(); this.display(); }); }); @@ -59,7 +59,7 @@ export function paneMaintenance( (e) => { e.addEventListener("click", () => { fireAndForget(async () => { - await this.services.remote.markUnlocked(); + await this.services.replication.markUnlocked(); this.display(); }); }); @@ -78,7 +78,7 @@ export function paneMaintenance( .setDisabled(false) .setWarning() .onClick(async () => { - await this.services.remote.markLocked(); + await this.services.replication.markLocked(); }) ) .addOnUpdate(this.onlyOnCouchDBOrMinIO); diff --git a/src/modules/main/ModuleLiveSyncMain.ts b/src/modules/main/ModuleLiveSyncMain.ts index af645e7..3618c61 100644 --- a/src/modules/main/ModuleLiveSyncMain.ts +++ b/src/modules/main/ModuleLiveSyncMain.ts @@ -3,17 +3,13 @@ import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, VER, type ObsidianLiveSyncSettings import { EVENT_LAYOUT_READY, EVENT_PLUGIN_LOADED, - EVENT_PLUGIN_UNLOADED, EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub, } from "../../common/events.ts"; import { $msg, setLang } from "../../lib/src/common/i18n.ts"; import { versionNumberString2Number } from "../../lib/src/string_and_binary/convert.ts"; -import { cancelAllPeriodicTask, cancelAllTasks } from "octagonal-wheels/concurrency/task"; -import { stopAllRunningProcessors } from "octagonal-wheels/concurrency/processor"; import { AbstractModule } from "../AbstractModule.ts"; -import { EVENT_PLATFORM_UNLOADED } from "@lib/events/coreEvents"; import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub.ts"; import type { LiveSyncCore } from "../../main.ts"; import { initialiseWorkerModule } from "@lib/worker/bgWorker.ts"; @@ -139,29 +135,29 @@ export class ModuleLiveSyncMain extends AbstractModule { return true; } - async _onLiveSyncUnload(): Promise { - eventHub.emitEvent(EVENT_PLUGIN_UNLOADED); - await this.services.appLifecycle.onBeforeUnload(); - cancelAllPeriodicTask(); - cancelAllTasks(); - stopAllRunningProcessors(); - await this.services.appLifecycle.onUnload(); - this._unloaded = true; - for (const addOn of this.core.addOns) { - addOn.onunload(); - } - if (this.localDatabase != null) { - this.localDatabase.onunload(); - if (this.core.replicator) { - this.core.replicator?.closeReplication(); - } - await this.localDatabase.close(); - } - eventHub.emitEvent(EVENT_PLATFORM_UNLOADED); - eventHub.offAll(); - this._log($msg("moduleLiveSyncMain.logUnloadingPlugin")); - return; - } + // async _onLiveSyncUnload(): Promise { + // eventHub.emitEvent(EVENT_PLUGIN_UNLOADED); + // await this.services.appLifecycle.onBeforeUnload(); + // cancelAllPeriodicTask(); + // cancelAllTasks(); + // stopAllRunningProcessors(); + // await this.services.appLifecycle.onUnload(); + // this._unloaded = true; + // for (const addOn of this.core.addOns) { + // addOn.onunload(); + // } + // if (this.localDatabase != null) { + // this.localDatabase.onunload(); + // if (this.core.replicator) { + // this.core.replicator?.closeReplication(); + // } + // await this.localDatabase.close(); + // } + // eventHub.emitEvent(EVENT_PLATFORM_UNLOADED); + // eventHub.offAll(); + // this._log($msg("moduleLiveSyncMain.logUnloadingPlugin")); + // return; + // } // private async _realizeSettingSyncMode(): Promise { // await this.services.appLifecycle.onSuspending(); @@ -177,45 +173,44 @@ export class ModuleLiveSyncMain extends AbstractModule { // return; // } - isReady = false; + // isReady = false; - _isReady(): boolean { - return this.isReady; - } + // _isReady(): boolean { + // return this.isReady; + // } - _markIsReady(): void { - this.isReady = true; - } + // _markIsReady(): void { + // this.isReady = true; + // } - _resetIsReady(): void { - this.isReady = false; - } + // _resetIsReady(): void { + // this.isReady = false; + // } - _suspended = false; - _isSuspended(): boolean { - return this._suspended || !this.settings?.isConfigured; - } + // _suspended = false; + // _isSuspended(): boolean { + // return this._suspended || !this.settings?.isConfigured; + // } - _setSuspended(value: boolean) { - this._suspended = value; - } + // _setSuspended(value: boolean) { + // this._suspended = value; + // } - _unloaded = false; - _isUnloaded(): boolean { - return this._unloaded; - } + // _unloaded = false; + // _isUnloaded(): boolean { + // return this._unloaded; + // } - onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { + override onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { super.onBindFunction(core, services); - services.appLifecycle.isSuspended.setHandler(this._isSuspended.bind(this)); - services.appLifecycle.setSuspended.setHandler(this._setSuspended.bind(this)); - services.appLifecycle.isReady.setHandler(this._isReady.bind(this)); - services.appLifecycle.markIsReady.setHandler(this._markIsReady.bind(this)); - services.appLifecycle.resetIsReady.setHandler(this._resetIsReady.bind(this)); - services.appLifecycle.hasUnloaded.setHandler(this._isUnloaded.bind(this)); + // services.appLifecycle.isSuspended.setHandler(this._isSuspended.bind(this)); + // services.appLifecycle.setSuspended.setHandler(this._setSuspended.bind(this)); + // services.appLifecycle.isReady.setHandler(this._isReady.bind(this)); + // services.appLifecycle.markIsReady.setHandler(this._markIsReady.bind(this)); + // services.appLifecycle.resetIsReady.setHandler(this._resetIsReady.bind(this)); + // services.appLifecycle.hasUnloaded.setHandler(this._isUnloaded.bind(this)); services.appLifecycle.onReady.addHandler(this._onLiveSyncReady.bind(this)); services.appLifecycle.onWireUpEvents.addHandler(this._wireUpEvents.bind(this)); services.appLifecycle.onLoad.addHandler(this._onLiveSyncLoad.bind(this)); - services.appLifecycle.onAppUnload.addHandler(this._onLiveSyncUnload.bind(this)); } } diff --git a/src/modules/services/ObsidianAPIService.ts b/src/modules/services/ObsidianAPIService.ts index a1e3f1f..5e5e580 100644 --- a/src/modules/services/ObsidianAPIService.ts +++ b/src/modules/services/ObsidianAPIService.ts @@ -42,7 +42,7 @@ export class ObsidianAPIService extends InjectableAPIService = { appLifecycle: AppLifecycleService; config: ConfigService; replicator: ReplicatorService; APIService: IAPIService; + control: IControlService; }; export class ObsidianUIService extends UIService { @@ -24,6 +25,7 @@ export class ObsidianUIService extends UIService { config: dependents.config, replicator: dependents.replicator, confirm: obsidianConfirm, + control: dependents.control, }); super(context, { appLifecycle: dependents.appLifecycle, diff --git a/src/modules/services/ObsidianVaultService.ts b/src/modules/services/ObsidianVaultService.ts index 00912b7..4229e00 100644 --- a/src/modules/services/ObsidianVaultService.ts +++ b/src/modules/services/ObsidianVaultService.ts @@ -11,7 +11,7 @@ declare module "obsidian" { // InjectableVaultService export class ObsidianVaultService extends InjectableVaultService { - vaultName(): string { + override vaultName(): string { return this.context.app.vault.getName(); } getActiveFilePath(): FilePath | undefined { diff --git a/src/serviceFeatures/types.ts b/src/serviceFeatures/types.ts index bc18fd2..cd24144 100644 --- a/src/serviceFeatures/types.ts +++ b/src/serviceFeatures/types.ts @@ -3,6 +3,7 @@ import type { DatabaseFileAccess } from "@lib/interfaces/DatabaseFileAccess"; import type { Rebuilder } from "@lib/interfaces/DatabaseRebuilder"; import type { IFileHandler } from "@lib/interfaces/FileHandler"; import type { StorageAccess } from "@lib/interfaces/StorageAccess"; +import type { LogFunction } from "@/lib/src/services/lib/logUtils"; export interface ServiceModules { storageAccess: StorageAccess; @@ -31,6 +32,13 @@ export type NecessaryServices = ( host: NecessaryServices ) => TR; +type ServiceFeatureContext = T & { + _log: LogFunction; +}; +export type ServiceFeatureFunctionWithContext = ( + host: NecessaryServices, + context: ServiceFeatureContext +) => TR; /** * Helper function to create a service feature with proper typing. @@ -48,3 +56,23 @@ export function createServiceFeature { return featureFunction; } + +type ContextFactory = ( + host: NecessaryServices +) => ServiceFeatureContext; + +export function serviceFeature() { + return { + create(featureFunction: ServiceFeatureFunction) { + return featureFunction; + }, + withContext(ContextFactory: ContextFactory) { + return { + create: + (featureFunction: ServiceFeatureFunctionWithContext) => + (host: NecessaryServices, context: ServiceFeatureContext) => + featureFunction(host, ContextFactory(host)), + }; + }, + }; +} diff --git a/src/serviceModules/FileAccessObsidian.ts b/src/serviceModules/FileAccessObsidian.ts index 4755717..823fd16 100644 --- a/src/serviceModules/FileAccessObsidian.ts +++ b/src/serviceModules/FileAccessObsidian.ts @@ -1,7 +1,7 @@ import { markChangesAreSame } from "@/common/utils"; import type { FilePath, UXDataWriteOptions, UXFileInfoStub, UXFolderInfo } from "@lib/common/types"; -import { TFolder, type TAbstractFile, TFile, type Stat, type App, type DataWriteOptions } from "@/deps"; +import { TFolder, type TAbstractFile, TFile, type Stat, type App, type DataWriteOptions, normalizePath } from "@/deps"; import { FileAccessBase, toArrayBuffer, type FileAccessBaseDependencies } from "@lib/serviceModules/FileAccessBase.ts"; import { TFileToUXFileInfoStub } from "@/modules/coreObsidian/storageLib/utilObsidian"; @@ -52,6 +52,10 @@ export class FileAccessObsidian extends FileAccessBase { export async function waitForClosed(harness: LiveSyncHarness): Promise { await delay(100); for (let i = 0; i < 10; i++) { - if (harness.plugin.services.appLifecycle.hasUnloaded()) { - console.log("App Lifecycle has unloaded"); + if (harness.plugin.services.control.hasUnloaded()) { + console.log("App has unloaded"); return; } await delay(100); diff --git a/tsconfig.json b/tsconfig.json index 1ee9b7a..5e8bd6c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,7 @@ "alwaysStrict": true, "allowImportingTsExtensions": true, "verbatimModuleSyntax": true, + "noImplicitOverride": true, "noEmit": true, "lib": ["es2018", "DOM", "ES5", "ES6", "ES7", "es2019.array", "ES2021.WeakRef", "ES2020.BigInt", "ESNext.Intl"], "strictBindCallApply": true, diff --git a/updates.md b/updates.md index 78e7dc2..abb2044 100644 --- a/updates.md +++ b/updates.md @@ -3,11 +3,23 @@ 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. -## --next -- +## 0.25.43-patched-8 + +I really must thank you all. You know that it seems we have just a little more to do. +Note: This version is not fully tested yet. Be careful to use this. Very dogfood-y one. ### Fixed -- Now device name is saved correctly. +- Now the device name is saved correctly. + +### Refactored + +- Add `override` keyword to all overridden items. +- More dynamic binding has been removed. +- The number of inverted dependencies has decreased much more. +- Some check-logic; i.e., like pre-replication check is now separated into check functions and added to the service as handlers, layered. + - This may help with better testing and better maintainability. + ## 0.25.43-patched-7 From f4d8c0a8dbd81023708e41cbe1651e49e530882e Mon Sep 17 00:00:00 2001 From: A-wry Date: Fri, 20 Feb 2026 21:51:30 -0500 Subject: [PATCH 019/339] Add connection warning style logic and settings UI dropdown --- src/modules/features/ModuleLog.ts | 6 +++++- src/modules/features/SettingDialogue/PaneGeneral.ts | 11 +++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/modules/features/ModuleLog.ts b/src/modules/features/ModuleLog.ts index f841256..b2689e2 100644 --- a/src/modules/features/ModuleLog.ts +++ b/src/modules/features/ModuleLog.ts @@ -281,7 +281,11 @@ export class ModuleLog extends AbstractObsidianModule { const fileStatus = this.activeFileStatus.value; if (fileStatus && !this.settings.hideFileWarningNotice) messageLines.push(fileStatus); const messages = (await this.services.appLifecycle.getUnresolvedMessages()).flat().filter((e) => e); - messageLines.push(...messages); + if (this.settings.connectionWarningStyle === "banner") { + messageLines.push(...messages); + } else if (this.settings.connectionWarningStyle === "icon") { + if (messages.length > 0) messageLines.push("🔗❌"); + } this.messageArea.innerText = messageLines.map((e) => `⚠️ ${e}`).join("\n"); } } diff --git a/src/modules/features/SettingDialogue/PaneGeneral.ts b/src/modules/features/SettingDialogue/PaneGeneral.ts index 0a92b7b..4568392 100644 --- a/src/modules/features/SettingDialogue/PaneGeneral.ts +++ b/src/modules/features/SettingDialogue/PaneGeneral.ts @@ -4,6 +4,7 @@ import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts"; import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts"; import type { PageFunctions } from "./SettingPane.ts"; import { visibleOnly } from "./SettingPane.ts"; +import { EVENT_ON_UNRESOLVED_ERROR, eventHub } from "@/common/events.ts"; export function paneGeneral( this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement, @@ -24,6 +25,16 @@ export function paneGeneral( }); new Setting(paneEl).autoWireToggle("showStatusOnStatusbar"); new Setting(paneEl).autoWireToggle("hideFileWarningNotice"); + new Setting(paneEl).autoWireDropDown("connectionWarningStyle", { + options: { + banner: "Show full banner", + icon: "Show icon only", + hidden: "Hide completely", + }, + }); + this.addOnSaved("connectionWarningStyle", () => { + eventHub.emitEvent(EVENT_ON_UNRESOLVED_ERROR); + }); }); void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleLogging")).then((paneEl) => { paneEl.addClass("wizardHidden"); From 4f987e7c2b600de38bc46cf0938e4612bd395ba7 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 21 Feb 2026 14:05:32 +0900 Subject: [PATCH 020/339] ### Fixed - Hidden file synchronisation now works! - Now Hidden file synchronisation respects `.ignore` files. - Replicator initialisation during rebuilding now works correctly. ### Refactored - Some methods naming have been changed for better clarity, i.e., `_isTargetFileByLocalDB` is now `_isTargetAcceptedByLocalDB`. --- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- src/lib | 2 +- src/modules/core/ModuleTargetFilter.ts | 21 ++++++++++++-------- updates.md | 27 ++++++++++++++++++++++++++ 6 files changed, 45 insertions(+), 13 deletions(-) diff --git a/manifest.json b/manifest.json index 9f9365d..c0b1568 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.43-patched-8", + "version": "0.25.43-patched-9", "minAppVersion": "0.9.12", "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 9137591..960247b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-8", + "version": "0.25.43-patched-9", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.43-patched-8", + "version": "0.25.43-patched-9", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index d3858a3..d1ede6c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-8", + "version": "0.25.43-patched-9", "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 d402f2d..d038ee5 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit d402f2d7f3d5677bee4ca11846b0c4f2259840f1 +Subproject commit d038ee5149ccd853210ad755e6951139105e9de6 diff --git a/src/modules/core/ModuleTargetFilter.ts b/src/modules/core/ModuleTargetFilter.ts index 6c5af7e..f4db428 100644 --- a/src/modules/core/ModuleTargetFilter.ts +++ b/src/modules/core/ModuleTargetFilter.ts @@ -47,7 +47,7 @@ export class ModuleTargetFilter extends AbstractModule { totalFileEventCount = 0; - private async _isTargetFileByFileNameDuplication(file: string | UXFileInfoStub) { + private async _isTargetAcceptedByFileNameDuplication(file: string | UXFileInfoStub) { await this.fileCountMap.updateValue(this.totalFileEventCount); const fileCountMap = this.fileCountMap.value; if (!fileCountMap) { @@ -107,7 +107,7 @@ export class ModuleTargetFilter extends AbstractModule { } } - private async _isTargetFileByLocalDB(file: string | UXFileInfoStub) { + private async _isTargetAcceptedByLocalDB(file: string | UXFileInfoStub) { const filepath = getStoragePathFromUXFileInfo(file); if (!this.localDatabase?.isTargetFile(filepath)) { this._log("File is not target by local DB: " + filepath); @@ -117,12 +117,12 @@ export class ModuleTargetFilter extends AbstractModule { return await Promise.resolve(true); } - private async _isTargetFileFinal(file: string | UXFileInfoStub) { + private async _isTargetAcceptedFinally(file: string | UXFileInfoStub) { this._log("File is target finally: " + getStoragePathFromUXFileInfo(file), LOG_LEVEL_DEBUG); return await Promise.resolve(true); } - private async _isTargetIgnoredByIgnoreFiles(file: string | UXFileInfoStub): Promise { + private async _isTargetAcceptedByIgnoreFiles(file: string | UXFileInfoStub): Promise { if (!this.settings.useIgnoreFiles) { return true; } @@ -137,14 +137,19 @@ export class ModuleTargetFilter extends AbstractModule { return true; } + private async _isTargetIgnoredByIgnoreFiles(file: string | UXFileInfoStub) { + const result = await this._isTargetAcceptedByIgnoreFiles(file); + return !result; + } + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.vault.markFileListPossiblyChanged.setHandler(this._markFileListPossiblyChanged.bind(this)); services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this)); services.vault.isIgnoredByIgnoreFile.setHandler(this._isTargetIgnoredByIgnoreFiles.bind(this)); - services.vault.isTargetFile.addHandler(this._isTargetFileByFileNameDuplication.bind(this), 10); - services.vault.isTargetFile.addHandler(this._isTargetIgnoredByIgnoreFiles.bind(this), 20); - services.vault.isTargetFile.addHandler(this._isTargetFileByLocalDB.bind(this), 30); - services.vault.isTargetFile.addHandler(this._isTargetFileFinal.bind(this), 100); + services.vault.isTargetFile.addHandler(this._isTargetAcceptedByFileNameDuplication.bind(this), 10); + services.vault.isTargetFile.addHandler(this._isTargetAcceptedByIgnoreFiles.bind(this), 20); + services.vault.isTargetFile.addHandler(this._isTargetAcceptedByLocalDB.bind(this), 30); + services.vault.isTargetFile.addHandler(this._isTargetAcceptedFinally.bind(this), 100); services.setting.onSettingRealised.addHandler(this.refreshSettings.bind(this)); } } diff --git a/updates.md b/updates.md index abb2044..74067ee 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,33 @@ 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.43-patched-9 a.ka. 0.25.44-rc1 + +We are finally ready for release. I think I will go ahead and release it after using it for a few days. + +### Fixed + +- Hidden file synchronisation now works! +- Now Hidden file synchronisation respects `.ignore` files. +- Replicator initialisation during rebuilding now works correctly. + +### Refactored + +- Some methods naming have been changed for better clarity, i.e., `_isTargetFileByLocalDB` is now `_isTargetAcceptedByLocalDB`. + +### Follow-up tasks memo (After 0.25.44) + +Going forward, functionality that does not span multiple events is expected to be implemented as middleware-style functions rather than modules based on classes. + +Consequently, the existing modules will likely be gradually dismantled. +For reference, `ModuleReplicator.ts` has extracted several functionalities as functions. + +However, this does not negate object-oriented design. Where lifecycles and state are present, and the Liskov Substitution Principle can be upheld, we design using classes. After all, a visible state is preferable to a hidden state. In other words, the handler still accepts both functions and member methods, so formally there is no change. + +As undertaking this for everything would be a bit longer task, I intend to release it at this stage. + +Note: I left using `setHandler`s that as a mark of `need to be refactored`. Basically, they should be implemented in the service itself. That because it is just only a mis-designed separated implementation. + ## 0.25.43-patched-8 I really must thank you all. You know that it seems we have just a little more to do. From 14b4c3cd50c13b01a0363686285b4c91bdfa9c5c Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 21 Feb 2026 14:12:05 +0900 Subject: [PATCH 021/339] prettified --- terser_vite.config.ts | 4 ++-- vite.config.ts | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/terser_vite.config.ts b/terser_vite.config.ts index fce78f0..9fd9ccc 100644 --- a/terser_vite.config.ts +++ b/terser_vite.config.ts @@ -45,5 +45,5 @@ export const terserOption: TerserOptions = { ecma: 2020, safari10: true, webkit: true, - } -} \ No newline at end of file + }, +}; diff --git a/vite.config.ts b/vite.config.ts index 79f9156..0f2c221 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -89,7 +89,6 @@ if (PATH_TEST_INSTALL) { } import { terserOption } from "./terser_vite.config"; export default defineConfig(({ mode }) => { - const prod = mode === "production" || mode === "original"; let minify = prod ? "terser" : false; let outFile = `main_vite.${prod ? "prod" : "dev"}.js`; @@ -129,7 +128,7 @@ export default defineConfig(({ mode }) => { }, }, build: { - target: 'es2018', + target: "es2018", commonjsOptions: {}, lib: { entry: path.resolve(__dirname, "src/main.ts"), @@ -160,5 +159,5 @@ export default defineConfig(({ mode }) => { worker: { format: "iife", }, - } -}) + }; +}); From e961f01187015870b9f65935cd7447bf73b4e524 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 21 Feb 2026 14:17:28 +0900 Subject: [PATCH 022/339] fix typos. --- updates.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/updates.md b/updates.md index 74067ee..a2b70ab 100644 --- a/updates.md +++ b/updates.md @@ -3,7 +3,7 @@ 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.43-patched-9 a.ka. 0.25.44-rc1 +## 0.25.43-patched-9 a.k.a. 0.25.44-rc1 We are finally ready for release. I think I will go ahead and release it after using it for a few days. @@ -28,7 +28,7 @@ However, this does not negate object-oriented design. Where lifecycles and state As undertaking this for everything would be a bit longer task, I intend to release it at this stage. -Note: I left using `setHandler`s that as a mark of `need to be refactored`. Basically, they should be implemented in the service itself. That because it is just only a mis-designed separated implementation. +Note: I left using `setHandler`s that as a mark of `need to be refactored`. Basically, they should be implemented in the service itself. That is because it is just a mis-designed, separated implementation. ## 0.25.43-patched-8 From 310496d0b818d6cba2c7bc0b052268bc144a57b8 Mon Sep 17 00:00:00 2001 From: L4z3x <167608145+L4z3x@users.noreply.github.com> Date: Mon, 23 Feb 2026 22:21:18 +0100 Subject: [PATCH 023/339] added changing docker compose data and etc folder, to prevent permissions errors while trying to follow the docker compose guide i created the data folders using the root user, and had this error when i run the stack: `touch: cannot touch '/opt/couchdb/etc/local.d/docker.ini': Permission denied` the problem was solved by changing the ownership of the folder to the user 5984, then one in the docker compose file. --- docs/setup_own_server.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/setup_own_server.md b/docs/setup_own_server.md index 41f42fd..b444125 100644 --- a/docs/setup_own_server.md +++ b/docs/setup_own_server.md @@ -64,6 +64,10 @@ Congrats, move on to [step 2](#2-run-couchdb-initsh-for-initialise) # Creating the save data & configuration directories. mkdir couchdb-data mkdir couchdb-etc + +# Changing perms to user 5984. +chown -R 5984:5984 ./couchdb-data +chown -R 5984:5984 ./couchdb-etc ``` #### 2. Create a `docker-compose.yml` file with the following added to it From 25dd907591250f3b9f758d67699474e8218aced6 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Tue, 24 Feb 2026 01:57:08 +0000 Subject: [PATCH 024/339] port PR #802 --- src/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib b/src/lib index d038ee5..3d2749c 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit d038ee5149ccd853210ad755e6951139105e9de6 +Subproject commit 3d2749c1674fd215cce65f346eb5e01a4a962955 From fdcf3be0f9247aff02577dd62f5dad3fc2452572 Mon Sep 17 00:00:00 2001 From: A-wry Date: Mon, 23 Feb 2026 22:10:25 -0500 Subject: [PATCH 025/339] Tagged downstream network errors to respect networkWarningStyle setting --- src/lib | 2 +- src/modules/core/ModuleReplicator.ts | 9 ++++-- .../essentialObsidian/ModuleObsidianAPI.ts | 2 +- src/modules/features/ModuleLog.ts | 30 ++++++++++++------- .../features/SettingDialogue/PaneGeneral.ts | 11 +++---- 5 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/lib b/src/lib index 4ff3cad..c228285 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 4ff3cad80b7b42ec6940f765b113bf11d5ba555a +Subproject commit c228285c53148bfdf9886c98d32eabe418e99c61 diff --git a/src/modules/core/ModuleReplicator.ts b/src/modules/core/ModuleReplicator.ts index 08af7fa..b7bff1d 100644 --- a/src/modules/core/ModuleReplicator.ts +++ b/src/modules/core/ModuleReplicator.ts @@ -116,7 +116,8 @@ export class ModuleReplicator extends AbstractModule { } // Showing message is false: that because be shown here. (And it is a fatal error, no way to hide it). if (!(await this.ensureReplicatorPBKDF2Salt(false))) { - this.showError("Failed to initialise the encryption key, preventing replication."); + //tagged as network error at beginning for error filtering with NetworkWarningStyles + this.showError("\u{200b}Failed to initialise the encryption key, preventing replication."); return false; } await this.processor.restoreFromSnapshotOnce(); @@ -218,7 +219,11 @@ Even if you choose to clean up, you will see this option again if you exit Obsid return false; } if (!(await this.services.replication.onBeforeReplicate(showMessage))) { - this.showError($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE); + // check for tagged network errors for filtering by NetworkWarningStyles + const hasNetworkError = [...this._previousErrors].some(e => e.startsWith("\u{200b}")); + if (!hasNetworkError) { + this.showError($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE); + } return false; } this.clearErrors(); diff --git a/src/modules/essentialObsidian/ModuleObsidianAPI.ts b/src/modules/essentialObsidian/ModuleObsidianAPI.ts index cdd085b..f3ca47a 100644 --- a/src/modules/essentialObsidian/ModuleObsidianAPI.ts +++ b/src/modules/essentialObsidian/ModuleObsidianAPI.ts @@ -237,7 +237,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule { } catch (ex: any) { this._log(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL_VERBOSE); const msg = ex instanceof Error ? `${ex?.name}:${ex?.message}` : ex?.toString(); - this.showError(`Failed to fetch: ${msg}`); // Do not show notice, due to throwing below + this.showError(`\u{200b}Network Error: Failed to fetch: ${msg}`); // Do not show notice, due to throwing below this._log(ex, LOG_LEVEL_VERBOSE); // limit only in bulk_docs. if (url.toString().indexOf("_bulk_docs") !== -1) { diff --git a/src/modules/features/ModuleLog.ts b/src/modules/features/ModuleLog.ts index b2689e2..79f24d1 100644 --- a/src/modules/features/ModuleLog.ts +++ b/src/modules/features/ModuleLog.ts @@ -39,6 +39,7 @@ import { isValidFilenameInDarwin, isValidFilenameInWidows, } from "@/lib/src/string_and_binary/path.ts"; +import { NetworkWarningStyles } from "@lib/common/models/setting.const.ts" // This module cannot be a core module because it depends on the Obsidian UI. @@ -155,14 +156,14 @@ export class ModuleLog extends AbstractObsidianModule { lastSyncPushSeq == 0 ? "" : lastSyncPushSeq >= maxPushSeq - ? " (LIVE)" - : ` (${maxPushSeq - lastSyncPushSeq})`; + ? " (LIVE)" + : ` (${maxPushSeq - lastSyncPushSeq})`; pullLast = lastSyncPullSeq == 0 ? "" : lastSyncPullSeq >= maxPullSeq - ? " (LIVE)" - : ` (${maxPullSeq - lastSyncPullSeq})`; + ? " (LIVE)" + : ` (${maxPullSeq - lastSyncPullSeq})`; break; case "ERRORED": w = "⚠"; @@ -281,10 +282,19 @@ export class ModuleLog extends AbstractObsidianModule { const fileStatus = this.activeFileStatus.value; if (fileStatus && !this.settings.hideFileWarningNotice) messageLines.push(fileStatus); const messages = (await this.services.appLifecycle.getUnresolvedMessages()).flat().filter((e) => e); - if (this.settings.connectionWarningStyle === "banner") { - messageLines.push(...messages); - } else if (this.settings.connectionWarningStyle === "icon") { - if (messages.length > 0) messageLines.push("🔗❌"); + const stringMessages = messages.filter((m): m is string => typeof m === "string"); // for 'startsWith' + const networkMessages = stringMessages.filter(m => m.startsWith("\u{200b}")); + const otherMessages = stringMessages.filter(m => !m.startsWith("\u{200b}")); + + messageLines.push(...otherMessages); + + if ( + this.settings.networkWarningStyle !== NetworkWarningStyles.ICON && + this.settings.networkWarningStyle !== NetworkWarningStyles.HIDDEN + ) { + messageLines.push(...networkMessages); + } else if (this.settings.networkWarningStyle === NetworkWarningStyles.ICON) { + if (networkMessages.length > 0) messageLines.push("🔗❌"); } this.messageArea.innerText = messageLines.map((e) => `⚠️ ${e}`).join("\n"); } @@ -443,8 +453,8 @@ export class ModuleLog extends AbstractObsidianModule { typeof message == "string" ? message : message instanceof Error - ? `${errorInfo}` - : JSON.stringify(message, null, 2); + ? `${errorInfo}` + : JSON.stringify(message, null, 2); const newMessage = timestamp + "->" + messageContent; if (message instanceof Error) { console.error(vaultName + ":" + newMessage); diff --git a/src/modules/features/SettingDialogue/PaneGeneral.ts b/src/modules/features/SettingDialogue/PaneGeneral.ts index 4568392..213d27a 100644 --- a/src/modules/features/SettingDialogue/PaneGeneral.ts +++ b/src/modules/features/SettingDialogue/PaneGeneral.ts @@ -5,6 +5,7 @@ import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts import type { PageFunctions } from "./SettingPane.ts"; import { visibleOnly } from "./SettingPane.ts"; import { EVENT_ON_UNRESOLVED_ERROR, eventHub } from "@/common/events.ts"; +import { NetworkWarningStyles } from "@lib/common/models/setting.const.ts"; export function paneGeneral( this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement, @@ -25,14 +26,14 @@ export function paneGeneral( }); new Setting(paneEl).autoWireToggle("showStatusOnStatusbar"); new Setting(paneEl).autoWireToggle("hideFileWarningNotice"); - new Setting(paneEl).autoWireDropDown("connectionWarningStyle", { + new Setting(paneEl).autoWireDropDown("networkWarningStyle", { options: { - banner: "Show full banner", - icon: "Show icon only", - hidden: "Hide completely", + [NetworkWarningStyles.BANNER]: "Show full banner", + [NetworkWarningStyles.ICON]: "Show icon only", + [NetworkWarningStyles.HIDDEN]: "Hide completely", }, }); - this.addOnSaved("connectionWarningStyle", () => { + this.addOnSaved("networkWarningStyle", () => { eventHub.emitEvent(EVENT_ON_UNRESOLVED_ERROR); }); }); From b923b43b6bd1bc0a4ca7966f8d36cbdad763331b Mon Sep 17 00:00:00 2001 From: A-wry Date: Mon, 23 Feb 2026 22:29:46 -0500 Subject: [PATCH 026/339] update submodule pointer to latest commonlib changes --- src/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib b/src/lib index c228285..131121a 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit c228285c53148bfdf9886c98d32eabe418e99c61 +Subproject commit 131121a3043f77339f8b8bc770af568872f0300b From 6eec8117f5ef34d5c05d28deada2268a280e9068 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Tue, 24 Feb 2026 07:51:21 +0000 Subject: [PATCH 027/339] Refactor: Constantising log-mark --- src/lib | 2 +- src/modules/core/ModuleReplicator.ts | 3 ++- src/modules/essentialObsidian/ModuleObsidianAPI.ts | 3 ++- src/modules/features/ModuleLog.ts | 6 +++--- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/lib b/src/lib index b19f087..1c176da 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit b19f0875991a74945db089a3600d7d2469df82c0 +Subproject commit 1c176da469c811cf00a7cd5b16946011b942c8a4 diff --git a/src/modules/core/ModuleReplicator.ts b/src/modules/core/ModuleReplicator.ts index 34a6f55..e059b72 100644 --- a/src/modules/core/ModuleReplicator.ts +++ b/src/modules/core/ModuleReplicator.ts @@ -15,6 +15,7 @@ import { ReplicateResultProcessor } from "./ReplicateResultProcessor"; import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager"; import { clearHandlers } from "@lib/replication/SyncParamsHandler"; import type { NecessaryServices } from "@/serviceFeatures/types"; +import { MARK_LOG_NETWORK_ERROR } from "@lib/services/lib/logUtils"; function isOnlineAndCanReplicate( errorManager: UnresolvedErrorManager, @@ -46,7 +47,7 @@ async function canReplicateWithPBKDF2( errorManager.clearError(errorMessage); // Showing message is false: that because be shown here. (And it is a fatal error, no way to hide it). // tagged as network error at beginning for error filtering with NetworkWarningStyles - const ensureMessage = "\u{200b}Failed to initialise the encryption key, preventing replication."; + const ensureMessage = `${MARK_LOG_NETWORK_ERROR}Failed to initialise the encryption key, preventing replication.`; const ensureResult = await replicator.ensurePBKDF2Salt(currentSettings, showMessage, true); if (!ensureResult) { errorManager.showError(ensureMessage, showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO); diff --git a/src/modules/essentialObsidian/ModuleObsidianAPI.ts b/src/modules/essentialObsidian/ModuleObsidianAPI.ts index d19bb1e..49e81a1 100644 --- a/src/modules/essentialObsidian/ModuleObsidianAPI.ts +++ b/src/modules/essentialObsidian/ModuleObsidianAPI.ts @@ -18,6 +18,7 @@ import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.ts"; import { AuthorizationHeaderGenerator } from "../../lib/src/replication/httplib.ts"; import type { LiveSyncCore } from "../../main.ts"; import { EVENT_ON_UNRESOLVED_ERROR, eventHub } from "../../common/events.ts"; +import { MARK_LOG_NETWORK_ERROR } from "@lib/services/lib/logUtils.ts"; setNoticeClass(Notice); @@ -236,7 +237,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule { } catch (ex: any) { this._log(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL_VERBOSE); const msg = ex instanceof Error ? `${ex?.name}:${ex?.message}` : ex?.toString(); - this.showError(`\u{200b}Network Error: Failed to fetch: ${msg}`); // Do not show notice, due to throwing below + this.showError(`${MARK_LOG_NETWORK_ERROR}Network Error: Failed to fetch: ${msg}`); // Do not show notice, due to throwing below this._log(ex, LOG_LEVEL_VERBOSE); // limit only in bulk_docs. if (url.toString().indexOf("_bulk_docs") !== -1) { diff --git a/src/modules/features/ModuleLog.ts b/src/modules/features/ModuleLog.ts index e743a10..7d9c3e9 100644 --- a/src/modules/features/ModuleLog.ts +++ b/src/modules/features/ModuleLog.ts @@ -39,7 +39,7 @@ import { isValidFilenameInDarwin, isValidFilenameInWidows, } from "@lib/string_and_binary/path.ts"; -import { MARK_LOG_SEPARATOR } from "@lib/services/lib/logUtils.ts"; +import { MARK_LOG_NETWORK_ERROR, MARK_LOG_SEPARATOR } from "@lib/services/lib/logUtils.ts"; import { NetworkWarningStyles } from "@lib/common/models/setting.const.ts"; // This module cannot be a core module because it depends on the Obsidian UI. @@ -284,8 +284,8 @@ export class ModuleLog extends AbstractObsidianModule { if (fileStatus && !this.settings.hideFileWarningNotice) messageLines.push(fileStatus); const messages = (await this.services.appLifecycle.getUnresolvedMessages()).flat().filter((e) => e); const stringMessages = messages.filter((m): m is string => typeof m === "string"); // for 'startsWith' - const networkMessages = stringMessages.filter((m) => m.startsWith("\u{200b}")); - const otherMessages = stringMessages.filter((m) => !m.startsWith("\u{200b}")); + const networkMessages = stringMessages.filter((m) => m.startsWith(MARK_LOG_NETWORK_ERROR)); + const otherMessages = stringMessages.filter((m) => !m.startsWith(MARK_LOG_NETWORK_ERROR)); messageLines.push(...otherMessages); From 988cb34d7cb63bbd07b08067aeb3f84531d3bce7 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Tue, 24 Feb 2026 07:56:23 +0000 Subject: [PATCH 028/339] Update --- devs.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/devs.md b/devs.md index 1d8e954..bb5d584 100644 --- a/devs.md +++ b/devs.md @@ -11,13 +11,28 @@ The plugin uses a dynamic module system to reduce coupling and improve maintaina - **Service Hub**: Central registry for services using dependency injection - Services are registered, and accessed via `this.services` (in most modules) -- **Module Loading**: All modules extend `AbstractModule` or `AbstractObsidianModule` (which extends `AbstractModule`). These modules are loaded in main.ts and some modules +- **Module Loading**: All modules extend `AbstractModule` or `AbstractObsidianModule` (which extends `AbstractModule`). These modules are loaded in main.ts and some modules. - **Module Categories** (by directory): - `core/` - Platform-independent core functionality - `coreObsidian/` - Obsidian-specific core (e.g., `ModuleFileAccessObsidian`) - `essential/` - Required modules (e.g., `ModuleMigration`, `ModuleKeyValueDB`) - `features/` - Optional features (e.g., `ModuleLog`, `ModuleObsidianSettings`) - `extras/` - Development/testing tools (e.g., `ModuleDev`, `ModuleIntegratedTest`) +- **Services**: Core services (e.g., `database`, `replicator`, `storageAccess`) are registered in `ServiceHub` and accessed by modules. They provide an extension point for add new behaviour without modifying existing code. + - For example, checks before the replication can be added to the `replication.onBeforeReplicate` handler, and the handlers can be return `false` to prevent replication-starting. `vault.isTargetFile` also can be used to prevent processing specific files. +- **ServiceModule**: A new type of module that directly depends on services. + +#### Note on Module vs Service + +After v0.25.44 refactoring, the Service will henceforth, as a rule, cease to use setHandler, that is to say, simple lazy binding. - They will be implemented directly in the service. - However, not everything will be middlewarised. Modules that maintain state or make decisions based on the results of multiple handlers are permitted. + +Hence, the new feature should be implemented as follows: + +- If it is a simple extension point (e.g., adding a check before replication), it should be implemented as a handler in the service (e.g., `replication.onBeforeReplicate`). +- If it requires maintaining state or making decisions based on multiple handlers, it should be implemented as a serviceModule dependent on the relevant services explicitly. +- If you have to implement a new feature without much modification, you can extent existing modules, but it is recommended to implement a new module or serviceModule for better maintainability. +- Refactoring existing modules to services is also always welcome! +- Please write tests for new features, you will notice that the simple handler approach is quite testable. ### Key Architectural Components From 8c0c65307ae830692d9061e7c121792449eece91 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Tue, 24 Feb 2026 08:23:08 +0000 Subject: [PATCH 029/339] bump --- manifest.json | 2 +- package-lock.json | 4 +- package.json | 2 +- updates.md | 285 ++++-------------------------- updates_old.md | 428 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 469 insertions(+), 252 deletions(-) diff --git a/manifest.json b/manifest.json index c0b1568..801f131 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.43-patched-9", + "version": "0.25.44", "minAppVersion": "0.9.12", "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 960247b..8ed4978 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-9", + "version": "0.25.44", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.43-patched-9", + "version": "0.25.44", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index d1ede6c..33e92b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.43-patched-9", + "version": "0.25.44", "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 a2b70ab..bfacd7a 100644 --- a/updates.md +++ b/updates.md @@ -3,223 +3,56 @@ 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.43-patched-9 a.k.a. 0.25.44-rc1 +## 0.25.44 -We are finally ready for release. I think I will go ahead and release it after using it for a few days. +24th February, 2026 + +This release represents a significant architectural overhaul of the plug-in, focusing on modularity, testability, and stability. While many changes are internal, they pave the way for more robust features and easier maintenance. +However, as this update is very substantial, please do feel free to let me know if you encounter any issues. ### Fixed -- Hidden file synchronisation now works! -- Now Hidden file synchronisation respects `.ignore` files. -- Replicator initialisation during rebuilding now works correctly. +- Ignore files (e.g., `.ignore`) are now handled efficiently. +- Replication & Database: + - Replication statistics are now correctly reset after switching replicators. +- Fixed `File already exists` for .md files has been merged (PR #802) So thanks @waspeer for the contribution! + +### Improved + +- Now we can configure network-error banners as icons, or hide them completely with the new `Network Warning Style` setting in the `General` pane of the settings dialogue. (#770, PR #804) + - Thanks so much to @A-wry! ### Refactored -- Some methods naming have been changed for better clarity, i.e., `_isTargetFileByLocalDB` is now `_isTargetAcceptedByLocalDB`. +Architectural Overhaul: -### Follow-up tasks memo (After 0.25.44) +- A major transition from Class-based Modules to a Service/Middleware architecture has begun. + - Many modules (for example, `ModulePouchDB`, `ModuleLocalDatabaseObsidian`, `ModuleKeyValueDB`) have been removed or integrated into specific Services (`database`, `keyValueDB`, etc.). + - Reduced reliance on dynamic binding and inverted dependencies; dependencies are now explicit. + - `ObsidianLiveSyncPlugin` properties (`replicator`, `localDatabase`, `storageAccess`, etc.) have been moved to their respective services for better separation of concerns. + - In this refactoring, the Service will henceforth, as a rule, cease to use setHandler, that is to say, simple lazy binding. + - They will be implemented directly in the service. + - However, not everything will be middlewarised. Modules that maintain state or make decisions based on the results of multiple handlers are permitted. +- Lifecycle: + - Application LifeCycle now starts in `Main` rather than `ServiceHub` or `ObsidianMenuModule`, ensuring smoother startup coordination. -Going forward, functionality that does not span multiple events is expected to be implemented as middleware-style functions rather than modules based on classes. +New Services & Utilities: -Consequently, the existing modules will likely be gradually dismantled. -For reference, `ModuleReplicator.ts` has extracted several functionalities as functions. +- Added a `control` service to orchestrate other services (for example, handling stop/start logic during settings realisation). +- Added `UnresolvedErrorManager` to handle and display unresolved errors in a unified way. +- Added `logUtils` to unify logging injection and formatting. +- `VaultService.isTargetFile` now uses multiple, distinct checkers for better extensibility. -However, this does not negate object-oriented design. Where lifecycles and state are present, and the Liskov Substitution Principle can be upheld, we design using classes. After all, a visible state is preferable to a hidden state. In other words, the handler still accepts both functions and member methods, so formally there is no change. +Code Separation: -As undertaking this for everything would be a bit longer task, I intend to release it at this stage. +- Separated Obsidian-specific logic from base logic for `StorageEventManager` and `FileAccess` modules. +- Moved reactive state values and statistics from the main plug-in instance to the services responsible for them. -Note: I left using `setHandler`s that as a mark of `need to be refactored`. Basically, they should be implemented in the service itself. That is because it is just a mis-designed, separated implementation. +Internal Cleanups: -## 0.25.43-patched-8 - -I really must thank you all. You know that it seems we have just a little more to do. -Note: This version is not fully tested yet. Be careful to use this. Very dogfood-y one. - -### Fixed - -- Now the device name is saved correctly. - -### Refactored - -- Add `override` keyword to all overridden items. -- More dynamic binding has been removed. -- The number of inverted dependencies has decreased much more. -- Some check-logic; i.e., like pre-replication check is now separated into check functions and added to the service as handlers, layered. - - This may help with better testing and better maintainability. - - -## 0.25.43-patched-7 - -19th February, 2026 - -Right then, let us make a decision already. - -Last time, since I found a bug, I ended up doing a few other things as well, but next time I intend to release it with just the bug fix. It is quite substantial, after all. - -Customisation Sync has mostly been verified. Hidden file synchronisation has not been done yet. - -Vite's build system is not in the production. However, I possibly migrate to it in the future. - -And, the `daily-progress` will be tidied on releasing 0.25.44. Do not worry! - -### Fixed - -- Fixed an issue where the StorageEventManager was not correctly loading the settings. -- Replication statistics are now correctly reset after switching replicators. - -### Refactored - -- Now, many reactive values which keep the state or statistics of the plugin are moved to the services which have the responsibility for these states. -- `serviceFeatures` are now able to be added to the services; this is not a class module, but a function which accepts dependencies and returns an addHandler-able function. This is for better separation of concerns, better maintainability, and testability. -- `control` service; is a meta-service which is responsible for orchestrating services has been added. - - Don't you think stopping replication or something occurs during `settingService.realiseSetting` is quite weird? It may be done by the control service, which can orchestrate the setting service and the replicator service. - - -- Some functions on services have been moved. e.g., `getSystemVaultName` is now on the API service. -- Setting Service is now responsible for the setting, no longer using dynamic binding for the modules. - -## 0.25.43-patched-6 - -18th February, 2026 - -Let me confess that I have lied about `now all ambiguous properties`... I have found some more implicit calling. - -Note: I have not checked hidden file sync and customisation sync yet. Please report if you find any unexpected behaviour in these features. - -### Fixed - -- Now ReplicatorService responds to database reset and database initialisation events to dispose of the active replicator. - - Fixes some unlocking issues during rebuilding. - -### Refactored - -- Now `StorageEventManagerBase` is separated from `StorageEventManagerObsidian` following their concerns. - - No longer using `ObsidianFileAccess` indirectly during checking duplicated-file events. - - Last event memorisation is now moved into the StorageAccessManager, just like the file processing interlocking. - - These methods, i.e., `ObsidianFileAccess.touch`. `StorageEventManager.recentlyTouched`, and `StorageEventManager.touch` are still available, but simply call the StorageAccessManager's methods. -- Now `FileAccessBase` is separated from `FileAccessObsidian` following their concerns. - -## 0.25.43-patched-5 - -17th February, 2026 - -Yes, we mostly have got refactored! - -### Refactored - -- Following properties of `ObsidianLiveSyncPlugin` are now initialised more explicitly: - - - property : what is responsible - - `storageAccess` : `ServiceFileAccessObsidian` - - `databaseFileAccess` : `ServiceDatabaseFileAccess` - - `fileHandler` : `ServiceFileHandler` - - `rebuilder` : `ServiceRebuilder` - - Not so long from now, ServiceFileAccessObsidian might be abstracted to a more general FileAccessService, and make more testable and maintainable. - - These properties are initialised in `initialiseServiceModules` on `ObsidianLiveSyncPlugin`. - - They are `ServiceModule`s. - - Which means they do not use dynamic binding themselves, but they use bound services. - - ServiceModules are in src/lib/src/serviceModules for common implementations, and src/serviceModules for Obsidian-specific implementations. - - Hence, now all ambiguous properties of `ObsidianLiveSyncPlugin` are initialised explicitly. We can proceed to testing. - - Well, I will release v0.25.44 after testing this. - -- Conflict service is now responsible for `resolveAllConflictedFilesByNewerOnes` function, which has been in the rebuilder. -- New functions `updateSettings`, and `applyPartial` have been added to the setting service. We should use these functions instead of directly writing the settings on `ObsidianLiveSyncPlugin.setting`. -- Some interfaces for services have been moved to src/lib/src/interfaces. -- `RemoteService.tryResetDatabase` and `tryCreateDatabase` are now moved to the replicator service. - - You know that these functions are surely performed by the replicator. - - Probably, most of the functions in `RemoteService` should be moved to the replicator service, but for now, these two functions are moved as they are the most related ones, to rewrite the rebuilder service. -- Common functions are gradually moved to the common library. -- Now, binding functions on modules have been delayed until the services and service modules are initialised, to avoid fragile behaviour. - -## 0.25.43-patched-4 - -16th February, 2026 - -I have been working on it little by little in my spare time. Sorry for the delayed response for issues! ! However, thanks for your patience, we seems the `revert to 0.25.43` is not necessary, and I will keep going with this version. - -### Refactored - -- No longer `DatabaseService` is an injectable service. It is now actually a service which has its own handlers. No dynamic binding for necessary functions. -- Now the following properties of `ObsidianLiveSyncPlugin` belong to each service: - - `replicator` : `services.replicator` (still we can access `ObsidianLiveSyncPlugin.replicator` for the active replicator) -- A Handy class `UnresolvedErrorManager` has been added, which is responsible for managing unresolved errors and their handlers (we will see `unresolved errors` on a red-background-banner in the editor when they occur). - - This manager can be used to handle unresolved errors in a unified way, and it can also be used to display notifications or something when unresolved errors occur. - -## 0.25.43-patched-3 - -16th February, 2026 - -### Refactored - -- Now following properties of `ObsidianLiveSyncPlugin` belong to each service: - - property : service (still we can access these properties from `ObsidianLiveSyncPlugin` for better usability, but probably we should access these from services to clarify the dependencies) - - `localDatabase` : `services.database` - - `managers` : `services.database` - - `simpleStore` : `services.keyValueDB` - - `kvDB`: `services.keyValueDB` -- Initialising modules, addOns, and services are now explicitly separated in the `_startUp` function of the main plug-in class. -- LiveSyncLocalDB now depends more explicitly on specified services, not the whole `ServiceHub`. -- New service `keyValueDB` has been added. This had been separated from the `database` service. -- Non-trivial modules, such as `ModuleExtraSyncObsidian` (which only holds deviceAndVaultName), are simply implemented in the service. -- Add `logUtils` for unifying logging method injection and formatting. This utility is able to accept the API service for log writing. -- `ModuleKeyValueDB` has been removed, and its functionality is now implemented in the `keyValueDB` service. -- `ModulePouchDB` and `ModuleLocalDatabaseObsidian` have been removed, and their functionality is now implemented in the `database` service. - - Please be aware that you have overridden createPouchDBInstance or something by dynamic binding; you should now override the createPouchDBInstance in the database service instead of using the module. - - You can refer to the `DirectFileManipulatorV2` for an example of how to override the createPouchDBInstance function in the database service. - -## 0.25.43-patched-2 - -14th February, 2026 - -### Fixed - -- Application LifeCycle has now started in Main, not ServiceHub. - - Indeed, ServiceHub cannot be known other things in main have got ready, so it is quite natural to start the lifecycle in main. - -## 0.25.43-patched-1 - -13th February, 2026 - -**NOTE: Hidden File Sync and Customisation Sync may not work in this version.** - -Just a heads-up: this is a patch version, which is essentially a beta release. Do not worry about the following memos, as they are indeed freaking us out. I trust that you have thought this was too large; you're right. - -If this cannot be stable, I will revert to 0.24.43 and try again. - -### Refactored - -- Now resolving unexpected and inexplicable dependency order issues... -- The function which is able to implement to the service is now moved to each service. - - AppLifecycleService.performRestart -- VaultService.isTargetFile is now using multiple checkers instead of a single function. - - This change allows better separation of concerns and easier extension in the future. -- Application LifeCycle has now started in ServiceHub, not ObsidianMenuModule. - - - It was in a QUITE unexpected place..., isn't it? - - Instead of, we should call `await this.services.appLifecycle.onReady()` in other platforms. - - As in the browser platform, it will be called at `DOMContentLoaded` event. - -- ModuleTargetFilter, which is responsible for parsing ignore files, has been refined. - - This should be separated to a TargetFilter and an IgnoreFileFilter for better maintainability. -- Using `API.addCommand` or some Obsidian API and shimmer APIs, Many modules have been refactored to be derived to AbstractModule from AbstractObsidianModule, to clarify the dependencies. (we should make `app` usage clearer...) -- Fixed initialising `storageAccess` too late in `FileAccessObsidian` module (I am still wondering why it worked before...). -- Remove some redundant overrides in modules. - -### Planned - -- Some services have an ambiguous name, such as `Injectable`. These will be renamed in the future for better clarity. -- Following properties of `ObsidianLiveSyncPlugin` should be initialised more explicitly: - - property : where it is initialised currently - - `localDatabase` : `ModuleLocalDatabaseObsidian` - - `managers` : `ModuleLocalDatabaseObsidian` - - `replicator` : `ModuleReplicator` - - `simpleStore` : `ModuleKeyValueDB` - - `storageAccess` : `ModuleFileAccessObsidian` - - `databaseFileAccess` : `ModuleDatabaseFileAccess` - - `fileHandler` : `ModuleFileHandler` - - `rebuilder` : `ModuleRebuilder` - - `kvDB`: `ModuleKeyValueDB` - - And I think that having a feature in modules directly is not good for maintainability, these should be separated to some module (loader) and implementation (not only service, but also independent something). -- Plug-in statuses such as requestCount, responseCount... should be moved to a status service or somewhere for better separation of concerns. +- Many functions have been renamed for clarity (for example, `_isTargetFileByLocalDB` is now `_isTargetAcceptedByLocalDB`). +- Added `override` keywords to overridden items and removed dynamic binding for clearer code inheritance. +- Moved common functions to the common library. ## 0.25.43 @@ -379,49 +212,5 @@ Sorry for a small release! I would like to keep things moving along like this if - Storage application process has been refactored. - Please report if you find any unexpected behaviour after this update. A bit of large refactoring. -## 0.25.33 - -05th December, 2025 - -### New feature - -- We can analyse the local database with the `Analyse database usage` command. - - This command makes a TSV-style report of the database usage, which can be pasted into spreadsheet applications. - - The report contains the number of unique chunks and shared chunks for each document revision. - - Unique chunks indicate the actual consumption. - - Shared chunks indicate the reference counts from other chunks with no consumption. - - We can find which notes or files are using large amounts of storage in the database. Or which notes cannot share chunks effectively. - - This command is useful when optimising the database size or investigating an unexpectedly large database size. -- We can reset the notification threshold and check the remote usage at once with the `Reset notification threshold and check the remote database usage` command. -- Commands are available from the Command Palette, or `Hatch` pane in the settings dialogue. - -### Fixed - -- Now the plug-in resets the remote size notification threshold after rebuild. - -## 0.25.32 - -02nd December, 2025 - -Now I am back from a short (?) break! Thank you all for your patience. (It is nothing major, but the first half of the year has finally come to an end). -Anyway, I will release the things a bit by bit. I think that we need a rehabilitation or getting gears in again. - -### Improved - -- Now the plugin warns when we are in several file-related situations that may cause unexpected behaviour (#300). - - These errors are displayed alongside issues such as file size exceeding limits. - - Such situations include: - - When the document has a name which is not supported by some file systems. - - When the vault has the same file names with different letter cases. - -## 0.25.31 - -18th November, 2025 - -### Fixed - -- Now fetching configuration from the server can handle the empty remote correctly (reported on #756). -- No longer asking to switch adapters during rebuilding. - -Older notes are in +Full notes are in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). diff --git a/updates_old.md b/updates_old.md index 1d21094..5c0c3b0 100644 --- a/updates_old.md +++ b/updates_old.md @@ -1,4 +1,431 @@ # 0.25 +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.43-patched-9 a.k.a. 0.25.44-rc1 + +We are finally ready for release. I think I will go ahead and release it after using it for a few days. + +### Fixed + +- Hidden file synchronisation now works! +- Now Hidden file synchronisation respects `.ignore` files. +- Replicator initialisation during rebuilding now works correctly. + +### Refactored + +- Some methods naming have been changed for better clarity, i.e., `_isTargetFileByLocalDB` is now `_isTargetAcceptedByLocalDB`. + +### Follow-up tasks memo (After 0.25.44) + +Going forward, functionality that does not span multiple events is expected to be implemented as middleware-style functions rather than modules based on classes. + +Consequently, the existing modules will likely be gradually dismantled. +For reference, `ModuleReplicator.ts` has extracted several functionalities as functions. + +However, this does not negate object-oriented design. Where lifecycles and state are present, and the Liskov Substitution Principle can be upheld, we design using classes. After all, a visible state is preferable to a hidden state. In other words, the handler still accepts both functions and member methods, so formally there is no change. + +As undertaking this for everything would be a bit longer task, I intend to release it at this stage. + +Note: I left using `setHandler`s that as a mark of `need to be refactored`. Basically, they should be implemented in the service itself. That is because it is just a mis-designed, separated implementation. + +## 0.25.43-patched-8 + +I really must thank you all. You know that it seems we have just a little more to do. +Note: This version is not fully tested yet. Be careful to use this. Very dogfood-y one. + +### Fixed + +- Now the device name is saved correctly. + +### Refactored + +- Add `override` keyword to all overridden items. +- More dynamic binding has been removed. +- The number of inverted dependencies has decreased much more. +- Some check-logic; i.e., like pre-replication check is now separated into check functions and added to the service as handlers, layered. + - This may help with better testing and better maintainability. + + +## 0.25.43-patched-7 + +19th February, 2026 + +Right then, let us make a decision already. + +Last time, since I found a bug, I ended up doing a few other things as well, but next time I intend to release it with just the bug fix. It is quite substantial, after all. + +Customisation Sync has mostly been verified. Hidden file synchronisation has not been done yet. + +Vite's build system is not in the production. However, I possibly migrate to it in the future. + +And, the `daily-progress` will be tidied on releasing 0.25.44. Do not worry! + +### Fixed + +- Fixed an issue where the StorageEventManager was not correctly loading the settings. +- Replication statistics are now correctly reset after switching replicators. + +### Refactored + +- Now, many reactive values which keep the state or statistics of the plugin are moved to the services which have the responsibility for these states. +- `serviceFeatures` are now able to be added to the services; this is not a class module, but a function which accepts dependencies and returns an addHandler-able function. This is for better separation of concerns, better maintainability, and testability. +- `control` service; is a meta-service which is responsible for orchestrating services has been added. + - Don't you think stopping replication or something occurs during `settingService.realiseSetting` is quite weird? It may be done by the control service, which can orchestrate the setting service and the replicator service. + - +- Some functions on services have been moved. e.g., `getSystemVaultName` is now on the API service. +- Setting Service is now responsible for the setting, no longer using dynamic binding for the modules. + +## 0.25.43-patched-6 + +18th February, 2026 + +Let me confess that I have lied about `now all ambiguous properties`... I have found some more implicit calling. + +Note: I have not checked hidden file sync and customisation sync yet. Please report if you find any unexpected behaviour in these features. + +### Fixed + +- Now ReplicatorService responds to database reset and database initialisation events to dispose of the active replicator. + - Fixes some unlocking issues during rebuilding. + +### Refactored + +- Now `StorageEventManagerBase` is separated from `StorageEventManagerObsidian` following their concerns. + - No longer using `ObsidianFileAccess` indirectly during checking duplicated-file events. + - Last event memorisation is now moved into the StorageAccessManager, just like the file processing interlocking. + - These methods, i.e., `ObsidianFileAccess.touch`. `StorageEventManager.recentlyTouched`, and `StorageEventManager.touch` are still available, but simply call the StorageAccessManager's methods. +- Now `FileAccessBase` is separated from `FileAccessObsidian` following their concerns. + +## 0.25.43-patched-5 + +17th February, 2026 + +Yes, we mostly have got refactored! + +### Refactored + +- Following properties of `ObsidianLiveSyncPlugin` are now initialised more explicitly: + + - property : what is responsible + - `storageAccess` : `ServiceFileAccessObsidian` + - `databaseFileAccess` : `ServiceDatabaseFileAccess` + - `fileHandler` : `ServiceFileHandler` + - `rebuilder` : `ServiceRebuilder` + - Not so long from now, ServiceFileAccessObsidian might be abstracted to a more general FileAccessService, and make more testable and maintainable. + - These properties are initialised in `initialiseServiceModules` on `ObsidianLiveSyncPlugin`. + - They are `ServiceModule`s. + - Which means they do not use dynamic binding themselves, but they use bound services. + - ServiceModules are in src/lib/src/serviceModules for common implementations, and src/serviceModules for Obsidian-specific implementations. + - Hence, now all ambiguous properties of `ObsidianLiveSyncPlugin` are initialised explicitly. We can proceed to testing. + - Well, I will release v0.25.44 after testing this. + +- Conflict service is now responsible for `resolveAllConflictedFilesByNewerOnes` function, which has been in the rebuilder. +- New functions `updateSettings`, and `applyPartial` have been added to the setting service. We should use these functions instead of directly writing the settings on `ObsidianLiveSyncPlugin.setting`. +- Some interfaces for services have been moved to src/lib/src/interfaces. +- `RemoteService.tryResetDatabase` and `tryCreateDatabase` are now moved to the replicator service. + - You know that these functions are surely performed by the replicator. + - Probably, most of the functions in `RemoteService` should be moved to the replicator service, but for now, these two functions are moved as they are the most related ones, to rewrite the rebuilder service. +- Common functions are gradually moved to the common library. +- Now, binding functions on modules have been delayed until the services and service modules are initialised, to avoid fragile behaviour. + +## 0.25.43-patched-4 + +16th February, 2026 + +I have been working on it little by little in my spare time. Sorry for the delayed response for issues! ! However, thanks for your patience, we seems the `revert to 0.25.43` is not necessary, and I will keep going with this version. + +### Refactored + +- No longer `DatabaseService` is an injectable service. It is now actually a service which has its own handlers. No dynamic binding for necessary functions. +- Now the following properties of `ObsidianLiveSyncPlugin` belong to each service: + - `replicator` : `services.replicator` (still we can access `ObsidianLiveSyncPlugin.replicator` for the active replicator) +- A Handy class `UnresolvedErrorManager` has been added, which is responsible for managing unresolved errors and their handlers (we will see `unresolved errors` on a red-background-banner in the editor when they occur). + - This manager can be used to handle unresolved errors in a unified way, and it can also be used to display notifications or something when unresolved errors occur. + +## 0.25.43-patched-3 + +16th February, 2026 + +### Refactored + +- Now following properties of `ObsidianLiveSyncPlugin` belong to each service: + - property : service (still we can access these properties from `ObsidianLiveSyncPlugin` for better usability, but probably we should access these from services to clarify the dependencies) + - `localDatabase` : `services.database` + - `managers` : `services.database` + - `simpleStore` : `services.keyValueDB` + - `kvDB`: `services.keyValueDB` +- Initialising modules, addOns, and services are now explicitly separated in the `_startUp` function of the main plug-in class. +- LiveSyncLocalDB now depends more explicitly on specified services, not the whole `ServiceHub`. +- New service `keyValueDB` has been added. This had been separated from the `database` service. +- Non-trivial modules, such as `ModuleExtraSyncObsidian` (which only holds deviceAndVaultName), are simply implemented in the service. +- Add `logUtils` for unifying logging method injection and formatting. This utility is able to accept the API service for log writing. +- `ModuleKeyValueDB` has been removed, and its functionality is now implemented in the `keyValueDB` service. +- `ModulePouchDB` and `ModuleLocalDatabaseObsidian` have been removed, and their functionality is now implemented in the `database` service. + - Please be aware that you have overridden createPouchDBInstance or something by dynamic binding; you should now override the createPouchDBInstance in the database service instead of using the module. + - You can refer to the `DirectFileManipulatorV2` for an example of how to override the createPouchDBInstance function in the database service. + +## 0.25.43-patched-2 + +14th February, 2026 + +### Fixed + +- Application LifeCycle has now started in Main, not ServiceHub. + - Indeed, ServiceHub cannot be known other things in main have got ready, so it is quite natural to start the lifecycle in main. + +## 0.25.43-patched-1 + +13th February, 2026 + +**NOTE: Hidden File Sync and Customisation Sync may not work in this version.** + +Just a heads-up: this is a patch version, which is essentially a beta release. Do not worry about the following memos, as they are indeed freaking us out. I trust that you have thought this was too large; you're right. + +If this cannot be stable, I will revert to 0.24.43 and try again. + +### Refactored + +- Now resolving unexpected and inexplicable dependency order issues... +- The function which is able to implement to the service is now moved to each service. + - AppLifecycleService.performRestart +- VaultService.isTargetFile is now using multiple checkers instead of a single function. + - This change allows better separation of concerns and easier extension in the future. +- Application LifeCycle has now started in ServiceHub, not ObsidianMenuModule. + + - It was in a QUITE unexpected place..., isn't it? + - Instead of, we should call `await this.services.appLifecycle.onReady()` in other platforms. + - As in the browser platform, it will be called at `DOMContentLoaded` event. + +- ModuleTargetFilter, which is responsible for parsing ignore files, has been refined. + - This should be separated to a TargetFilter and an IgnoreFileFilter for better maintainability. +- Using `API.addCommand` or some Obsidian API and shimmer APIs, Many modules have been refactored to be derived to AbstractModule from AbstractObsidianModule, to clarify the dependencies. (we should make `app` usage clearer...) +- Fixed initialising `storageAccess` too late in `FileAccessObsidian` module (I am still wondering why it worked before...). +- Remove some redundant overrides in modules. + +### Planned + +- Some services have an ambiguous name, such as `Injectable`. These will be renamed in the future for better clarity. +- Following properties of `ObsidianLiveSyncPlugin` should be initialised more explicitly: + - property : where it is initialised currently + - `localDatabase` : `ModuleLocalDatabaseObsidian` + - `managers` : `ModuleLocalDatabaseObsidian` + - `replicator` : `ModuleReplicator` + - `simpleStore` : `ModuleKeyValueDB` + - `storageAccess` : `ModuleFileAccessObsidian` + - `databaseFileAccess` : `ModuleDatabaseFileAccess` + - `fileHandler` : `ModuleFileHandler` + - `rebuilder` : `ModuleRebuilder` + - `kvDB`: `ModuleKeyValueDB` + - And I think that having a feature in modules directly is not good for maintainability, these should be separated to some module (loader) and implementation (not only service, but also independent something). +- Plug-in statuses such as requestCount, responseCount... should be moved to a status service or somewhere for better separation of concerns. + +## 0.25.43 + +5th, February, 2026 + +### Fixed + +- Encryption/decryption issues when using Object Storage as remote have been fixed. + - Now the plug-in falls back to V1 encryption/decryption when V2 fails (if not configured as ForceV1). + - This may fix the issue reported in #772. + +### Notice + +Quite a few packages have been updated in this release. Please report if you find any unexpected behaviour after this update. + +## 0.25.42 + +2nd, February, 2026 + +This release is identical to 0.25.41-patched-3, except for the version number. + +### Refactored + +- Now the service context is `protected` instead of `private` in `ServiceBase`. + - This change allows derived classes to access the context directly. +- Some dynamically bound services have been moved to services for better dependency management. +- `WebPeer` has been moved to the main repository from the sub repository `livesync-commonlib` for correct dependency management. +- Migrated from the outdated, unstable platform abstraction layer to services. + - A bit more services will be added in the future for better maintainability. + +## 0.25.41 + +24th January, 2026 + +### Fixed + +- No longer `No available splitter for settings!!` errors occur after fetching old remote settings while rebuilding local database. (#748) + +### Improved + +- Boot sequence warning is now kept in the in-editor notification area. + +### New feature + +- We can now set the maximum modified time for reflect events in the settings. (for #754) + - This setting can be configured from `Patches` -> `Remediation` in the settings dialogue. + - Enabling this setting will restrict the propagation from the database to storage to only those changes made before the specified date and time. + - This feature is primarily intended for recovery purposes. After placing `redflag.md` in an empty vault and importing the Self-hosted LiveSync configuration, please perform this configuration, and then fetch the local database from the remote. + - This feature is useful when we want to prevent recent unwanted changes from being reflected in the local storage. + +### Refactored + +- Module to service refactoring has been started for better maintainability: + - UI module has been moved to UI service. + +### Behaviour change + +- Default chunk splitter version has been changed to `Rabin-Karp` for new installations. + +## 0.25.40 + +23rd January, 2026 + +### Fixed + +- Fixed an issue where some events were not triggered correctly after the refactoring in 0.25.39. + +## 0.25.39 + +23rd January, 2026 + +Also no behaviour changes or fixes in this release. Just refactoring for better maintainability. Thank you for your patience! I will address some of the reported issues soon. +However, this is not a minor refactoring, so please be careful. Let me know if you find any unexpected behaviour after this update. + +### Refactored + +- Rewrite the service's binding/handler assignment systems +- Removed loopholes that allowed traversal between services to clarify dependencies. +- Consolidated the hidden state-related state, the handler, and the addition of bindings to the handler into a single object. + - Currently, functions that can have handlers added implement either addHandler or setHandler directly on the function itself. + I understand there are differing opinions on this, but for now, this is how it stands. +- Services now possess a Context. Please ensure each platform has a class that inherits from ServiceContext. +- To permit services to be dynamically bound, the services themselves are now defined by interfaces. + +## 0.25.38 + +17th January, 2026 + +### Fixed + +- Fixed an issue where indexedDB would not close correctly on some environments, causing unexpected errors during database operations. + +## 0.25.37 + +15th January, 2026 + +Thank you for your patience until my return! + +This release contains minor changes discovered and fixed during test implementation. +There are no changes affecting usage. + +### Refactored + +- Logging system has been slightly refactored to improve maintainability. +- Some import statements have been unified. + +## 0.25.36 + +25th December, 2025 + +### Improved + +- Now the garbage collector (V3) has been implemented. (Beta) + - This garbage collector ensures that all devices are synchronised to the latest progress to prevent inconsistencies. + - In other words, it makes sure that no new conflicts would have arisen. + - This feature requires additional information (via node information), but it should be more reliable. + - This feature requires all devices have v0.25.36 or later. + - After the garbage collector runs, the database size may be reduced (Compaction will be run automatically after GC). + - We should have an administrative privilege on the remote database to run this garbage collector. +- Now the plug-in and device information is stored in the remote database. + - This information is used for the garbage collector (V3). + - Some additional features may be added in the future using this information. + +## 0.25.35 + +24th December, 2025 + +Sorry for a small release! I would like to keep things moving along like this if possible. After all, the holidays seem to be starting soon. I will be doubled by my business until the 27th though, indeed. + +### Fixed + +- Now the conflict resolution dialogue shows correctly which device only has older APIs (#764). + +## 0.25.34 + +10th December, 2025 + +### Behaviour change + +- The plug-in automatically fetches the missing chunks even if `Fetch chunks on demand` is disabled. + - This change is to avoid loss of data when receiving a bulk of revisions. + - This can be prevented by enabling `Use Only Local Chunks` in the settings. +- Storage application now saved during each event and restored on startup. +- Synchronisation result application is also now saved during each event and restored on startup. + - These may avoid some unexpected loss of data when the editor crashes. + +### Fixed + +- Now the plug-in waits for the application of pended batch changes before the synchronisation starts. + - This may avoid some unexpected loss or unexpected conflicts. + Plug-in sends custom headers correctly when RequestAPI is used. +- No longer causing unexpected chunk creation during `Reset synchronisation on This Device` with bucket sync. + +### Refactored + +- Synchronisation result application process has been refactored. +- Storage application process has been refactored. + - Please report if you find any unexpected behaviour after this update. A bit of large refactoring. + +## 0.25.33 + +05th December, 2025 + +### New feature + +- We can analyse the local database with the `Analyse database usage` command. + - This command makes a TSV-style report of the database usage, which can be pasted into spreadsheet applications. + - The report contains the number of unique chunks and shared chunks for each document revision. + - Unique chunks indicate the actual consumption. + - Shared chunks indicate the reference counts from other chunks with no consumption. + - We can find which notes or files are using large amounts of storage in the database. Or which notes cannot share chunks effectively. + - This command is useful when optimising the database size or investigating an unexpectedly large database size. +- We can reset the notification threshold and check the remote usage at once with the `Reset notification threshold and check the remote database usage` command. +- Commands are available from the Command Palette, or `Hatch` pane in the settings dialogue. + +### Fixed + +- Now the plug-in resets the remote size notification threshold after rebuild. + +## 0.25.32 + +02nd December, 2025 + +Now I am back from a short (?) break! Thank you all for your patience. (It is nothing major, but the first half of the year has finally come to an end). +Anyway, I will release the things a bit by bit. I think that we need a rehabilitation or getting gears in again. + +### Improved + +- Now the plugin warns when we are in several file-related situations that may cause unexpected behaviour (#300). + - These errors are displayed alongside issues such as file size exceeding limits. + - Such situations include: + - When the document has a name which is not supported by some file systems. + - When the vault has the same file names with different letter cases. + +## 0.25.31 + +18th November, 2025 + +### Fixed + +- Now fetching configuration from the server can handle the empty remote correctly (reported on #756). +- No longer asking to switch adapters during rebuilding. + +# 0.25 + +(0.25.0 through 0.25.30) Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025) @@ -9,6 +436,7 @@ I have now rewritten the E2EE code to be more robust and easier to understand. I As a result, this is the first time in a while that forward compatibility has been broken. We have also taken the opportunity to change all metadata to use encryption rather than obfuscation. Furthermore, the `Dynamic Iteration Count` setting is now redundant and has been moved to the `Patches` pane in the settings. Thanks to Rabin-Karp, the eden setting is also no longer necessary and has been relocated accordingly. Therefore, v0.25.0 represents a legitimate and correct evolution. --- + ## 0.25.30 17th November, 2025 From 010631f5535a4e63abb333c5ed1f61d7a262ca95 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Tue, 24 Feb 2026 10:38:35 +0000 Subject: [PATCH 030/339] bump dependencies --- package-lock.json | 3715 ++++++++++++++++++++++++++++++--------------- package.json | 60 +- updates.md | 14 +- 3 files changed, 2551 insertions(+), 1238 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ed4978..0d66536 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,30 +10,30 @@ "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", - "@smithy/fetch-http-handler": "^5.0.2", - "@smithy/md5-js": "^4.0.2", - "@smithy/middleware-apply-body-checksum": "^4.1.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/querystring-builder": "^4.0.2", + "@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", "diff-match-patch": "^1.0.5", "fflate": "^0.8.2", "idb": "^8.0.3", - "minimatch": "^10.0.2", + "minimatch": "^10.2.2", "octagonal-wheels": "^0.1.45", "qrcode-generator": "^1.4.4", "trystero": "^0.22.0", "xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2" }, "devDependencies": { - "@chialab/esbuild-plugin-worker": "^0.18.1", - "@eslint/compat": "^1.2.7", - "@eslint/eslintrc": "^3.3.0", - "@eslint/js": "^9.21.0", - "@sveltejs/vite-plugin-svelte": "^6.2.1", - "@tsconfig/svelte": "^5.0.5", - "@types/deno": "^2.3.0", + "@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", + "@types/deno": "^2.5.0", "@types/diff-match-patch": "^1.0.36", - "@types/node": "^22.13.8", + "@types/node": "^24.10.13", "@types/pouchdb": "^6.4.2", "@types/pouchdb-adapter-http": "^6.1.6", "@types/pouchdb-adapter-idb": "^6.1.7", @@ -42,25 +42,25 @@ "@types/pouchdb-mapreduce": "^6.1.10", "@types/pouchdb-replication": "^6.4.7", "@types/transform-pouch": "^1.0.6", - "@typescript-eslint/eslint-plugin": "8.46.2", - "@typescript-eslint/parser": "8.46.2", + "@typescript-eslint/eslint-plugin": "8.56.1", + "@typescript-eslint/parser": "8.56.1", "@vitest/browser": "^4.0.16", "@vitest/browser-playwright": "^4.0.16", "@vitest/coverage-v8": "^4.0.16", "builtin-modules": "5.0.0", - "dotenv": "^17.2.3", + "dotenv": "^17.3.1", "dotenv-cli": "^11.0.0", "esbuild": "0.25.0", "esbuild-plugin-inline-worker": "^0.1.1", - "esbuild-svelte": "^0.9.3", - "eslint": "^9.38.0", + "esbuild-svelte": "^0.9.4", + "eslint": "^9.39.3", "eslint-plugin-import": "^2.32.0", - "eslint-plugin-svelte": "^3.12.4", + "eslint-plugin-svelte": "^3.15.0", "events": "^3.3.0", - "glob": "^11.0.3", - "obsidian": "^1.8.7", - "playwright": "^1.57.0", - "postcss": "^8.5.3", + "glob": "^13.0.6", + "obsidian": "^1.12.3", + "playwright": "^1.58.2", + "postcss": "^8.5.6", "postcss-load-config": "^6.0.1", "pouchdb-adapter-http": "^9.0.0", "pouchdb-adapter-idb": "^9.0.0", @@ -73,20 +73,20 @@ "pouchdb-merge": "^9.0.0", "pouchdb-replication": "^9.0.0", "pouchdb-utils": "^9.0.0", - "prettier": "3.5.2", + "prettier": "3.8.1", "rollup-plugin-copy": "^3.5.0", "svelte": "5.41.1", - "svelte-check": "^4.3.3", + "svelte-check": "^4.4.3", "svelte-preprocess": "^6.0.3", "terser": "^5.39.0", "transform-pouch": "^2.0.0", "tslib": "^2.8.1", - "tsx": "^4.20.6", + "tsx": "^4.21.0", "typescript": "5.9.3", - "vite": "^7.3.0", + "vite": "^7.3.1", "vitest": "^4.0.16", - "webdriverio": "^9.23.0", - "yaml": "^2.8.0" + "webdriverio": "^9.24.0", + "yaml": "^2.8.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -1012,13 +1012,13 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.4.tgz", - "integrity": "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.5.tgz", + "integrity": "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.12.0", - "fast-xml-parser": "5.3.4", + "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" }, "engines": { @@ -1156,14 +1156,14 @@ } }, "node_modules/@chialab/esbuild-plugin-meta-url": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/@chialab/esbuild-plugin-meta-url/-/esbuild-plugin-meta-url-0.18.2.tgz", - "integrity": "sha512-uIRIdLvYnw5mLrTRXY0BTgeZx6ANL2/OHkWFl8FaiTYNb7cyXmwEDRE1mh6kBXPRPtGuqv6XSpNX+koEkElu4g==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@chialab/esbuild-plugin-meta-url/-/esbuild-plugin-meta-url-0.19.0.tgz", + "integrity": "sha512-VzXuFNaUW9v4bQeFVFVM/KL4/hB/xwMNGGxy1E8CHZAuA+6xHGSqJK7gf17vyWC+GulwIEfzTqfIHU8H8Ic/Zg==", "dev": true, "license": "MIT", "dependencies": { - "@chialab/esbuild-rna": "^0.18.1", - "@chialab/estransform": "^0.18.1", + "@chialab/esbuild-rna": "^0.19.0", + "@chialab/estransform": "^0.19.0", "mime-types": "^2.1.35" }, "engines": { @@ -1171,51 +1171,55 @@ } }, "node_modules/@chialab/esbuild-plugin-worker": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@chialab/esbuild-plugin-worker/-/esbuild-plugin-worker-0.18.1.tgz", - "integrity": "sha512-FCpdhMQkrwBejY+uWo3xLdqHhUK3hbn0ICedyqo97hzRX98ErB2fhRq4LEEPMEaiplF2se2ToYTQaoxHDpkouw==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@chialab/esbuild-plugin-worker/-/esbuild-plugin-worker-0.19.0.tgz", + "integrity": "sha512-atbjSLe9GcG1vMABKai0uwmfsZmSpdjSvLh9pn5CxnI4p57VqSxp5aW24hGV2wwTna78DhtP/spUFUlaWaqVgg==", "dev": true, "license": "MIT", "dependencies": { - "@chialab/esbuild-plugin-meta-url": "^0.18.2", - "@chialab/esbuild-rna": "^0.18.0", - "@chialab/estransform": "^0.18.1" + "@chialab/esbuild-plugin-meta-url": "^0.19.0", + "@chialab/esbuild-rna": "^0.19.0", + "@chialab/estransform": "^0.19.0" }, "engines": { "node": ">=18" } }, "node_modules/@chialab/esbuild-rna": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/@chialab/esbuild-rna/-/esbuild-rna-0.18.2.tgz", - "integrity": "sha512-ckzskez7bxstVQ4c5cxbx0DRP2teldzrcSGQl2KPh1VJGdO2ZmRrb6vNkBBD5K3dx9tgTyvskWp4dV+Fbg07Ag==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@chialab/esbuild-rna/-/esbuild-rna-0.19.0.tgz", + "integrity": "sha512-701y6cN/F9JEjMsJW7PcLxUq1kcReNJ69DTcw7TXLA5f5flBH7zZf+U0F/bwn3vQ2StszT8scTmlflPP4J8m3Q==", "dev": true, "license": "MIT", "dependencies": { - "@chialab/estransform": "^0.18.0", - "@chialab/node-resolve": "^0.18.0" + "@chialab/estransform": "^0.19.0", + "@chialab/node-resolve": "^0.19.0" }, "engines": { "node": ">=18" } }, "node_modules/@chialab/estransform": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@chialab/estransform/-/estransform-0.18.1.tgz", - "integrity": "sha512-W/WmjpQL2hndD0/XfR0FcPBAUj+aLNeoAVehOjV/Q9bSnioz0GVSAXXhzp59S33ZynxJBBfn8DNiMTVNJmk4Aw==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@chialab/estransform/-/estransform-0.19.0.tgz", + "integrity": "sha512-w6i/RZ3SddSCDIYZLkwnK+KAseCKW3BT627San3EbzH3NRY0Gn5RqXCjX9J/mNGe2FydwAevG7O605z50woWuA==", "dev": true, "license": "MIT", "dependencies": { - "@parcel/source-map": "^2.0.0" + "@napi-rs/magic-string": "^0.3.4", + "@parcel/source-map": "^2.0.0", + "cjs-module-lexer": "^1.2.2", + "es-module-lexer": "^1.0.0", + "oxc-parser": "^0.8.0" }, "engines": { "node": ">=18" } }, "node_modules/@chialab/node-resolve": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@chialab/node-resolve/-/node-resolve-0.18.0.tgz", - "integrity": "sha512-eV1m70Qn9pLY9xwFmZ2FlcOzwiaUywsJ7NB/ud8VB7DouvCQtIHkQ3Om7uPX0ojXGEG1LCyO96kZkvbNTxNu0Q==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@chialab/node-resolve/-/node-resolve-0.19.0.tgz", + "integrity": "sha512-VaYOPbgIEVPu7ic+LAA8z2syEpEQhO1nLce0BSPs2eMubQ+mxSrH3RyXKH+CREhOnBWH3Rd3uuFZn2Rofypsbg==", "dev": true, "license": "MIT", "engines": { @@ -1223,21 +1227,27 @@ } }, "node_modules/@codemirror/state": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.1.0.tgz", - "integrity": "sha512-qbUr94DZTe6/V1VS7LDLz11rM/1t/nJxR1El4I6UaxDEdc0aZZvq6JCLJWiRmUf95NRAnDH6fhXn+PWp9wGCIg==", - "dev": true, - "peer": true - }, - "node_modules/@codemirror/view": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.0.3.tgz", - "integrity": "sha512-1gDBymhbx2DZzwnR/rNUu1LiQqjxBJtFiB+4uLR6tHQ6vKhTIwUsP5uZUQ7SM7JxVx3UihMynnTqjcsC+mczZg==", + "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": { - "@codemirror/state": "^6.0.0", - "style-mod": "^4.0.0", + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "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": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, @@ -1599,9 +1609,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", "cpu": [ "arm64" ], @@ -1684,9 +1694,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1703,9 +1713,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -1713,16 +1723,19 @@ } }, "node_modules/@eslint/compat": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.9.tgz", - "integrity": "sha512-gCdSY54n7k+driCadyMNv8JSPzYLeDVM/ikZRtvtROBpRdFSkS8W9A82MqsaY7lZuwL0wiapgD0NT1xT0hyJsA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.2.tgz", + "integrity": "sha512-pR1DoD0h3HfF675QZx0xsyrsU8q70Z/plx7880NOhS02NuWLgBCOMDL787nUeQ7EWLkxv3bPQJaarjcPQb2Dwg==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.1.0" + }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "peerDependencies": { - "eslint": "^9.10.0" + "eslint": "^8.40 || 9 || 10" }, "peerDependenciesMeta": { "eslint": { @@ -1730,6 +1743,19 @@ } } }, + "node_modules/@eslint/compat/node_modules/@eslint/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", + "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", + "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/config-array": { "version": "0.21.1", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", @@ -1746,9 +1772,9 @@ } }, "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "license": "ISC", "dependencies": { @@ -1759,22 +1785,22 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.1.tgz", - "integrity": "sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.16.0" + "@eslint/core": "^0.17.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", - "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", + "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": { @@ -1785,20 +1811,20 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz", + "integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.3", "strip-json-comments": "^3.1.1" }, "engines": { @@ -1809,9 +1835,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "license": "ISC", "dependencies": { @@ -1822,9 +1848,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.38.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz", - "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==", + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", + "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", "dev": true, "license": "MIT", "engines": { @@ -1845,13 +1871,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", - "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.16.0", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { @@ -2639,27 +2665,6 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", - "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -3138,6 +3143,13 @@ "ws": "^8.18.2" } }, + "node_modules/@marijn/find-cluster-break": { + "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/@multiformats/dns": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.10.tgz", @@ -3196,6 +3208,252 @@ "@multiformats/multiaddr": "^12.3.0" } }, + "node_modules/@napi-rs/magic-string": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string/-/magic-string-0.3.4.tgz", + "integrity": "sha512-DEWl/B99RQsyMT3F9bvrXuhL01/eIQp/dtNSE3G1jQ4mTGRcP4iHWxoPZ577WrbjUinrNgvRA5+08g8fkPgimQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/magic-string-android-arm-eabi": "0.3.4", + "@napi-rs/magic-string-android-arm64": "0.3.4", + "@napi-rs/magic-string-darwin-arm64": "0.3.4", + "@napi-rs/magic-string-darwin-x64": "0.3.4", + "@napi-rs/magic-string-freebsd-x64": "0.3.4", + "@napi-rs/magic-string-linux-arm-gnueabihf": "0.3.4", + "@napi-rs/magic-string-linux-arm64-gnu": "0.3.4", + "@napi-rs/magic-string-linux-arm64-musl": "0.3.4", + "@napi-rs/magic-string-linux-x64-gnu": "0.3.4", + "@napi-rs/magic-string-linux-x64-musl": "0.3.4", + "@napi-rs/magic-string-win32-arm64-msvc": "0.3.4", + "@napi-rs/magic-string-win32-ia32-msvc": "0.3.4", + "@napi-rs/magic-string-win32-x64-msvc": "0.3.4" + } + }, + "node_modules/@napi-rs/magic-string-android-arm-eabi": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-android-arm-eabi/-/magic-string-android-arm-eabi-0.3.4.tgz", + "integrity": "sha512-sszAYxqtzzJ4FDerDNHcqL9NhqPhj8W4DNiOanXYy50mA5oojlRtaAFPiB5ZMrWDBM32v5Q30LrmxQ4eTtu2Dg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/magic-string-android-arm64": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-android-arm64/-/magic-string-android-arm64-0.3.4.tgz", + "integrity": "sha512-jdQ6HuO0X5rkX4MauTcWR4HWdgjakTOmmzqXg8L26+jOHVVG1LZE+Su5qvV4bP8vMb2h+vPE+JsnwqSmWymu3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/magic-string-darwin-arm64": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-darwin-arm64/-/magic-string-darwin-arm64-0.3.4.tgz", + "integrity": "sha512-6NmMtvURce9/oq09XBZmuIeI6lPLGtEJ2ZPO/QzL3nLZa6wygiCnO/sFACKYNg5/73ET5HMMTeuogE1JI+r2Lw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/magic-string-darwin-x64": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-darwin-x64/-/magic-string-darwin-x64-0.3.4.tgz", + "integrity": "sha512-f9LmfMiUAKDOtl0meOuLYeVb6OERrgGzrTg1Tn3R3fTAShM2kxRbfAuPE9ljuXxIFzOv/uqRNLSl/LqCJwpREA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/magic-string-freebsd-x64": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-freebsd-x64/-/magic-string-freebsd-x64-0.3.4.tgz", + "integrity": "sha512-rqduQ4odiDK4QdM45xHWRTU4wtFIfpp8g8QGpz+3qqg7ivldDqbbNOrBaf6Oeu77uuEvWggnkyuChotfKgJdJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/magic-string-linux-arm-gnueabihf": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-linux-arm-gnueabihf/-/magic-string-linux-arm-gnueabihf-0.3.4.tgz", + "integrity": "sha512-pVaJEdEpiPqIfq3M4+yMAATS7Z9muDcWYn8H7GFH1ygh8GwgLgKfy/n/lG2M6zp18Mwd0x7E2E/qg9GgCyUzoQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/magic-string-linux-arm64-gnu": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-linux-arm64-gnu/-/magic-string-linux-arm64-gnu-0.3.4.tgz", + "integrity": "sha512-9FwoAih/0tzEZx0BjYYIxWkSRMjonIn91RFM3q3MBs/evmThXUYXUqLNa1PPIkK1JoksswtDi48qWWLt8nGflQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/magic-string-linux-arm64-musl": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-linux-arm64-musl/-/magic-string-linux-arm64-musl-0.3.4.tgz", + "integrity": "sha512-wCR7R+WPOcAKmVQc1s6h6HwfwW1vL9pM8BjUY9Ljkdb8wt1LmZEmV2Sgfc1SfbRQzbyl+pKeufP6adRRQVzYDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/magic-string-linux-x64-gnu": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-linux-x64-gnu/-/magic-string-linux-x64-gnu-0.3.4.tgz", + "integrity": "sha512-sbxFDpYnt5WFbxQ1xozwOvh5A7IftqSI0WnE9O7KsQIOi0ej2dvFbfOW4tmFkvH/YP8KJELo5AhP2+kEq1DpYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/magic-string-linux-x64-musl": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-linux-x64-musl/-/magic-string-linux-x64-musl-0.3.4.tgz", + "integrity": "sha512-jN4h/7e2Ul8v3UK5IZu38NXLMdzVWhY4uEDlnwuUAhwRh26wBQ1/pLD97Uy/Z3dFNBQPcsv60XS9fOM1YDNT6w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/magic-string-win32-arm64-msvc": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-win32-arm64-msvc/-/magic-string-win32-arm64-msvc-0.3.4.tgz", + "integrity": "sha512-gMUyTRHLWpzX2ntJFCbW2Gnla9Y/WUmbkZuW5SBAo/Jo8QojHn76Y4PNgnoXdzcsV9b/45RBxurYKAfFg9WTyg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/magic-string-win32-ia32-msvc": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-win32-ia32-msvc/-/magic-string-win32-ia32-msvc-0.3.4.tgz", + "integrity": "sha512-QIMauMOvEHgL00K9np/c9CT/CRtLOz3mRTQqcZ9XGzSoAMrpxH71KSpDJrKl7h7Ro6TZ+hJ0C3T+JVuTCZNv4A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/magic-string-win32-x64-msvc": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-win32-x64-msvc/-/magic-string-win32-x64-msvc-0.3.4.tgz", + "integrity": "sha512-V8FMSf828MzOI3P6/765MR7zHU6CUZqiyPhmAnwYoKFNxfv7oCviN/G6NcENeCdcYOvNgh5fYzaNLB96ndId5A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@noble/ciphers": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", @@ -3285,6 +3543,118 @@ "node": ">= 8" } }, + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.8.0.tgz", + "integrity": "sha512-3Dws5Wzj9efojjqvhS4ZF+Abh0EoiI5ciOE2kdLifMzSg4fnmYAIOktoUnPEo87TNIb4SiFJ5JgPBgEyq42Eow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.8.0.tgz", + "integrity": "sha512-DAUJ/mfq0Jn2VDYn69bhHTsIWj+aZ/viamexFwaLL7ntkIFmGpzAJZUlWofpY1IRJynKWW+P5AOLYXMllw4qUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.8.0.tgz", + "integrity": "sha512-ZHQVey/O4K3zTIKtpfsbtJIE8MPTRHRxgY3dejaoeFQGf9C3HasgF132Yp4zN/jOUx+x8czKPVa/Af40ViyhGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.8.0.tgz", + "integrity": "sha512-Diw+Tnf5v+zAYXzDoSKCZsMaroU6GoqZMS7smfDtFnZYTHWZrsTmPBLUQe7AFiG7O7tkhsCdcWjOYgbVkrSVOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.8.0.tgz", + "integrity": "sha512-WloqcRrtQUVEP/Sy8ZeEgF0HgBKQjOv3zLFZqbC5ipkerKriGcVbsq3fOIMOi/55AM6/UhIAjeZGnoeco72JjQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.8.0.tgz", + "integrity": "sha512-2j7BD9szwSXTvSj0Q8VE98UHGYvrgZzdLy4EyB0FilhQnopEfz+YV674rWGY2Il1VYxHJwGctrTJHvARolu37g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.8.0.tgz", + "integrity": "sha512-mcomr1og17yCmnwn8Q7CRzrH9Va0HccWe4Ld3/u/elBsw0SEzYGVvECRzCyRglYAbKTtusz7as9Jee0RiMOMmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.8.0.tgz", + "integrity": "sha512-nIBkc1KZOVYUaHT3+U+gM354P3byMAIXMvlmLMbs0kWVRcI4vrzL8qwWpC6QdBQxWKZGqPEqGolv8H4dDYA9nQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@parcel/source-map": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@parcel/source-map/-/source-map-2.1.1.tgz", @@ -3401,9 +3771,9 @@ "license": "BSD-3-Clause" }, "node_modules/@puppeteer/browsers": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.11.0.tgz", - "integrity": "sha512-n6oQX6mYkG8TRPuPXmbPidkUbsSRalhmaaVAQxvH1IkQy63cwsH+kOjB3e4cpCDHg0aSvsiX9bQ4s2VB6mGWUQ==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.0.tgz", + "integrity": "sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3411,7 +3781,7 @@ "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", - "semver": "^7.7.3", + "semver": "^7.7.4", "tar-fs": "^3.1.1", "yargs": "^17.7.2" }, @@ -3912,15 +4282,15 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz", - "integrity": "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.10.tgz", + "integrity": "sha512-qF4EcrEtEf2P6f2kGGuSVe1lan26cn7PsWJBC3vZJ6D16Fm5FSN06udOMVoW6hjzQM3W7VDFwtyUG2szQY50dA==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.8", - "@smithy/querystring-builder": "^4.2.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", + "@smithy/protocol-http": "^5.3.9", + "@smithy/querystring-builder": "^4.2.9", + "@smithy/types": "^4.12.1", + "@smithy/util-base64": "^4.3.1", "tslib": "^2.6.2" }, "engines": { @@ -3985,9 +4355,9 @@ } }, "node_modules/@smithy/is-array-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", - "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.1.tgz", + "integrity": "sha512-Yfu664Qbf1B4IYIsYgKoABt010daZjkaCRvdU/sPnZG6TtHOB0md0RjNdLGzxe5UIdn9js4ftPICzmkRa9RJ4Q==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -3997,13 +4367,13 @@ } }, "node_modules/@smithy/md5-js": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.8.tgz", - "integrity": "sha512-oGMaLj4tVZzLi3itBa9TCswgMBr7k9b+qKYowQ6x1rTyTuO1IU2YHdHUa+891OsOH+wCsH7aTPRsTJO3RMQmjQ==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.9.tgz", + "integrity": "sha512-ZCCWfGj4wvqV+5OS9e/GvR5jlR7j1mMB1UkGE+V7P1USFMwcL4Z4j5mO9nGvQGkfe20KM87ymbvZIcU9tHNlIg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.12.0", - "@smithy/util-utf8": "^4.2.0", + "@smithy/types": "^4.12.1", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" }, "engines": { @@ -4011,14 +4381,14 @@ } }, "node_modules/@smithy/middleware-apply-body-checksum": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-apply-body-checksum/-/middleware-apply-body-checksum-4.1.0.tgz", - "integrity": "sha512-6YOMHT3IYYjkxhA5h0pw3uu84aZInCuCVwZ8J/cObUEXxyCLNXV8Ws/T+Likp7LR2AEt2AjlqQ5VKZUbrSbJkA==", + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-apply-body-checksum/-/middleware-apply-body-checksum-4.3.9.tgz", + "integrity": "sha512-53wIgp1Jz5F886zUHTnVnXEUvDocPUs0icN5ja9oPIUWRXN9PpXOIQFUqEMQlU5zKMi+sfUz8yKnlsxYHkWjqw==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", + "@smithy/is-array-buffer": "^4.2.1", + "@smithy/protocol-http": "^5.3.9", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { @@ -4150,12 +4520,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.3.8", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.8.tgz", - "integrity": "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==", + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.9.tgz", + "integrity": "sha512-PRy4yZqsKI3Eab8TLc16Dj2NzC4dnw/8E95+++Jc+wwlkjBpAq3tNLqkLHMmSvDfxKQ+X5PmmCYt+rM/GcMKPA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { @@ -4163,13 +4533,13 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz", - "integrity": "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.9.tgz", + "integrity": "sha512-/AIDaq0+ehv+QfeyAjCUFShwHIt+FA1IodsV/2AZE5h4PUZcQYv5sjmy9V67UWfsBoTjOPKUFYSRfGoNW9T2UQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.12.0", - "@smithy/util-uri-escape": "^4.2.0", + "@smithy/types": "^4.12.1", + "@smithy/util-uri-escape": "^4.2.1", "tslib": "^2.6.2" }, "engines": { @@ -4252,9 +4622,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", - "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.1.tgz", + "integrity": "sha512-ow30Ze/DD02KH2p0eMyIF2+qJzGyNb0kFrnTRtPpuOkQ4hrgvLdaU4YC6r/K8aOrCML4FH0Cmm0aI4503L1Hwg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4278,13 +4648,13 @@ } }, "node_modules/@smithy/util-base64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", - "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.1.tgz", + "integrity": "sha512-BKGuawX4Doq/bI/uEmg+Zyc36rJKWuin3py89PquXBIBqmbnJwBBsmKhdHfNEp0+A4TDgLmT/3MSKZ1SxHcR6w==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@smithy/util-buffer-from": "^4.2.1", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" }, "engines": { @@ -4316,12 +4686,12 @@ } }, "node_modules/@smithy/util-buffer-from": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", - "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.1.tgz", + "integrity": "sha512-/swhmt1qTiVkaejlmMPPDgZhEaWb/HWMGRBheaxwuVkusp/z+ErJyQxO6kaXumOciZSWlmq6Z5mNylCd33X7Ig==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", + "@smithy/is-array-buffer": "^4.2.1", "tslib": "^2.6.2" }, "engines": { @@ -4446,9 +4816,9 @@ } }, "node_modules/@smithy/util-uri-escape": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", - "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.1.tgz", + "integrity": "sha512-YmiUDn2eo2IOiWYYvGQkgX5ZkBSiTQu4FlDo5jNPpAxng2t6Sjb6WutnZV9l6VR4eJul1ABmCrnWBC9hKHQa6Q==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4458,12 +4828,12 @@ } }, "node_modules/@smithy/util-utf8": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", - "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.1.tgz", + "integrity": "sha512-DSIwNaWtmzrNQHv8g7DBGR9mulSit65KSj5ymGEIAknmIN8IpbZefEep10LaMG/P/xquwbmJ1h9ectz8z6mV6g==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-buffer-from": "^4.2.1", "tslib": "^2.6.2" }, "engines": { @@ -4593,17 +4963,17 @@ } }, "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.1.tgz", - "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz", + "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", - "debug": "^4.4.1", "deepmerge": "^4.3.1", - "magic-string": "^0.30.17", + "magic-string": "^0.30.21", + "obug": "^2.1.0", "vitefu": "^1.1.1" }, "engines": { @@ -4640,9 +5010,9 @@ "license": "MIT" }, "node_modules/@tsconfig/svelte": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.5.tgz", - "integrity": "sha512-48fAnUjKye38FvMiNOj0J9I/4XlQQiZlpe9xaNPfe8vy2Y1hFBt8g1yqf2EGjVvHavo4jf2lC+TQyENCr4BJBQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.8.tgz", + "integrity": "sha512-UkNnw1/oFEfecR8ypyHIQuWYdkPvHiwcQ78sh+ymIiYoF+uc5H1UBetbjyqT+vgGJ3qQN6nhucJviX6HesWtKQ==", "dev": true, "license": "MIT" }, @@ -4683,9 +5053,9 @@ "license": "MIT" }, "node_modules/@types/deno": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@types/deno/-/deno-2.3.0.tgz", - "integrity": "sha512-/4SyefQpKjwNKGkq9qG3Ln7MazfbWKvydyVFBnXzP5OQA4u1paoFtaOe1iHKycIWHHkhYag0lPxyheThV1ijzw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@types/deno/-/deno-2.5.0.tgz", + "integrity": "sha512-g8JS38vmc0S87jKsFzre+0ZyMOUDHPVokEJymSCRlL57h6f/FdKPWBXgdFh3Z8Ees9sz11qt9VWELU9Y9ZkiVw==", "dev": true, "license": "MIT" }, @@ -4765,14 +5135,20 @@ "dev": true }, "node_modules/@types/node": { - "version": "22.19.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.8.tgz", - "integrity": "sha512-ebO/Yl+EAvVe8DnMfi+iaAyIqYdK0q/q0y0rw82INWEKJOBe6b/P3YWE8NW7oOlF/nXFNrHwhARrN/hdgDkraA==", + "version": "24.10.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", + "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.16.0" } }, + "node_modules/@types/node/node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, "node_modules/@types/phoenix": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", @@ -5028,21 +5404,20 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz", - "integrity": "sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", + "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.46.2", - "@typescript-eslint/type-utils": "8.46.2", - "@typescript-eslint/utils": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/type-utils": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5052,8 +5427,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.46.2", - "eslint": "^8.57.0 || ^9.0.0", + "@typescript-eslint/parser": "^8.56.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, @@ -5068,18 +5443,18 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.2.tgz", - "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", + "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.46.2", - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5089,20 +5464,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.2.tgz", - "integrity": "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", + "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.46.2", - "@typescript-eslint/types": "^8.46.2", - "debug": "^4.3.4" + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5116,14 +5491,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz", - "integrity": "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", + "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2" + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5134,9 +5509,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz", - "integrity": "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", + "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", "dev": true, "license": "MIT", "engines": { @@ -5151,17 +5526,17 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.2.tgz", - "integrity": "sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", + "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2", - "@typescript-eslint/utils": "8.46.2", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5171,14 +5546,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.2.tgz", - "integrity": "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", "dev": true, "license": "MIT", "engines": { @@ -5190,22 +5565,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz", - "integrity": "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", + "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.46.2", - "@typescript-eslint/tsconfig-utils": "8.46.2", - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5218,43 +5592,17 @@ "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "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/@typescript-eslint/utils": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.2.tgz", - "integrity": "sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", + "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.46.2", - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5264,19 +5612,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz", - "integrity": "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", + "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.2", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.56.1", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5287,13 +5635,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "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": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -5806,37 +6154,52 @@ } }, "node_modules/@wdio/config": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.23.0.tgz", - "integrity": "sha512-hhtngUG2uCxYmScSEor+k22EVlsTW3ARXgke8NPVeQA4p1+GC2CvRZi4P7nmhRTZubgLrENYYsveFcYR+1UXhQ==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.24.0.tgz", + "integrity": "sha512-rcHu0eG16rSEmHL0sEKDcr/vYFmGhQ5GOlmlx54r+1sgh6sf136q+kth4169s16XqviWGW3LjZbUfpTK29pGtw==", "dev": true, "license": "MIT", "dependencies": { "@wdio/logger": "9.18.0", - "@wdio/types": "9.20.0", - "@wdio/utils": "9.23.0", + "@wdio/types": "9.24.0", + "@wdio/utils": "9.24.0", "deepmerge-ts": "^7.0.3", "glob": "^10.2.2", - "import-meta-resolve": "^4.0.0" + "import-meta-resolve": "^4.0.0", + "jiti": "^2.6.1" }, "engines": { "node": ">=18.20.0" } }, + "node_modules/@wdio/config/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/@wdio/config/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@wdio/config/node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -5854,30 +6217,14 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/config/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/@wdio/config/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", + "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -5963,9 +6310,9 @@ } }, "node_modules/@wdio/protocols": { - "version": "9.16.2", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.16.2.tgz", - "integrity": "sha512-h3k97/lzmyw5MowqceAuY3HX/wGJojXHkiPXA3WlhGPCaa2h4+GovV2nJtRvknCKsE7UHA1xB5SWeI8MzloBew==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.24.0.tgz", + "integrity": "sha512-ozQKYddBLT4TRvU9J+fGrhVUtx3iDAe+KNCJcTDMFMxNSdDMR2xFQdNp8HLHypspk58oXTYCvz6ZYjySthhqsw==", "dev": true, "license": "MIT" }, @@ -5983,9 +6330,9 @@ } }, "node_modules/@wdio/repl/node_modules/@types/node": { - "version": "20.19.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.31.tgz", - "integrity": "sha512-5jsi0wpncvTD33Sh1UCgacK37FFwDn+EG7wCmEvs62fCvBL+n8/76cAYDok21NF6+jaVWIqKwCZyX7Vbu8eB3A==", + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", "dev": true, "license": "MIT", "dependencies": { @@ -5993,9 +6340,9 @@ } }, "node_modules/@wdio/types": { - "version": "9.20.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.20.0.tgz", - "integrity": "sha512-zMmAtse2UMCSOW76mvK3OejauAdcFGuKopNRH7crI0gwKTZtvV89yXWRziz9cVXpFgfmJCjf9edxKFWdhuF5yw==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.24.0.tgz", + "integrity": "sha512-PYYunNl8Uq1r8YMJAK6ReRy/V/XIrCSyj5cpCtR5EqCL6heETOORFj7gt4uPnzidfgbtMBcCru0LgjjlMiH1UQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6006,9 +6353,9 @@ } }, "node_modules/@wdio/types/node_modules/@types/node": { - "version": "20.19.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.31.tgz", - "integrity": "sha512-5jsi0wpncvTD33Sh1UCgacK37FFwDn+EG7wCmEvs62fCvBL+n8/76cAYDok21NF6+jaVWIqKwCZyX7Vbu8eB3A==", + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", "dev": true, "license": "MIT", "dependencies": { @@ -6016,15 +6363,15 @@ } }, "node_modules/@wdio/utils": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.23.0.tgz", - "integrity": "sha512-WhXuVSxEvPw/i34bL1aCHAOi+4g29kRkIMyBShNSxH+Shxh2G91RJYsXm4IAiPMGcC4H6G8T2VcbZ32qnGPm5Q==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.24.0.tgz", + "integrity": "sha512-6WhtzC5SNCGRBTkaObX6A07Ofnnyyf+TQH/d/fuhZRqvBknrP4AMMZF+PFxGl1fwdySWdBn+gV2QLE+52Byowg==", "dev": true, "license": "MIT", "dependencies": { "@puppeteer/browsers": "^2.2.0", "@wdio/logger": "9.18.0", - "@wdio/types": "9.20.0", + "@wdio/types": "9.24.0", "decamelize": "^6.0.0", "deepmerge-ts": "^7.0.3", "edgedriver": "^6.1.2", @@ -6042,9 +6389,9 @@ } }, "node_modules/@zip.js/zip.js": { - "version": "2.8.13", - "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.13.tgz", - "integrity": "sha512-UcWZ5i9Hlspgo+8q8PUPd9TmDYM62eqfb/0iZ5SF3bqfX/uFUmV660MC9Lsvon7/sidzesXLQlIf8/mbDUf6gQ==", + "version": "2.8.21", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.21.tgz", + "integrity": "sha512-fkyzXISE3IMrstDO1AgPkJCx14MYHP/suIGiAovEYEuBjq3mffsuL6aMV7ohOSjW4rXtuACuUfpA3GtITgdtYg==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -6117,9 +6464,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -6204,14 +6551,27 @@ "node": ">= 14" } }, + "node_modules/archiver-utils/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/archiver-utils/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/archiver-utils/node_modules/glob": { @@ -6235,30 +6595,14 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/archiver-utils/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/archiver-utils/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", + "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -6554,9 +6898,9 @@ } }, "node_modules/bare-fs": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.2.tgz", - "integrity": "sha512-veTnRzkb6aPHOvSKIOy60KzURfBdUflr5VReI+NSaPL6xf+XLdONQgZgpYvUuZLVQ8dCqxpBAudaOM1+KpAUxw==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.4.tgz", + "integrity": "sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==", "dev": true, "license": "Apache-2.0", "optional": true, @@ -6602,14 +6946,15 @@ } }, "node_modules/bare-stream": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", - "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.8.0.tgz", + "integrity": "sha512-reUN0M2sHRqCdG4lUK3Fw8w98eeUIZHL5c3H7Mbhk2yVBL+oofgaIp0ieLfD5QXwPCypBpmEEKU2WZKzbAk8GA==", "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { - "streamx": "^2.21.0" + "streamx": "^2.21.0", + "teex": "^1.0.1" }, "peerDependencies": { "bare-buffer": "*", @@ -6656,9 +7001,9 @@ "license": "MIT" }, "node_modules/basic-ftp": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.1.0.tgz", - "integrity": "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz", + "integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==", "dev": true, "license": "MIT", "engines": { @@ -6938,6 +7283,13 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -7089,6 +7441,13 @@ "node": ">= 14" } }, + "node_modules/crelt": { + "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": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -7539,9 +7898,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -7667,23 +8026,23 @@ } }, "node_modules/edgedriver/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=16" + "node": ">=20" } }, "node_modules/edgedriver/node_modules/which": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", - "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^3.1.1" + "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" @@ -7956,9 +8315,9 @@ } }, "node_modules/esbuild-svelte": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.9.3.tgz", - "integrity": "sha512-CgEcGY1r/d16+aggec3czoFBEBaYIrFOnMxpsO6fWNaNEqHregPN5DLAPZDqrL7rXDNplW+WMu8s3GMq9FqgJA==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.9.4.tgz", + "integrity": "sha512-v/a0GjkKN06nal2QLluxjk2GXsei3fdtjIuHRa6pVnri5rQBZ6pj4a2WwjLfRojgRsLwDHl4xSeZ1BeUHsqQrw==", "dev": true, "license": "MIT", "dependencies": { @@ -8016,9 +8375,9 @@ } }, "node_modules/eslint": { - "version": "9.38.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz", - "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", + "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, "license": "MIT", "peer": true, @@ -8026,11 +8385,11 @@ "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.1", - "@eslint/core": "^0.16.0", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.38.0", - "@eslint/plugin-kit": "^0.4.0", + "@eslint/js": "9.39.3", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -8180,10 +8539,11 @@ } }, "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -8201,9 +8561,9 @@ } }, "node_modules/eslint-plugin-svelte": { - "version": "3.12.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.12.5.tgz", - "integrity": "sha512-4KRG84eAHQfYd9OjZ1K7sCHy0nox+9KwT+s5WCCku3jTim5RV4tVENob274nCwIaApXsYPKAUAZFBxKZ3Wyfjw==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.15.0.tgz", + "integrity": "sha512-QKB7zqfuB8aChOfBTComgDptMf2yxiJx7FE04nneCmtQzgTHvY8UJkuh8J2Rz7KB9FFV9aTHX6r7rdYGvG8T9Q==", "dev": true, "license": "MIT", "dependencies": { @@ -8225,7 +8585,7 @@ "url": "https://github.com/sponsors/ota-meshi" }, "peerDependencies": { - "eslint": "^8.57.1 || ^9.0.0", + "eslint": "^8.57.1 || ^9.0.0 || ^10.0.0", "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "peerDependenciesMeta": { @@ -8340,10 +8700,11 @@ } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -8607,9 +8968,9 @@ } }, "node_modules/fast-xml-parser": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.4.tgz", - "integrity": "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==", + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.6.tgz", + "integrity": "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==", "funding": [ { "type": "github", @@ -8618,7 +8979,7 @@ ], "license": "MIT", "dependencies": { - "strnum": "^2.1.0" + "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" @@ -9085,24 +9446,18 @@ } }, "node_modules/glob": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", - "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -9193,9 +9548,9 @@ } }, "node_modules/globby/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "license": "ISC", "dependencies": { @@ -9232,12 +9587,6 @@ "dev": true, "license": "MIT" }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -10362,19 +10711,30 @@ } }, "node_modules/jackspeak": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", - "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": "20 || >=22" - }, "funding": { "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" } }, "node_modules/js-sdsl": { @@ -11036,20 +11396,41 @@ } }, "node_modules/minimatch": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", - "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimatch/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -11060,11 +11441,11 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -11077,9 +11458,9 @@ "license": "MIT" }, "node_modules/modern-tar": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.3.tgz", - "integrity": "sha512-4W79zekKGyYU4JXVmB78DOscMFaJth2gGhgfTl2alWE4rNe3nf4N2pqenQ0rEtIewrnD79M687Ouba3YGTLOvg==", + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.5.tgz", + "integrity": "sha512-YTefgdpKKFgoTDbEUqXqgUJct2OG6/4hs4XWLsxcHkDLj/x/V8WmKIRppPnXP5feQ7d1vuYWSp3qKkxfwaFaxA==", "dev": true, "license": "MIT", "engines": { @@ -11364,9 +11745,9 @@ } }, "node_modules/obsidian": { - "version": "1.8.7", - "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.8.7.tgz", - "integrity": "sha512-h4bWwNFAGRXlMlMAzdEiIM2ppTGlrh7uGOJS6w4gClrsjc+ei/3YAtU2VdFUlCiPuTHpY4aBpFJJW75S1Tl/JA==", + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.12.3.tgz", + "integrity": "sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw==", "dev": true, "license": "MIT", "dependencies": { @@ -11374,8 +11755,8 @@ "moment": "2.29.4" }, "peerDependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0" + "@codemirror/state": "6.5.0", + "@codemirror/view": "6.38.6" } }, "node_modules/obug": { @@ -11443,6 +11824,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oxc-parser": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.8.0.tgz", + "integrity": "sha512-ObPeMkbDX7igb7NyyAC8CbVC3fY+YmlMsxsRQ2oyFBkpQtI5tjoyqSDKbS9A9EcJvt2q89C4UoC+HjVBdLYYJg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-darwin-arm64": "0.8.0", + "@oxc-parser/binding-darwin-x64": "0.8.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.8.0", + "@oxc-parser/binding-linux-arm64-musl": "0.8.0", + "@oxc-parser/binding-linux-x64-gnu": "0.8.0", + "@oxc-parser/binding-linux-x64-musl": "0.8.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.8.0", + "@oxc-parser/binding-win32-x64-msvc": "0.8.0" + } + }, "node_modules/p-defer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-4.0.1.tgz", @@ -11716,9 +12117,9 @@ "dev": true }, "node_modules/path-scurry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", - "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -11726,16 +12127,16 @@ "minipass": "^7.1.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", - "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -11873,13 +12274,13 @@ } }, "node_modules/playwright": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", - "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.57.0" + "playwright-core": "1.58.2" }, "bin": { "playwright": "cli.js" @@ -11892,9 +12293,9 @@ } }, "node_modules/playwright-core": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", - "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -12366,9 +12767,9 @@ } }, "node_modules/prettier": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", - "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", "bin": { @@ -12621,9 +13022,9 @@ } }, "node_modules/readdir-glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.7.tgz", + "integrity": "sha512-FjiwU9HaHW6YB3H4a1sFudnv93lvydNjz2lmyUXR6IwKhGI+bgL3SOZrBGn6kvvX2pJvhEkGSGjyTHN47O4rqA==", "dev": true, "license": "ISC", "dependencies": { @@ -13010,9 +13411,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -13555,10 +13956,11 @@ "license": "MIT" }, "node_modules/style-mod": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz", - "integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==", - "dev": true + "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": { "version": "9.0.0", @@ -13651,9 +14053,9 @@ } }, "node_modules/svelte-check": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.3.tgz", - "integrity": "sha512-RYP0bEwenDXzfv0P1sKAwjZSlaRyqBn0Fz1TVni58lqyEiqgwztTpmodJrGzP6ZT2aHl4MbTvWP6gbmQ3FOnBg==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.3.tgz", + "integrity": "sha512-4HtdEv2hOoLCEsSXI+RDELk9okP/4sImWa7X02OjMFFOWeSdFF3NFy3vqpw0z+eH9C88J9vxZfUXz/Uv2A1ANw==", "dev": true, "license": "MIT", "dependencies": { @@ -13818,6 +14220,17 @@ "streamx": "^2.15.0" } }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "streamx": "^2.12.5" + } + }, "node_modules/terser": { "version": "5.39.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", @@ -14025,9 +14438,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "license": "MIT", "engines": { @@ -14056,14 +14469,14 @@ "license": "0BSD" }, "node_modules/tsx": { - "version": "4.20.6", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", - "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "esbuild": "~0.25.0", + "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "bin": { @@ -14076,6 +14489,473 @@ "fsevents": "~2.3.3" } }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -14257,9 +15137,9 @@ } }, "node_modules/undici": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.20.0.tgz", - "integrity": "sha512-MJZrkjyd7DeC+uPZh+5/YaMDxFiiEEaDgbUSVMXayofAkDWF1088CDo+2RPg7B1BuS1qf1vgNE7xqwPxE0DuSQ==", + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", + "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", "dev": true, "license": "MIT", "engines": { @@ -14270,6 +15150,7 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, "license": "MIT" }, "node_modules/universalify": { @@ -14334,9 +15215,9 @@ } }, "node_modules/vite": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", - "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", "peer": true, @@ -14410,9 +15291,9 @@ } }, "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", "cpu": [ "ppc64" ], @@ -14427,9 +15308,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", "cpu": [ "arm" ], @@ -14444,9 +15325,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", "cpu": [ "arm64" ], @@ -14461,9 +15342,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", "cpu": [ "x64" ], @@ -14478,9 +15359,9 @@ } }, "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", "cpu": [ "arm64" ], @@ -14495,9 +15376,9 @@ } }, "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", "cpu": [ "x64" ], @@ -14512,9 +15393,9 @@ } }, "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", "cpu": [ "arm64" ], @@ -14529,9 +15410,9 @@ } }, "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", "cpu": [ "x64" ], @@ -14546,9 +15427,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", "cpu": [ "arm" ], @@ -14563,9 +15444,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", "cpu": [ "arm64" ], @@ -14580,9 +15461,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", "cpu": [ "ia32" ], @@ -14597,9 +15478,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", "cpu": [ "loong64" ], @@ -14614,9 +15495,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", "cpu": [ "mips64el" ], @@ -14631,9 +15512,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", "cpu": [ "ppc64" ], @@ -14648,9 +15529,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", "cpu": [ "riscv64" ], @@ -14665,9 +15546,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", "cpu": [ "s390x" ], @@ -14682,9 +15563,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", "cpu": [ "x64" ], @@ -14699,9 +15580,9 @@ } }, "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", "cpu": [ "arm64" ], @@ -14716,9 +15597,9 @@ } }, "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", "cpu": [ "x64" ], @@ -14733,9 +15614,9 @@ } }, "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", "cpu": [ "arm64" ], @@ -14750,9 +15631,9 @@ } }, "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", "cpu": [ "x64" ], @@ -14767,9 +15648,9 @@ } }, "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", "cpu": [ "x64" ], @@ -14784,9 +15665,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", "cpu": [ "arm64" ], @@ -14801,9 +15682,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", "cpu": [ "ia32" ], @@ -14818,9 +15699,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", "cpu": [ "x64" ], @@ -14835,9 +15716,9 @@ } }, "node_modules/vite/node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -14848,32 +15729,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" } }, "node_modules/vite/node_modules/fdir": { @@ -15027,10 +15908,11 @@ "dev": true }, "node_modules/w3c-keyname": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.4.tgz", - "integrity": "sha512-tOhfEwEzFLJzf6d1ZPkYfGj+FWhIpBux9ppoP3rlclw3Z0BZv3N7b7030Z1kYth+6rDuAsXUFr+d0VE6Ed1ikw==", - "dev": true + "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": { "version": "1.1.0", @@ -15098,19 +15980,19 @@ "license": "Apache-2.0" }, "node_modules/webdriver": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.23.0.tgz", - "integrity": "sha512-XkZOhjoBOY7maKI3BhDF2rNiDne4wBD6Gw6VUnt4X9b7j9NtfzcCrThBlT0hnA8W77bWNtMRCSpw9Ajy08HqKg==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.24.0.tgz", + "integrity": "sha512-2R31Ey83NzMsafkl4hdFq6GlIBvOODQMkueLjeRqYAITu3QCYiq9oqBdnWA6CdePuV4dbKlYsKRX0mwMiPclDA==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "^20.1.0", "@types/ws": "^8.5.3", - "@wdio/config": "9.23.0", + "@wdio/config": "9.24.0", "@wdio/logger": "9.18.0", - "@wdio/protocols": "9.16.2", - "@wdio/types": "9.20.0", - "@wdio/utils": "9.23.0", + "@wdio/protocols": "9.24.0", + "@wdio/types": "9.24.0", + "@wdio/utils": "9.24.0", "deepmerge-ts": "^7.0.3", "https-proxy-agent": "^7.0.6", "undici": "^6.21.3", @@ -15121,9 +16003,9 @@ } }, "node_modules/webdriver/node_modules/@types/node": { - "version": "20.19.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.31.tgz", - "integrity": "sha512-5jsi0wpncvTD33Sh1UCgacK37FFwDn+EG7wCmEvs62fCvBL+n8/76cAYDok21NF6+jaVWIqKwCZyX7Vbu8eB3A==", + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", "dev": true, "license": "MIT", "dependencies": { @@ -15141,20 +16023,20 @@ } }, "node_modules/webdriverio": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.23.0.tgz", - "integrity": "sha512-Y5y4jpwHvuduUfup+gXTuCU6AROn/k6qOba3st0laFluKHY+q5SHOpQAJdS8acYLwE8caDQ2dXJhmXyxuJrm0Q==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.24.0.tgz", + "integrity": "sha512-LTJt6Z/iDM0ne/4ytd3BykoPv9CuJ+CAILOzlwFeMGn4Mj02i4Bk2Rg9o/jeJ89f52hnv4OPmNjD0e8nzWAy5g==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "^20.11.30", "@types/sinonjs__fake-timers": "^8.1.5", - "@wdio/config": "9.23.0", + "@wdio/config": "9.24.0", "@wdio/logger": "9.18.0", - "@wdio/protocols": "9.16.2", + "@wdio/protocols": "9.24.0", "@wdio/repl": "9.16.2", - "@wdio/types": "9.20.0", - "@wdio/utils": "9.23.0", + "@wdio/types": "9.24.0", + "@wdio/utils": "9.24.0", "archiver": "^7.0.1", "aria-query": "^5.3.0", "cheerio": "^1.0.0-rc.12", @@ -15171,7 +16053,7 @@ "rgb2hex": "0.2.5", "serialize-error": "^12.0.0", "urlpattern-polyfill": "^10.0.0", - "webdriver": "9.23.0" + "webdriver": "9.24.0" }, "engines": { "node": ">=18.20.0" @@ -15186,9 +16068,9 @@ } }, "node_modules/webdriverio/node_modules/@types/node": { - "version": "20.19.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.31.tgz", - "integrity": "sha512-5jsi0wpncvTD33Sh1UCgacK37FFwDn+EG7wCmEvs62fCvBL+n8/76cAYDok21NF6+jaVWIqKwCZyX7Vbu8eB3A==", + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", "dev": true, "license": "MIT", "dependencies": { @@ -16389,12 +17271,12 @@ } }, "@aws-sdk/xml-builder": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.4.tgz", - "integrity": "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.5.tgz", + "integrity": "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA==", "requires": { "@smithy/types": "^4.12.0", - "fast-xml-parser": "5.3.4", + "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, @@ -16493,68 +17375,76 @@ } }, "@chialab/esbuild-plugin-meta-url": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/@chialab/esbuild-plugin-meta-url/-/esbuild-plugin-meta-url-0.18.2.tgz", - "integrity": "sha512-uIRIdLvYnw5mLrTRXY0BTgeZx6ANL2/OHkWFl8FaiTYNb7cyXmwEDRE1mh6kBXPRPtGuqv6XSpNX+koEkElu4g==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@chialab/esbuild-plugin-meta-url/-/esbuild-plugin-meta-url-0.19.0.tgz", + "integrity": "sha512-VzXuFNaUW9v4bQeFVFVM/KL4/hB/xwMNGGxy1E8CHZAuA+6xHGSqJK7gf17vyWC+GulwIEfzTqfIHU8H8Ic/Zg==", "dev": true, "requires": { - "@chialab/esbuild-rna": "^0.18.1", - "@chialab/estransform": "^0.18.1", + "@chialab/esbuild-rna": "^0.19.0", + "@chialab/estransform": "^0.19.0", "mime-types": "^2.1.35" } }, "@chialab/esbuild-plugin-worker": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@chialab/esbuild-plugin-worker/-/esbuild-plugin-worker-0.18.1.tgz", - "integrity": "sha512-FCpdhMQkrwBejY+uWo3xLdqHhUK3hbn0ICedyqo97hzRX98ErB2fhRq4LEEPMEaiplF2se2ToYTQaoxHDpkouw==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@chialab/esbuild-plugin-worker/-/esbuild-plugin-worker-0.19.0.tgz", + "integrity": "sha512-atbjSLe9GcG1vMABKai0uwmfsZmSpdjSvLh9pn5CxnI4p57VqSxp5aW24hGV2wwTna78DhtP/spUFUlaWaqVgg==", "dev": true, "requires": { - "@chialab/esbuild-plugin-meta-url": "^0.18.2", - "@chialab/esbuild-rna": "^0.18.0", - "@chialab/estransform": "^0.18.1" + "@chialab/esbuild-plugin-meta-url": "^0.19.0", + "@chialab/esbuild-rna": "^0.19.0", + "@chialab/estransform": "^0.19.0" } }, "@chialab/esbuild-rna": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/@chialab/esbuild-rna/-/esbuild-rna-0.18.2.tgz", - "integrity": "sha512-ckzskez7bxstVQ4c5cxbx0DRP2teldzrcSGQl2KPh1VJGdO2ZmRrb6vNkBBD5K3dx9tgTyvskWp4dV+Fbg07Ag==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@chialab/esbuild-rna/-/esbuild-rna-0.19.0.tgz", + "integrity": "sha512-701y6cN/F9JEjMsJW7PcLxUq1kcReNJ69DTcw7TXLA5f5flBH7zZf+U0F/bwn3vQ2StszT8scTmlflPP4J8m3Q==", "dev": true, "requires": { - "@chialab/estransform": "^0.18.0", - "@chialab/node-resolve": "^0.18.0" + "@chialab/estransform": "^0.19.0", + "@chialab/node-resolve": "^0.19.0" } }, "@chialab/estransform": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@chialab/estransform/-/estransform-0.18.1.tgz", - "integrity": "sha512-W/WmjpQL2hndD0/XfR0FcPBAUj+aLNeoAVehOjV/Q9bSnioz0GVSAXXhzp59S33ZynxJBBfn8DNiMTVNJmk4Aw==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@chialab/estransform/-/estransform-0.19.0.tgz", + "integrity": "sha512-w6i/RZ3SddSCDIYZLkwnK+KAseCKW3BT627San3EbzH3NRY0Gn5RqXCjX9J/mNGe2FydwAevG7O605z50woWuA==", "dev": true, "requires": { - "@parcel/source-map": "^2.0.0" + "@napi-rs/magic-string": "^0.3.4", + "@parcel/source-map": "^2.0.0", + "cjs-module-lexer": "^1.2.2", + "es-module-lexer": "^1.0.0", + "oxc-parser": "^0.8.0" } }, "@chialab/node-resolve": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@chialab/node-resolve/-/node-resolve-0.18.0.tgz", - "integrity": "sha512-eV1m70Qn9pLY9xwFmZ2FlcOzwiaUywsJ7NB/ud8VB7DouvCQtIHkQ3Om7uPX0ojXGEG1LCyO96kZkvbNTxNu0Q==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@chialab/node-resolve/-/node-resolve-0.19.0.tgz", + "integrity": "sha512-VaYOPbgIEVPu7ic+LAA8z2syEpEQhO1nLce0BSPs2eMubQ+mxSrH3RyXKH+CREhOnBWH3Rd3uuFZn2Rofypsbg==", "dev": true }, "@codemirror/state": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.1.0.tgz", - "integrity": "sha512-qbUr94DZTe6/V1VS7LDLz11rM/1t/nJxR1El4I6UaxDEdc0aZZvq6JCLJWiRmUf95NRAnDH6fhXn+PWp9wGCIg==", - "dev": true, - "peer": true - }, - "@codemirror/view": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.0.3.tgz", - "integrity": "sha512-1gDBymhbx2DZzwnR/rNUu1LiQqjxBJtFiB+4uLR6tHQ6vKhTIwUsP5uZUQ7SM7JxVx3UihMynnTqjcsC+mczZg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz", + "integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==", "dev": true, "peer": true, "requires": { - "@codemirror/state": "^6.0.0", - "style-mod": "^4.0.0", + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "@codemirror/view": { + "version": "6.38.6", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz", + "integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==", + "dev": true, + "peer": true, + "requires": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, @@ -16706,9 +17596,9 @@ "optional": true }, "@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", "dev": true, "optional": true }, @@ -16741,26 +17631,39 @@ "optional": true }, "@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "requires": { "eslint-visitor-keys": "^3.4.3" } }, "@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true }, "@eslint/compat": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.9.tgz", - "integrity": "sha512-gCdSY54n7k+driCadyMNv8JSPzYLeDVM/ikZRtvtROBpRdFSkS8W9A82MqsaY7lZuwL0wiapgD0NT1xT0hyJsA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.2.tgz", + "integrity": "sha512-pR1DoD0h3HfF675QZx0xsyrsU8q70Z/plx7880NOhS02NuWLgBCOMDL787nUeQ7EWLkxv3bPQJaarjcPQb2Dwg==", "dev": true, - "requires": {} + "requires": { + "@eslint/core": "^1.1.0" + }, + "dependencies": { + "@eslint/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", + "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.15" + } + } + } }, "@eslint/config-array": { "version": "0.21.1", @@ -16774,9 +17677,9 @@ }, "dependencies": { "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -16785,44 +17688,44 @@ } }, "@eslint/config-helpers": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.1.tgz", - "integrity": "sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "requires": { - "@eslint/core": "^0.16.0" + "@eslint/core": "^0.17.0" } }, "@eslint/core": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", - "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "requires": { "@types/json-schema": "^7.0.15" } }, "@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz", + "integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==", "dev": true, "requires": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.3", "strip-json-comments": "^3.1.1" }, "dependencies": { "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -16831,9 +17734,9 @@ } }, "@eslint/js": { - "version": "9.38.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz", - "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==", + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", + "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", "dev": true }, "@eslint/object-schema": { @@ -16843,12 +17746,12 @@ "dev": true }, "@eslint/plugin-kit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", - "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "requires": { - "@eslint/core": "^0.16.0", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, @@ -17380,19 +18283,6 @@ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true }, - "@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==" - }, - "@isaacs/brace-expansion": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", - "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", - "requires": { - "@isaacs/balanced-match": "^4.0.1" - } - }, "@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -17796,6 +18686,12 @@ "ws": "^8.18.2" } }, + "@marijn/find-cluster-break": { + "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 + }, "@multiformats/dns": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.10.tgz", @@ -17849,6 +18745,118 @@ "@multiformats/multiaddr": "^12.3.0" } }, + "@napi-rs/magic-string": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string/-/magic-string-0.3.4.tgz", + "integrity": "sha512-DEWl/B99RQsyMT3F9bvrXuhL01/eIQp/dtNSE3G1jQ4mTGRcP4iHWxoPZ577WrbjUinrNgvRA5+08g8fkPgimQ==", + "dev": true, + "requires": { + "@napi-rs/magic-string-android-arm-eabi": "0.3.4", + "@napi-rs/magic-string-android-arm64": "0.3.4", + "@napi-rs/magic-string-darwin-arm64": "0.3.4", + "@napi-rs/magic-string-darwin-x64": "0.3.4", + "@napi-rs/magic-string-freebsd-x64": "0.3.4", + "@napi-rs/magic-string-linux-arm-gnueabihf": "0.3.4", + "@napi-rs/magic-string-linux-arm64-gnu": "0.3.4", + "@napi-rs/magic-string-linux-arm64-musl": "0.3.4", + "@napi-rs/magic-string-linux-x64-gnu": "0.3.4", + "@napi-rs/magic-string-linux-x64-musl": "0.3.4", + "@napi-rs/magic-string-win32-arm64-msvc": "0.3.4", + "@napi-rs/magic-string-win32-ia32-msvc": "0.3.4", + "@napi-rs/magic-string-win32-x64-msvc": "0.3.4" + } + }, + "@napi-rs/magic-string-android-arm-eabi": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-android-arm-eabi/-/magic-string-android-arm-eabi-0.3.4.tgz", + "integrity": "sha512-sszAYxqtzzJ4FDerDNHcqL9NhqPhj8W4DNiOanXYy50mA5oojlRtaAFPiB5ZMrWDBM32v5Q30LrmxQ4eTtu2Dg==", + "dev": true, + "optional": true + }, + "@napi-rs/magic-string-android-arm64": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-android-arm64/-/magic-string-android-arm64-0.3.4.tgz", + "integrity": "sha512-jdQ6HuO0X5rkX4MauTcWR4HWdgjakTOmmzqXg8L26+jOHVVG1LZE+Su5qvV4bP8vMb2h+vPE+JsnwqSmWymu3Q==", + "dev": true, + "optional": true + }, + "@napi-rs/magic-string-darwin-arm64": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-darwin-arm64/-/magic-string-darwin-arm64-0.3.4.tgz", + "integrity": "sha512-6NmMtvURce9/oq09XBZmuIeI6lPLGtEJ2ZPO/QzL3nLZa6wygiCnO/sFACKYNg5/73ET5HMMTeuogE1JI+r2Lw==", + "dev": true, + "optional": true + }, + "@napi-rs/magic-string-darwin-x64": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-darwin-x64/-/magic-string-darwin-x64-0.3.4.tgz", + "integrity": "sha512-f9LmfMiUAKDOtl0meOuLYeVb6OERrgGzrTg1Tn3R3fTAShM2kxRbfAuPE9ljuXxIFzOv/uqRNLSl/LqCJwpREA==", + "dev": true, + "optional": true + }, + "@napi-rs/magic-string-freebsd-x64": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-freebsd-x64/-/magic-string-freebsd-x64-0.3.4.tgz", + "integrity": "sha512-rqduQ4odiDK4QdM45xHWRTU4wtFIfpp8g8QGpz+3qqg7ivldDqbbNOrBaf6Oeu77uuEvWggnkyuChotfKgJdJQ==", + "dev": true, + "optional": true + }, + "@napi-rs/magic-string-linux-arm-gnueabihf": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-linux-arm-gnueabihf/-/magic-string-linux-arm-gnueabihf-0.3.4.tgz", + "integrity": "sha512-pVaJEdEpiPqIfq3M4+yMAATS7Z9muDcWYn8H7GFH1ygh8GwgLgKfy/n/lG2M6zp18Mwd0x7E2E/qg9GgCyUzoQ==", + "dev": true, + "optional": true + }, + "@napi-rs/magic-string-linux-arm64-gnu": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-linux-arm64-gnu/-/magic-string-linux-arm64-gnu-0.3.4.tgz", + "integrity": "sha512-9FwoAih/0tzEZx0BjYYIxWkSRMjonIn91RFM3q3MBs/evmThXUYXUqLNa1PPIkK1JoksswtDi48qWWLt8nGflQ==", + "dev": true, + "optional": true + }, + "@napi-rs/magic-string-linux-arm64-musl": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-linux-arm64-musl/-/magic-string-linux-arm64-musl-0.3.4.tgz", + "integrity": "sha512-wCR7R+WPOcAKmVQc1s6h6HwfwW1vL9pM8BjUY9Ljkdb8wt1LmZEmV2Sgfc1SfbRQzbyl+pKeufP6adRRQVzYDA==", + "dev": true, + "optional": true + }, + "@napi-rs/magic-string-linux-x64-gnu": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-linux-x64-gnu/-/magic-string-linux-x64-gnu-0.3.4.tgz", + "integrity": "sha512-sbxFDpYnt5WFbxQ1xozwOvh5A7IftqSI0WnE9O7KsQIOi0ej2dvFbfOW4tmFkvH/YP8KJELo5AhP2+kEq1DpYA==", + "dev": true, + "optional": true + }, + "@napi-rs/magic-string-linux-x64-musl": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-linux-x64-musl/-/magic-string-linux-x64-musl-0.3.4.tgz", + "integrity": "sha512-jN4h/7e2Ul8v3UK5IZu38NXLMdzVWhY4uEDlnwuUAhwRh26wBQ1/pLD97Uy/Z3dFNBQPcsv60XS9fOM1YDNT6w==", + "dev": true, + "optional": true + }, + "@napi-rs/magic-string-win32-arm64-msvc": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-win32-arm64-msvc/-/magic-string-win32-arm64-msvc-0.3.4.tgz", + "integrity": "sha512-gMUyTRHLWpzX2ntJFCbW2Gnla9Y/WUmbkZuW5SBAo/Jo8QojHn76Y4PNgnoXdzcsV9b/45RBxurYKAfFg9WTyg==", + "dev": true, + "optional": true + }, + "@napi-rs/magic-string-win32-ia32-msvc": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-win32-ia32-msvc/-/magic-string-win32-ia32-msvc-0.3.4.tgz", + "integrity": "sha512-QIMauMOvEHgL00K9np/c9CT/CRtLOz3mRTQqcZ9XGzSoAMrpxH71KSpDJrKl7h7Ro6TZ+hJ0C3T+JVuTCZNv4A==", + "dev": true, + "optional": true + }, + "@napi-rs/magic-string-win32-x64-msvc": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-win32-x64-msvc/-/magic-string-win32-x64-msvc-0.3.4.tgz", + "integrity": "sha512-V8FMSf828MzOI3P6/765MR7zHU6CUZqiyPhmAnwYoKFNxfv7oCviN/G6NcENeCdcYOvNgh5fYzaNLB96ndId5A==", + "dev": true, + "optional": true + }, "@noble/ciphers": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", @@ -17898,6 +18906,62 @@ "fastq": "^1.6.0" } }, + "@oxc-parser/binding-darwin-arm64": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.8.0.tgz", + "integrity": "sha512-3Dws5Wzj9efojjqvhS4ZF+Abh0EoiI5ciOE2kdLifMzSg4fnmYAIOktoUnPEo87TNIb4SiFJ5JgPBgEyq42Eow==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-darwin-x64": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.8.0.tgz", + "integrity": "sha512-DAUJ/mfq0Jn2VDYn69bhHTsIWj+aZ/viamexFwaLL7ntkIFmGpzAJZUlWofpY1IRJynKWW+P5AOLYXMllw4qUw==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.8.0.tgz", + "integrity": "sha512-ZHQVey/O4K3zTIKtpfsbtJIE8MPTRHRxgY3dejaoeFQGf9C3HasgF132Yp4zN/jOUx+x8czKPVa/Af40ViyhGQ==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-linux-arm64-musl": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.8.0.tgz", + "integrity": "sha512-Diw+Tnf5v+zAYXzDoSKCZsMaroU6GoqZMS7smfDtFnZYTHWZrsTmPBLUQe7AFiG7O7tkhsCdcWjOYgbVkrSVOA==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-linux-x64-gnu": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.8.0.tgz", + "integrity": "sha512-WloqcRrtQUVEP/Sy8ZeEgF0HgBKQjOv3zLFZqbC5ipkerKriGcVbsq3fOIMOi/55AM6/UhIAjeZGnoeco72JjQ==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-linux-x64-musl": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.8.0.tgz", + "integrity": "sha512-2j7BD9szwSXTvSj0Q8VE98UHGYvrgZzdLy4EyB0FilhQnopEfz+YV674rWGY2Il1VYxHJwGctrTJHvARolu37g==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.8.0.tgz", + "integrity": "sha512-mcomr1og17yCmnwn8Q7CRzrH9Va0HccWe4Ld3/u/elBsw0SEzYGVvECRzCyRglYAbKTtusz7as9Jee0RiMOMmg==", + "dev": true, + "optional": true + }, + "@oxc-parser/binding-win32-x64-msvc": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.8.0.tgz", + "integrity": "sha512-nIBkc1KZOVYUaHT3+U+gM354P3byMAIXMvlmLMbs0kWVRcI4vrzL8qwWpC6QdBQxWKZGqPEqGolv8H4dDYA9nQ==", + "dev": true, + "optional": true + }, "@parcel/source-map": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@parcel/source-map/-/source-map-2.1.1.tgz", @@ -17984,16 +19048,16 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, "@puppeteer/browsers": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.11.0.tgz", - "integrity": "sha512-n6oQX6mYkG8TRPuPXmbPidkUbsSRalhmaaVAQxvH1IkQy63cwsH+kOjB3e4cpCDHg0aSvsiX9bQ4s2VB6mGWUQ==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.0.tgz", + "integrity": "sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==", "dev": true, "requires": { "debug": "^4.4.3", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", - "semver": "^7.7.3", + "semver": "^7.7.4", "tar-fs": "^3.1.1", "yargs": "^17.7.2" } @@ -18282,14 +19346,14 @@ } }, "@smithy/fetch-http-handler": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz", - "integrity": "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.10.tgz", + "integrity": "sha512-qF4EcrEtEf2P6f2kGGuSVe1lan26cn7PsWJBC3vZJ6D16Fm5FSN06udOMVoW6hjzQM3W7VDFwtyUG2szQY50dA==", "requires": { - "@smithy/protocol-http": "^5.3.8", - "@smithy/querystring-builder": "^4.2.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", + "@smithy/protocol-http": "^5.3.9", + "@smithy/querystring-builder": "^4.2.9", + "@smithy/types": "^4.12.1", + "@smithy/util-base64": "^4.3.1", "tslib": "^2.6.2" } }, @@ -18335,31 +19399,31 @@ } }, "@smithy/is-array-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", - "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.1.tgz", + "integrity": "sha512-Yfu664Qbf1B4IYIsYgKoABt010daZjkaCRvdU/sPnZG6TtHOB0md0RjNdLGzxe5UIdn9js4ftPICzmkRa9RJ4Q==", "requires": { "tslib": "^2.6.2" } }, "@smithy/md5-js": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.8.tgz", - "integrity": "sha512-oGMaLj4tVZzLi3itBa9TCswgMBr7k9b+qKYowQ6x1rTyTuO1IU2YHdHUa+891OsOH+wCsH7aTPRsTJO3RMQmjQ==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.9.tgz", + "integrity": "sha512-ZCCWfGj4wvqV+5OS9e/GvR5jlR7j1mMB1UkGE+V7P1USFMwcL4Z4j5mO9nGvQGkfe20KM87ymbvZIcU9tHNlIg==", "requires": { - "@smithy/types": "^4.12.0", - "@smithy/util-utf8": "^4.2.0", + "@smithy/types": "^4.12.1", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "@smithy/middleware-apply-body-checksum": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-apply-body-checksum/-/middleware-apply-body-checksum-4.1.0.tgz", - "integrity": "sha512-6YOMHT3IYYjkxhA5h0pw3uu84aZInCuCVwZ8J/cObUEXxyCLNXV8Ws/T+Likp7LR2AEt2AjlqQ5VKZUbrSbJkA==", + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-apply-body-checksum/-/middleware-apply-body-checksum-4.3.9.tgz", + "integrity": "sha512-53wIgp1Jz5F886zUHTnVnXEUvDocPUs0icN5ja9oPIUWRXN9PpXOIQFUqEMQlU5zKMi+sfUz8yKnlsxYHkWjqw==", "requires": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", + "@smithy/is-array-buffer": "^4.2.1", + "@smithy/protocol-http": "^5.3.9", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" } }, @@ -18456,21 +19520,21 @@ } }, "@smithy/protocol-http": { - "version": "5.3.8", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.8.tgz", - "integrity": "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==", + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.9.tgz", + "integrity": "sha512-PRy4yZqsKI3Eab8TLc16Dj2NzC4dnw/8E95+++Jc+wwlkjBpAq3tNLqkLHMmSvDfxKQ+X5PmmCYt+rM/GcMKPA==", "requires": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" } }, "@smithy/querystring-builder": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz", - "integrity": "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.9.tgz", + "integrity": "sha512-/AIDaq0+ehv+QfeyAjCUFShwHIt+FA1IodsV/2AZE5h4PUZcQYv5sjmy9V67UWfsBoTjOPKUFYSRfGoNW9T2UQ==", "requires": { - "@smithy/types": "^4.12.0", - "@smithy/util-uri-escape": "^4.2.0", + "@smithy/types": "^4.12.1", + "@smithy/util-uri-escape": "^4.2.1", "tslib": "^2.6.2" } }, @@ -18530,9 +19594,9 @@ } }, "@smithy/types": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", - "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.1.tgz", + "integrity": "sha512-ow30Ze/DD02KH2p0eMyIF2+qJzGyNb0kFrnTRtPpuOkQ4hrgvLdaU4YC6r/K8aOrCML4FH0Cmm0aI4503L1Hwg==", "requires": { "tslib": "^2.6.2" } @@ -18548,12 +19612,12 @@ } }, "@smithy/util-base64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", - "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.1.tgz", + "integrity": "sha512-BKGuawX4Doq/bI/uEmg+Zyc36rJKWuin3py89PquXBIBqmbnJwBBsmKhdHfNEp0+A4TDgLmT/3MSKZ1SxHcR6w==", "requires": { - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@smithy/util-buffer-from": "^4.2.1", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, @@ -18574,11 +19638,11 @@ } }, "@smithy/util-buffer-from": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", - "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.1.tgz", + "integrity": "sha512-/swhmt1qTiVkaejlmMPPDgZhEaWb/HWMGRBheaxwuVkusp/z+ErJyQxO6kaXumOciZSWlmq6Z5mNylCd33X7Ig==", "requires": { - "@smithy/is-array-buffer": "^4.2.0", + "@smithy/is-array-buffer": "^4.2.1", "tslib": "^2.6.2" } }, @@ -18668,19 +19732,19 @@ } }, "@smithy/util-uri-escape": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", - "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.1.tgz", + "integrity": "sha512-YmiUDn2eo2IOiWYYvGQkgX5ZkBSiTQu4FlDo5jNPpAxng2t6Sjb6WutnZV9l6VR4eJul1ABmCrnWBC9hKHQa6Q==", "requires": { "tslib": "^2.6.2" } }, "@smithy/util-utf8": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", - "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.1.tgz", + "integrity": "sha512-DSIwNaWtmzrNQHv8g7DBGR9mulSit65KSj5ymGEIAknmIN8IpbZefEep10LaMG/P/xquwbmJ1h9ectz8z6mV6g==", "requires": { - "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-buffer-from": "^4.2.1", "tslib": "^2.6.2" } }, @@ -18785,16 +19849,16 @@ "requires": {} }, "@sveltejs/vite-plugin-svelte": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.1.tgz", - "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz", + "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", "dev": true, "peer": true, "requires": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", - "debug": "^4.4.1", "deepmerge": "^4.3.1", - "magic-string": "^0.30.17", + "magic-string": "^0.30.21", + "obug": "^2.1.0", "vitefu": "^1.1.1" } }, @@ -18814,9 +19878,9 @@ "dev": true }, "@tsconfig/svelte": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.5.tgz", - "integrity": "sha512-48fAnUjKye38FvMiNOj0J9I/4XlQQiZlpe9xaNPfe8vy2Y1hFBt8g1yqf2EGjVvHavo4jf2lC+TQyENCr4BJBQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.8.tgz", + "integrity": "sha512-UkNnw1/oFEfecR8ypyHIQuWYdkPvHiwcQ78sh+ymIiYoF+uc5H1UBetbjyqT+vgGJ3qQN6nhucJviX6HesWtKQ==", "dev": true }, "@types/chai": { @@ -18854,9 +19918,9 @@ "dev": true }, "@types/deno": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@types/deno/-/deno-2.3.0.tgz", - "integrity": "sha512-/4SyefQpKjwNKGkq9qG3Ln7MazfbWKvydyVFBnXzP5OQA4u1paoFtaOe1iHKycIWHHkhYag0lPxyheThV1ijzw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@types/deno/-/deno-2.5.0.tgz", + "integrity": "sha512-g8JS38vmc0S87jKsFzre+0ZyMOUDHPVokEJymSCRlL57h6f/FdKPWBXgdFh3Z8Ees9sz11qt9VWELU9Y9ZkiVw==", "dev": true }, "@types/diff-match-patch": { @@ -18928,11 +19992,18 @@ "dev": true }, "@types/node": { - "version": "22.19.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.8.tgz", - "integrity": "sha512-ebO/Yl+EAvVe8DnMfi+iaAyIqYdK0q/q0y0rw82INWEKJOBe6b/P3YWE8NW7oOlF/nXFNrHwhARrN/hdgDkraA==", + "version": "24.10.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", + "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", "requires": { - "undici-types": "~6.21.0" + "undici-types": "~7.16.0" + }, + "dependencies": { + "undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==" + } } }, "@types/phoenix": { @@ -19182,20 +20253,19 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz", - "integrity": "sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", + "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", "dev": true, "requires": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.46.2", - "@typescript-eslint/type-utils": "8.46.2", - "@typescript-eslint/utils": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/type-utils": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.4.0" }, "dependencies": { "ignore": { @@ -19207,130 +20277,109 @@ } }, "@typescript-eslint/parser": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.2.tgz", - "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", + "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "peer": true, "requires": { - "@typescript-eslint/scope-manager": "8.46.2", - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3" } }, "@typescript-eslint/project-service": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.2.tgz", - "integrity": "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", + "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", "dev": true, "requires": { - "@typescript-eslint/tsconfig-utils": "^8.46.2", - "@typescript-eslint/types": "^8.46.2", - "debug": "^4.3.4" + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", + "debug": "^4.4.3" } }, "@typescript-eslint/scope-manager": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz", - "integrity": "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", + "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", "dev": true, "requires": { - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2" + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" } }, "@typescript-eslint/tsconfig-utils": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz", - "integrity": "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", + "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", "dev": true, "requires": {} }, "@typescript-eslint/type-utils": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.2.tgz", - "integrity": "sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", + "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", "dev": true, "requires": { - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2", - "@typescript-eslint/utils": "8.46.2", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" } }, "@typescript-eslint/types": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.2.tgz", - "integrity": "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz", - "integrity": "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", + "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", "dev": true, "requires": { - "@typescript-eslint/project-service": "8.46.2", - "@typescript-eslint/tsconfig-utils": "8.46.2", - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/visitor-keys": "8.46.2", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" } }, "@typescript-eslint/utils": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.2.tgz", - "integrity": "sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", + "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", "dev": true, "requires": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.46.2", - "@typescript-eslint/types": "8.46.2", - "@typescript-eslint/typescript-estree": "8.46.2" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1" } }, "@typescript-eslint/visitor-keys": { - "version": "8.46.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz", - "integrity": "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", + "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", "dev": true, "requires": { - "@typescript-eslint/types": "8.46.2", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.56.1", + "eslint-visitor-keys": "^5.0.0" }, "dependencies": { "eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "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 } } @@ -19686,26 +20735,33 @@ } }, "@wdio/config": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.23.0.tgz", - "integrity": "sha512-hhtngUG2uCxYmScSEor+k22EVlsTW3ARXgke8NPVeQA4p1+GC2CvRZi4P7nmhRTZubgLrENYYsveFcYR+1UXhQ==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.24.0.tgz", + "integrity": "sha512-rcHu0eG16rSEmHL0sEKDcr/vYFmGhQ5GOlmlx54r+1sgh6sf136q+kth4169s16XqviWGW3LjZbUfpTK29pGtw==", "dev": true, "requires": { "@wdio/logger": "9.18.0", - "@wdio/types": "9.20.0", - "@wdio/utils": "9.23.0", + "@wdio/types": "9.24.0", + "@wdio/utils": "9.24.0", "deepmerge-ts": "^7.0.3", "glob": "^10.2.2", - "import-meta-resolve": "^4.0.0" + "import-meta-resolve": "^4.0.0", + "jiti": "^2.6.1" }, "dependencies": { + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, "requires": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" } }, "glob": { @@ -19722,23 +20778,13 @@ "path-scurry": "^1.11.1" } }, - "jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "requires": { - "@isaacs/cliui": "^8.0.2", - "@pkgjs/parseargs": "^0.11.0" - } - }, "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", + "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" } }, "path-scurry": { @@ -19790,9 +20836,9 @@ } }, "@wdio/protocols": { - "version": "9.16.2", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.16.2.tgz", - "integrity": "sha512-h3k97/lzmyw5MowqceAuY3HX/wGJojXHkiPXA3WlhGPCaa2h4+GovV2nJtRvknCKsE7UHA1xB5SWeI8MzloBew==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.24.0.tgz", + "integrity": "sha512-ozQKYddBLT4TRvU9J+fGrhVUtx3iDAe+KNCJcTDMFMxNSdDMR2xFQdNp8HLHypspk58oXTYCvz6ZYjySthhqsw==", "dev": true }, "@wdio/repl": { @@ -19805,9 +20851,9 @@ }, "dependencies": { "@types/node": { - "version": "20.19.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.31.tgz", - "integrity": "sha512-5jsi0wpncvTD33Sh1UCgacK37FFwDn+EG7wCmEvs62fCvBL+n8/76cAYDok21NF6+jaVWIqKwCZyX7Vbu8eB3A==", + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", "dev": true, "requires": { "undici-types": "~6.21.0" @@ -19816,18 +20862,18 @@ } }, "@wdio/types": { - "version": "9.20.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.20.0.tgz", - "integrity": "sha512-zMmAtse2UMCSOW76mvK3OejauAdcFGuKopNRH7crI0gwKTZtvV89yXWRziz9cVXpFgfmJCjf9edxKFWdhuF5yw==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.24.0.tgz", + "integrity": "sha512-PYYunNl8Uq1r8YMJAK6ReRy/V/XIrCSyj5cpCtR5EqCL6heETOORFj7gt4uPnzidfgbtMBcCru0LgjjlMiH1UQ==", "dev": true, "requires": { "@types/node": "^20.1.0" }, "dependencies": { "@types/node": { - "version": "20.19.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.31.tgz", - "integrity": "sha512-5jsi0wpncvTD33Sh1UCgacK37FFwDn+EG7wCmEvs62fCvBL+n8/76cAYDok21NF6+jaVWIqKwCZyX7Vbu8eB3A==", + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", "dev": true, "requires": { "undici-types": "~6.21.0" @@ -19836,14 +20882,14 @@ } }, "@wdio/utils": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.23.0.tgz", - "integrity": "sha512-WhXuVSxEvPw/i34bL1aCHAOi+4g29kRkIMyBShNSxH+Shxh2G91RJYsXm4IAiPMGcC4H6G8T2VcbZ32qnGPm5Q==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.24.0.tgz", + "integrity": "sha512-6WhtzC5SNCGRBTkaObX6A07Ofnnyyf+TQH/d/fuhZRqvBknrP4AMMZF+PFxGl1fwdySWdBn+gV2QLE+52Byowg==", "dev": true, "requires": { "@puppeteer/browsers": "^2.2.0", "@wdio/logger": "9.18.0", - "@wdio/types": "9.20.0", + "@wdio/types": "9.24.0", "decamelize": "^6.0.0", "deepmerge-ts": "^7.0.3", "edgedriver": "^6.1.2", @@ -19858,9 +20904,9 @@ } }, "@zip.js/zip.js": { - "version": "2.8.13", - "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.13.tgz", - "integrity": "sha512-UcWZ5i9Hlspgo+8q8PUPd9TmDYM62eqfb/0iZ5SF3bqfX/uFUmV660MC9Lsvon7/sidzesXLQlIf8/mbDUf6gQ==", + "version": "2.8.21", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.21.tgz", + "integrity": "sha512-fkyzXISE3IMrstDO1AgPkJCx14MYHP/suIGiAovEYEuBjq3mffsuL6aMV7ohOSjW4rXtuACuUfpA3GtITgdtYg==", "dev": true }, "abort-controller": { @@ -19906,9 +20952,9 @@ "dev": true }, "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -19965,13 +21011,19 @@ "readable-stream": "^4.0.0" }, "dependencies": { + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, "requires": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" } }, "glob": { @@ -19988,23 +21040,13 @@ "path-scurry": "^1.11.1" } }, - "jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "requires": { - "@isaacs/cliui": "^8.0.2", - "@pkgjs/parseargs": "^0.11.0" - } - }, "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", + "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" } }, "path-scurry": { @@ -20198,9 +21240,9 @@ "requires": {} }, "bare-fs": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.2.tgz", - "integrity": "sha512-veTnRzkb6aPHOvSKIOy60KzURfBdUflr5VReI+NSaPL6xf+XLdONQgZgpYvUuZLVQ8dCqxpBAudaOM1+KpAUxw==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.4.tgz", + "integrity": "sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==", "dev": true, "optional": true, "requires": { @@ -20229,13 +21271,14 @@ } }, "bare-stream": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", - "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.8.0.tgz", + "integrity": "sha512-reUN0M2sHRqCdG4lUK3Fw8w98eeUIZHL5c3H7Mbhk2yVBL+oofgaIp0ieLfD5QXwPCypBpmEEKU2WZKzbAk8GA==", "dev": true, "optional": true, "requires": { - "streamx": "^2.21.0" + "streamx": "^2.21.0", + "teex": "^1.0.1" } }, "bare-url": { @@ -20254,9 +21297,9 @@ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "basic-ftp": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.1.0.tgz", - "integrity": "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz", + "integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==", "dev": true }, "bl": { @@ -20444,6 +21487,12 @@ "readdirp": "^4.0.1" } }, + "cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true + }, "cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -20560,6 +21609,12 @@ "readable-stream": "^4.0.0" } }, + "crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "dev": true + }, "cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -20849,9 +21904,9 @@ } }, "dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "dev": true }, "dotenv-cli": { @@ -20933,18 +21988,18 @@ }, "dependencies": { "isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", "dev": true }, "which": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", - "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", "dev": true, "requires": { - "isexe": "^3.1.1" + "isexe": "^4.0.0" } } } @@ -21154,9 +22209,9 @@ } }, "esbuild-svelte": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.9.3.tgz", - "integrity": "sha512-CgEcGY1r/d16+aggec3czoFBEBaYIrFOnMxpsO6fWNaNEqHregPN5DLAPZDqrL7rXDNplW+WMu8s3GMq9FqgJA==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.9.4.tgz", + "integrity": "sha512-v/a0GjkKN06nal2QLluxjk2GXsei3fdtjIuHRa6pVnri5rQBZ6pj4a2WwjLfRojgRsLwDHl4xSeZ1BeUHsqQrw==", "dev": true, "requires": { "@jridgewell/trace-mapping": "^0.3.19" @@ -21186,20 +22241,20 @@ } }, "eslint": { - "version": "9.38.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz", - "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", + "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.1", - "@eslint/core": "^0.16.0", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.38.0", - "@eslint/plugin-kit": "^0.4.0", + "@eslint/js": "9.39.3", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -21235,9 +22290,9 @@ "dev": true }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -21333,9 +22388,9 @@ } }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -21350,9 +22405,9 @@ } }, "eslint-plugin-svelte": { - "version": "3.12.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.12.5.tgz", - "integrity": "sha512-4KRG84eAHQfYd9OjZ1K7sCHy0nox+9KwT+s5WCCku3jTim5RV4tVENob274nCwIaApXsYPKAUAZFBxKZ3Wyfjw==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.15.0.tgz", + "integrity": "sha512-QKB7zqfuB8aChOfBTComgDptMf2yxiJx7FE04nneCmtQzgTHvY8UJkuh8J2Rz7KB9FFV9aTHX6r7rdYGvG8T9Q==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.6.1", @@ -21597,11 +22652,11 @@ } }, "fast-xml-parser": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.4.tgz", - "integrity": "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==", + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.6.tgz", + "integrity": "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==", "requires": { - "strnum": "^2.1.0" + "strnum": "^2.1.2" } }, "fastq": { @@ -21928,17 +22983,14 @@ } }, "glob": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", - "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, "requires": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" } }, "glob-parent": { @@ -21997,9 +23049,9 @@ } }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -22025,12 +23077,6 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, - "graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, "has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -22779,14 +23825,22 @@ } }, "jackspeak": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", - "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "requires": { - "@isaacs/cliui": "^8.0.2" + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" } }, + "jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "peer": true + }, "js-sdsl": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", @@ -23299,11 +24353,26 @@ } }, "minimatch": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", - "integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", "requires": { - "@isaacs/brace-expansion": "^5.0.1" + "brace-expansion": "^5.0.2" + }, + "dependencies": { + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==" + }, + "brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "requires": { + "balanced-match": "^4.0.2" + } + } } }, "minimist": { @@ -23312,9 +24381,9 @@ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, "minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true }, "mitt": { @@ -23324,9 +24393,9 @@ "dev": true }, "modern-tar": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.3.tgz", - "integrity": "sha512-4W79zekKGyYU4JXVmB78DOscMFaJth2gGhgfTl2alWE4rNe3nf4N2pqenQ0rEtIewrnD79M687Ouba3YGTLOvg==", + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.5.tgz", + "integrity": "sha512-YTefgdpKKFgoTDbEUqXqgUJct2OG6/4hs4XWLsxcHkDLj/x/V8WmKIRppPnXP5feQ7d1vuYWSp3qKkxfwaFaxA==", "dev": true }, "moment": { @@ -23512,9 +24581,9 @@ } }, "obsidian": { - "version": "1.8.7", - "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.8.7.tgz", - "integrity": "sha512-h4bWwNFAGRXlMlMAzdEiIM2ppTGlrh7uGOJS6w4gClrsjc+ei/3YAtU2VdFUlCiPuTHpY4aBpFJJW75S1Tl/JA==", + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.12.3.tgz", + "integrity": "sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw==", "dev": true, "requires": { "@types/codemirror": "5.60.8", @@ -23569,6 +24638,22 @@ "safe-push-apply": "^1.0.0" } }, + "oxc-parser": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.8.0.tgz", + "integrity": "sha512-ObPeMkbDX7igb7NyyAC8CbVC3fY+YmlMsxsRQ2oyFBkpQtI5tjoyqSDKbS9A9EcJvt2q89C4UoC+HjVBdLYYJg==", + "dev": true, + "requires": { + "@oxc-parser/binding-darwin-arm64": "0.8.0", + "@oxc-parser/binding-darwin-x64": "0.8.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.8.0", + "@oxc-parser/binding-linux-arm64-musl": "0.8.0", + "@oxc-parser/binding-linux-x64-gnu": "0.8.0", + "@oxc-parser/binding-linux-x64-musl": "0.8.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.8.0", + "@oxc-parser/binding-win32-x64-msvc": "0.8.0" + } + }, "p-defer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-4.0.1.tgz", @@ -23745,9 +24830,9 @@ "dev": true }, "path-scurry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", - "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, "requires": { "lru-cache": "^11.0.0", @@ -23755,9 +24840,9 @@ }, "dependencies": { "lru-cache": { - "version": "11.2.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", - "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "dev": true } } @@ -23855,13 +24940,13 @@ } }, "playwright": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", - "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", "dev": true, "requires": { "fsevents": "2.3.2", - "playwright-core": "1.57.0" + "playwright-core": "1.58.2" }, "dependencies": { "fsevents": { @@ -23874,9 +24959,9 @@ } }, "playwright-core": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", - "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", "dev": true }, "pngjs": { @@ -24217,9 +25302,9 @@ "dev": true }, "prettier": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", - "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true }, "process": { @@ -24402,9 +25487,9 @@ } }, "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.7.tgz", + "integrity": "sha512-FjiwU9HaHW6YB3H4a1sFudnv93lvydNjz2lmyUXR6IwKhGI+bgL3SOZrBGn6kvvX2pJvhEkGSGjyTHN47O4rqA==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -24651,9 +25736,9 @@ "dev": true }, "semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true }, "serialize-error": { @@ -25013,9 +26098,9 @@ "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==" }, "style-mod": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz", - "integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==", + "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 }, "sublevel-pouchdb": { @@ -25094,9 +26179,9 @@ } }, "svelte-check": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.3.tgz", - "integrity": "sha512-RYP0bEwenDXzfv0P1sKAwjZSlaRyqBn0Fz1TVni58lqyEiqgwztTpmodJrGzP6ZT2aHl4MbTvWP6gbmQ3FOnBg==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.3.tgz", + "integrity": "sha512-4HtdEv2hOoLCEsSXI+RDELk9okP/4sImWa7X02OjMFFOWeSdFF3NFy3vqpw0z+eH9C88J9vxZfUXz/Uv2A1ANw==", "dev": true, "requires": { "@jridgewell/trace-mapping": "^0.3.25", @@ -25167,6 +26252,16 @@ "streamx": "^2.15.0" } }, + "teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "optional": true, + "requires": { + "streamx": "^2.12.5" + } + }, "terser": { "version": "5.39.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", @@ -25317,9 +26412,9 @@ } }, "ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "requires": {} }, @@ -25341,15 +26436,226 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, "tsx": { - "version": "4.20.6", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", - "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "peer": true, "requires": { - "esbuild": "~0.25.0", + "esbuild": "~0.27.0", "fsevents": "~2.3.3", "get-tsconfig": "^4.7.5" + }, + "dependencies": { + "@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "dev": true, + "optional": true + }, + "esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + } } }, "type-check": { @@ -25475,15 +26781,16 @@ } }, "undici": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.20.0.tgz", - "integrity": "sha512-MJZrkjyd7DeC+uPZh+5/YaMDxFiiEEaDgbUSVMXayofAkDWF1088CDo+2RPg7B1BuS1qf1vgNE7xqwPxE0DuSQ==", + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", + "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", "dev": true }, "undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true }, "universalify": { "version": "0.2.0", @@ -25534,9 +26841,9 @@ "dev": true }, "vite": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", - "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "peer": true, "requires": { @@ -25550,212 +26857,212 @@ }, "dependencies": { "@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", "dev": true, "optional": true }, "@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", "dev": true, "optional": true }, "@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", "dev": true, "optional": true }, "@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", "dev": true, "optional": true }, "@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", "dev": true, "optional": true }, "@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", "dev": true, "optional": true }, "@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", "dev": true, "optional": true }, "@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", "dev": true, "optional": true }, "@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", "dev": true, "optional": true }, "@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", "dev": true, "optional": true }, "@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", "dev": true, "optional": true }, "@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", "dev": true, "optional": true }, "@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", "dev": true, "optional": true }, "@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", "dev": true, "optional": true }, "@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", "dev": true, "optional": true }, "@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", "dev": true, "optional": true }, "@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", "dev": true, "optional": true }, "@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", "dev": true, "optional": true }, "@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", "dev": true, "optional": true }, "@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", "dev": true, "optional": true }, "@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", "dev": true, "optional": true }, "@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", "dev": true, "optional": true }, "@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", "dev": true, "optional": true }, "@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", "dev": true, "optional": true }, "@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", "dev": true, "optional": true }, "esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", "dev": true, "requires": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" } }, "fdir": { @@ -25825,9 +27132,9 @@ "dev": true }, "w3c-keyname": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.4.tgz", - "integrity": "sha512-tOhfEwEzFLJzf6d1ZPkYfGj+FWhIpBux9ppoP3rlclw3Z0BZv3N7b7030Z1kYth+6rDuAsXUFr+d0VE6Ed1ikw==", + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", "dev": true }, "wait-port": { @@ -25876,18 +27183,18 @@ "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==" }, "webdriver": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.23.0.tgz", - "integrity": "sha512-XkZOhjoBOY7maKI3BhDF2rNiDne4wBD6Gw6VUnt4X9b7j9NtfzcCrThBlT0hnA8W77bWNtMRCSpw9Ajy08HqKg==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.24.0.tgz", + "integrity": "sha512-2R31Ey83NzMsafkl4hdFq6GlIBvOODQMkueLjeRqYAITu3QCYiq9oqBdnWA6CdePuV4dbKlYsKRX0mwMiPclDA==", "dev": true, "requires": { "@types/node": "^20.1.0", "@types/ws": "^8.5.3", - "@wdio/config": "9.23.0", + "@wdio/config": "9.24.0", "@wdio/logger": "9.18.0", - "@wdio/protocols": "9.16.2", - "@wdio/types": "9.20.0", - "@wdio/utils": "9.23.0", + "@wdio/protocols": "9.24.0", + "@wdio/types": "9.24.0", + "@wdio/utils": "9.24.0", "deepmerge-ts": "^7.0.3", "https-proxy-agent": "^7.0.6", "undici": "^6.21.3", @@ -25895,9 +27202,9 @@ }, "dependencies": { "@types/node": { - "version": "20.19.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.31.tgz", - "integrity": "sha512-5jsi0wpncvTD33Sh1UCgacK37FFwDn+EG7wCmEvs62fCvBL+n8/76cAYDok21NF6+jaVWIqKwCZyX7Vbu8eB3A==", + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", "dev": true, "requires": { "undici-types": "~6.21.0" @@ -25912,19 +27219,19 @@ } }, "webdriverio": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.23.0.tgz", - "integrity": "sha512-Y5y4jpwHvuduUfup+gXTuCU6AROn/k6qOba3st0laFluKHY+q5SHOpQAJdS8acYLwE8caDQ2dXJhmXyxuJrm0Q==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.24.0.tgz", + "integrity": "sha512-LTJt6Z/iDM0ne/4ytd3BykoPv9CuJ+CAILOzlwFeMGn4Mj02i4Bk2Rg9o/jeJ89f52hnv4OPmNjD0e8nzWAy5g==", "dev": true, "requires": { "@types/node": "^20.11.30", "@types/sinonjs__fake-timers": "^8.1.5", - "@wdio/config": "9.23.0", + "@wdio/config": "9.24.0", "@wdio/logger": "9.18.0", - "@wdio/protocols": "9.16.2", + "@wdio/protocols": "9.24.0", "@wdio/repl": "9.16.2", - "@wdio/types": "9.20.0", - "@wdio/utils": "9.23.0", + "@wdio/types": "9.24.0", + "@wdio/utils": "9.24.0", "archiver": "^7.0.1", "aria-query": "^5.3.0", "cheerio": "^1.0.0-rc.12", @@ -25941,13 +27248,13 @@ "rgb2hex": "0.2.5", "serialize-error": "^12.0.0", "urlpattern-polyfill": "^10.0.0", - "webdriver": "9.23.0" + "webdriver": "9.24.0" }, "dependencies": { "@types/node": { - "version": "20.19.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.31.tgz", - "integrity": "sha512-5jsi0wpncvTD33Sh1UCgacK37FFwDn+EG7wCmEvs62fCvBL+n8/76cAYDok21NF6+jaVWIqKwCZyX7Vbu8eB3A==", + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", "dev": true, "requires": { "undici-types": "~6.21.0" diff --git a/package.json b/package.json index 33e92b3..e6c09eb 100644 --- a/package.json +++ b/package.json @@ -59,15 +59,15 @@ "author": "vorotamoroz", "license": "MIT", "devDependencies": { - "@chialab/esbuild-plugin-worker": "^0.18.1", - "@eslint/compat": "^1.2.7", - "@eslint/eslintrc": "^3.3.0", - "@eslint/js": "^9.21.0", - "@sveltejs/vite-plugin-svelte": "^6.2.1", - "@tsconfig/svelte": "^5.0.5", - "@types/deno": "^2.3.0", + "@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", + "@types/deno": "^2.5.0", "@types/diff-match-patch": "^1.0.36", - "@types/node": "^22.13.8", + "@types/node": "^24.10.13", "@types/pouchdb": "^6.4.2", "@types/pouchdb-adapter-http": "^6.1.6", "@types/pouchdb-adapter-idb": "^6.1.7", @@ -76,25 +76,25 @@ "@types/pouchdb-mapreduce": "^6.1.10", "@types/pouchdb-replication": "^6.4.7", "@types/transform-pouch": "^1.0.6", - "@typescript-eslint/eslint-plugin": "8.46.2", - "@typescript-eslint/parser": "8.46.2", + "@typescript-eslint/eslint-plugin": "8.56.1", + "@typescript-eslint/parser": "8.56.1", "@vitest/browser": "^4.0.16", "@vitest/browser-playwright": "^4.0.16", "@vitest/coverage-v8": "^4.0.16", "builtin-modules": "5.0.0", - "dotenv": "^17.2.3", + "dotenv": "^17.3.1", "dotenv-cli": "^11.0.0", "esbuild": "0.25.0", "esbuild-plugin-inline-worker": "^0.1.1", - "esbuild-svelte": "^0.9.3", - "eslint": "^9.38.0", + "esbuild-svelte": "^0.9.4", + "eslint": "^9.39.3", "eslint-plugin-import": "^2.32.0", - "eslint-plugin-svelte": "^3.12.4", + "eslint-plugin-svelte": "^3.15.0", "events": "^3.3.0", - "glob": "^11.0.3", - "obsidian": "^1.8.7", - "playwright": "^1.57.0", - "postcss": "^8.5.3", + "glob": "^13.0.6", + "obsidian": "^1.12.3", + "playwright": "^1.58.2", + "postcss": "^8.5.6", "postcss-load-config": "^6.0.1", "pouchdb-adapter-http": "^9.0.0", "pouchdb-adapter-idb": "^9.0.0", @@ -107,32 +107,32 @@ "pouchdb-merge": "^9.0.0", "pouchdb-replication": "^9.0.0", "pouchdb-utils": "^9.0.0", - "prettier": "3.5.2", + "prettier": "3.8.1", "rollup-plugin-copy": "^3.5.0", "svelte": "5.41.1", - "svelte-check": "^4.3.3", + "svelte-check": "^4.4.3", "svelte-preprocess": "^6.0.3", "terser": "^5.39.0", "transform-pouch": "^2.0.0", "tslib": "^2.8.1", - "tsx": "^4.20.6", + "tsx": "^4.21.0", "typescript": "5.9.3", - "vite": "^7.3.0", + "vite": "^7.3.1", "vitest": "^4.0.16", - "webdriverio": "^9.23.0", - "yaml": "^2.8.0" + "webdriverio": "^9.24.0", + "yaml": "^2.8.2" }, "dependencies": { "@aws-sdk/client-s3": "^3.808.0", - "@smithy/fetch-http-handler": "^5.0.2", - "@smithy/md5-js": "^4.0.2", - "@smithy/middleware-apply-body-checksum": "^4.1.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/querystring-builder": "^4.0.2", + "@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", "diff-match-patch": "^1.0.5", "fflate": "^0.8.2", "idb": "^8.0.3", - "minimatch": "^10.0.2", + "minimatch": "^10.2.2", "octagonal-wheels": "^0.1.45", "qrcode-generator": "^1.4.4", "trystero": "^0.22.0", diff --git a/updates.md b/updates.md index bfacd7a..728ea93 100644 --- a/updates.md +++ b/updates.md @@ -24,7 +24,7 @@ However, as this update is very substantial, please do feel free to let me know ### Refactored -Architectural Overhaul: +#### Architectural Overhaul: - A major transition from Class-based Modules to a Service/Middleware architecture has begun. - Many modules (for example, `ModulePouchDB`, `ModuleLocalDatabaseObsidian`, `ModuleKeyValueDB`) have been removed or integrated into specific Services (`database`, `keyValueDB`, etc.). @@ -36,24 +36,30 @@ Architectural Overhaul: - Lifecycle: - Application LifeCycle now starts in `Main` rather than `ServiceHub` or `ObsidianMenuModule`, ensuring smoother startup coordination. -New Services & Utilities: +#### New Services & Utilities: - Added a `control` service to orchestrate other services (for example, handling stop/start logic during settings realisation). - Added `UnresolvedErrorManager` to handle and display unresolved errors in a unified way. - Added `logUtils` to unify logging injection and formatting. - `VaultService.isTargetFile` now uses multiple, distinct checkers for better extensibility. -Code Separation: +#### Code Separation: - Separated Obsidian-specific logic from base logic for `StorageEventManager` and `FileAccess` modules. - Moved reactive state values and statistics from the main plug-in instance to the services responsible for them. -Internal Cleanups: +#### Internal Cleanups: - Many functions have been renamed for clarity (for example, `_isTargetFileByLocalDB` is now `_isTargetAcceptedByLocalDB`). - Added `override` keywords to overridden items and removed dynamic binding for clearer code inheritance. - Moved common functions to the common library. +#### Dependencies: + +- Bumped dependencies simply to a point where they can be considered problem-free (by human-powered-artefacts-diff). + - Svelte, terser, and more something will be bumped later. they have a significant impact on diff and paints it totally. + - You may be surprised but, when bumping the library, I am actually checking for any unintended code. + ## 0.25.43 5th, February, 2026 From 278935f85d796f2298138565fbc7edcdc05cf058 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Tue, 24 Feb 2026 11:08:08 +0000 Subject: [PATCH 031/339] Fix mock for testing --- test/harness/obsidian-mock.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/harness/obsidian-mock.ts b/test/harness/obsidian-mock.ts index 7868ae0..b7fde70 100644 --- a/test/harness/obsidian-mock.ts +++ b/test/harness/obsidian-mock.ts @@ -481,8 +481,14 @@ export class Plugin { } export class Notice { + private _key:number; + private static _counter = 0; constructor(message: string) { - console.log("Notice:", message); + this._key = Notice._counter++; + console.log(`Notice [${this._key}]:`, message); + } + setMessage(message: string) { + console.log(`Notice [${this._key}]:`, message); } } From 2199c1ebd3a559642b5e1f7450896bd2c921003b Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Tue, 24 Feb 2026 11:13:58 +0000 Subject: [PATCH 032/339] change: test:docker-all:xxx to be parallel. --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index e6c09eb..1d97e7a 100644 --- a/package.json +++ b/package.json @@ -48,9 +48,9 @@ "test:docker-p2p:start": "npm run test:docker-p2p:up && sleep 3 && npm run test:docker-p2p:init", "test:docker-p2p:down": "npx dotenv-cli -e .env -e .test.env -- ./test/shell/p2p-stop.sh", "test:docker-p2p:stop": "npm run test:docker-p2p:down", - "test:docker-all:up": "npm run test:docker-couchdb:up && npm run test:docker-s3:up && npm run test:docker-p2p:up", - "test:docker-all:init": "npm run test:docker-couchdb:init && npm run test:docker-s3:init && npm run test:docker-p2p:init", - "test:docker-all:down": "npm run test:docker-couchdb:down && npm run test:docker-s3:down && npm run test:docker-p2p:down", + "test:docker-all:up": "npm run test:docker-couchdb:up ; npm run test:docker-s3:up ; npm run test:docker-p2p:up", + "test:docker-all:init": "npm run test:docker-couchdb:init ; npm run test:docker-s3:init ; npm run test:docker-p2p:init", + "test:docker-all:down": "npm run test:docker-couchdb:down ; npm run test:docker-s3:down ; npm run test:docker-p2p:down", "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" From c9a71e207601db6efc91a662a1ff95c3fe348e9d Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Tue, 24 Feb 2026 11:14:14 +0000 Subject: [PATCH 033/339] fix: align dependency versions to main --- src/apps/webpeer/package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/apps/webpeer/package.json b/src/apps/webpeer/package.json index 990666f..584432c 100644 --- a/src/apps/webpeer/package.json +++ b/src/apps/webpeer/package.json @@ -11,13 +11,13 @@ }, "dependencies": {}, "devDependencies": { - "eslint-plugin-svelte": "^3.12.4", - "@sveltejs/vite-plugin-svelte": "^6.2.1", - "@tsconfig/svelte": "^5.0.5", + "eslint-plugin-svelte": "^3.15.0", + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "@tsconfig/svelte": "^5.0.8", "svelte": "5.41.1", - "svelte-check": "^4.3.3", + "svelte-check": "^443.3", "typescript": "5.9.3", - "vite": "^7.3.0" + "vite": "^7.3.1" }, "imports": { "../../src/worker/bgWorker.ts": "../../src/worker/bgWorker.mock.ts", From be1642f1c1ef8d67002ec4bddc9bdf02fa6f7ee3 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Tue, 24 Feb 2026 11:20:59 +0000 Subject: [PATCH 034/339] Fix grammatical errors. --- updates.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/updates.md b/updates.md index 728ea93..a10357e 100644 --- a/updates.md +++ b/updates.md @@ -57,8 +57,8 @@ However, as this update is very substantial, please do feel free to let me know #### Dependencies: - Bumped dependencies simply to a point where they can be considered problem-free (by human-powered-artefacts-diff). - - Svelte, terser, and more something will be bumped later. they have a significant impact on diff and paints it totally. - - You may be surprised but, when bumping the library, I am actually checking for any unintended code. + - Svelte, terser, and more something will be bumped later. They have a significant impact on the diff and paint it totally. + - You may be surprised, but when I bump the library, I am actually checking for any unintended code. ## 0.25.43 From 19c03ec8d8589388dcfc168645855cbab7451898 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 25 Feb 2026 09:38:31 +0000 Subject: [PATCH 035/339] ### Refactored - `ModuleTargetFilter`, which was responsible for checking if a file is a target file, has been ported to a serviceFeature. - And also tests have been added. The middleware-style-power. - `ModuleObsidianAPI` has been removed and implemented in `APIService` and `RemoteService`. - Now `APIService` is responsible for the network-online-status, not `databaseService.managers.networkManager`. --- src/lib | 2 +- src/main.ts | 13 +- src/modules/core/ModuleReplicator.ts | 9 +- src/modules/core/ModuleTargetFilter.ts | 155 ---------- src/modules/essential/ModuleMigration.ts | 4 +- .../ModuleCheckRemoteSize.ts | 2 +- .../essentialObsidian/ModuleObsidianAPI.ts | 291 ------------------ src/modules/services/ObsidianAPIService.ts | 68 +++- src/modules/services/ObsidianServiceHub.ts | 6 +- .../onLayoutReady/enablei18n.ts | 2 +- src/serviceFeatures/types.ts | 78 ----- test/harness/obsidian-mock.ts | 2 +- tsconfig.json | 4 +- vitest.config.unit.ts | 2 +- 14 files changed, 92 insertions(+), 546 deletions(-) delete mode 100644 src/modules/core/ModuleTargetFilter.ts delete mode 100644 src/modules/essentialObsidian/ModuleObsidianAPI.ts delete mode 100644 src/serviceFeatures/types.ts diff --git a/src/lib b/src/lib index 1c176da..4af350b 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 1c176da469c811cf00a7cd5b16946011b942c8a4 +Subproject commit 4af350bb67a6033018c6a61845bccf9ed887a927 diff --git a/src/main.ts b/src/main.ts index 4f335df..53d62a7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import { Plugin, type App, type PluginManifest } from "./deps"; +import { Notice, Plugin, type App, type PluginManifest } from "./deps"; import { type EntryDoc, type ObsidianLiveSyncSettings, @@ -30,7 +30,6 @@ import type { StorageAccess } from "@lib/interfaces/StorageAccess.ts"; import type { Confirm } from "./lib/src/interfaces/Confirm.ts"; import type { Rebuilder } from "@lib/interfaces/DatabaseRebuilder.ts"; import type { DatabaseFileAccess } from "@lib/interfaces/DatabaseFileAccess.ts"; -import { ModuleObsidianAPI } from "./modules/essentialObsidian/ModuleObsidianAPI.ts"; import { ModuleObsidianEvents } from "./modules/essentialObsidian/ModuleObsidianEvents.ts"; import { AbstractModule } from "./modules/AbstractModule.ts"; import { ModuleObsidianSettingDialogue } from "./modules/features/ModuleObsidianSettingTab.ts"; @@ -41,7 +40,6 @@ import { ModuleInitializerFile } from "./modules/essential/ModuleInitializerFile import { ModuleReplicator } from "./modules/core/ModuleReplicator.ts"; import { ModuleReplicatorCouchDB } from "./modules/core/ModuleReplicatorCouchDB.ts"; import { ModuleReplicatorMinIO } from "./modules/core/ModuleReplicatorMinIO.ts"; -import { ModuleTargetFilter } from "./modules/core/ModuleTargetFilter.ts"; import { ModulePeriodicProcess } from "./modules/core/ModulePeriodicProcess.ts"; import { ModuleConflictChecker } from "./modules/coreFeatures/ModuleConflictChecker.ts"; import { ModuleResolvingMismatchedTweaks } from "./modules/coreFeatures/ModuleResolveMismatchedTweaks.ts"; @@ -64,6 +62,8 @@ import { FileAccessObsidian } from "./serviceModules/FileAccessObsidian.ts"; import { StorageEventManagerObsidian } from "./managers/StorageEventManagerObsidian.ts"; import { onLayoutReadyFeatures } from "./serviceFeatures/onLayoutReady.ts"; import type { ServiceModules } from "./types.ts"; +import { useTargetFilters } from "@lib/serviceFeatures/targetFilter.ts"; +import { setNoticeClass } from "@lib/mock_and_interop/wrapper.ts"; export default class ObsidianLiveSyncPlugin extends Plugin @@ -163,10 +163,8 @@ export default class ObsidianLiveSyncPlugin this._registerModule(new ModuleReplicatorCouchDB(this)); this._registerModule(new ModuleReplicator(this)); this._registerModule(new ModuleConflictResolver(this)); - this._registerModule(new ModuleTargetFilter(this)); this._registerModule(new ModulePeriodicProcess(this)); this._registerModule(new ModuleInitializerFile(this)); - this._registerModule(new ModuleObsidianAPI(this, this)); this._registerModule(new ModuleObsidianEvents(this, this)); this._registerModule(new ModuleResolvingMismatchedTweaks(this)); this._registerModule(new ModuleObsidianSettingsAsMarkdown(this)); @@ -421,10 +419,15 @@ export default class ObsidianLiveSyncPlugin const curriedFeature = () => feature(this); this.services.appLifecycle.onLayoutReady.addHandler(curriedFeature); } + // enable target filter feature. + useTargetFilters(this); } constructor(app: App, manifest: PluginManifest) { super(app, manifest); + // Maybe no more need to setNoticeClass, but for safety, set it in the constructor of the main plugin class. + // TODO: remove this. + setNoticeClass(Notice); this.initialiseServices(); this.registerModules(); this.registerAddOns(); diff --git a/src/modules/core/ModuleReplicator.ts b/src/modules/core/ModuleReplicator.ts index e059b72..0150fbe 100644 --- a/src/modules/core/ModuleReplicator.ts +++ b/src/modules/core/ModuleReplicator.ts @@ -14,17 +14,16 @@ import type { LiveSyncCore } from "../../main"; import { ReplicateResultProcessor } from "./ReplicateResultProcessor"; import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager"; import { clearHandlers } from "@lib/replication/SyncParamsHandler"; -import type { NecessaryServices } from "@/serviceFeatures/types"; +import type { NecessaryServices } from "@lib/interfaces/ServiceModule"; import { MARK_LOG_NETWORK_ERROR } from "@lib/services/lib/logUtils"; function isOnlineAndCanReplicate( errorManager: UnresolvedErrorManager, - host: NecessaryServices<"database", any>, + host: NecessaryServices<"API", any>, showMessage: boolean ): Promise { const errorMessage = "Network is offline"; - const manager = host.services.database.managers.networkManager; - if (!manager.isOnline) { + if (!host.services.API.isOnline) { errorManager.showError(errorMessage, showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO); return Promise.resolve(false); } @@ -270,7 +269,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid // --> These handlers can be separated. const isOnlineAndCanReplicateWithHost = isOnlineAndCanReplicate.bind(null, this._unresolvedErrorManager, { services: { - database: services.database, + API: services.API, }, serviceModules: {}, }); diff --git a/src/modules/core/ModuleTargetFilter.ts b/src/modules/core/ModuleTargetFilter.ts deleted file mode 100644 index f4db428..0000000 --- a/src/modules/core/ModuleTargetFilter.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { getStoragePathFromUXFileInfo } from "../../common/utils"; -import { LOG_LEVEL_DEBUG, LOG_LEVEL_VERBOSE, type UXFileInfoStub } from "../../lib/src/common/types"; -import { isAcceptedAll } from "../../lib/src/string_and_binary/path"; -import { AbstractModule } from "../AbstractModule"; -import type { LiveSyncCore } from "../../main"; -import { Computed } from "octagonal-wheels/dataobject/Computed"; -export class ModuleTargetFilter extends AbstractModule { - ignoreFiles: string[] = []; - private refreshSettings() { - this.ignoreFiles = this.settings.ignoreFiles.split(",").map((e) => e.trim()); - return Promise.resolve(true); - } - - private _everyOnload(): Promise { - void this.refreshSettings(); - return Promise.resolve(true); - } - - _markFileListPossiblyChanged(): void { - this.totalFileEventCount++; - } - - fileCountMap = new Computed({ - evaluation: (fileEventCount: number) => { - const vaultFiles = this.core.storageAccess.getFileNames().sort(); - const fileCountMap: Record = {}; - for (const file of vaultFiles) { - const lc = file.toLowerCase(); - if (!fileCountMap[lc]) { - fileCountMap[lc] = 1; - } else { - fileCountMap[lc]++; - } - } - return fileCountMap; - }, - requiresUpdate: (args, previousArgs, previousResult) => { - if (!previousResult) return true; - if (previousResult instanceof Error) return true; - if (!previousArgs) return true; - if (args[0] === previousArgs[0]) { - return false; - } - return true; - }, - }); - - totalFileEventCount = 0; - - private async _isTargetAcceptedByFileNameDuplication(file: string | UXFileInfoStub) { - await this.fileCountMap.updateValue(this.totalFileEventCount); - const fileCountMap = this.fileCountMap.value; - if (!fileCountMap) { - this._log("File count map is not ready yet."); - return false; - } - - const filepath = getStoragePathFromUXFileInfo(file); - const lc = filepath.toLowerCase(); - if (this.services.vault.shouldCheckCaseInsensitively()) { - if (lc in fileCountMap && fileCountMap[lc] > 1) { - this._log("File is duplicated (case-insensitive): " + filepath); - return false; - } - } - this._log("File is not duplicated: " + filepath, LOG_LEVEL_DEBUG); - return true; - } - - private ignoreFileCacheMap = new Map(); - - private invalidateIgnoreFileCache(path: string) { - // This erases `/path/to/.ignorefile` from cache, therefore, next access will reload it. - // When detecting edited the ignore file, this method should be called. - // Do not check whether it exists in cache or not; just delete it. - const key = path.toLowerCase(); - this.ignoreFileCacheMap.delete(key); - } - private async getIgnoreFile(path: string): Promise { - const key = path.toLowerCase(); - const cached = this.ignoreFileCacheMap.get(key); - if (cached !== undefined) { - // if cached is not undefined, cache hit (neither exists or not exists, string[] or false). - return cached; - } - try { - // load the ignore file - if (!(await this.core.storageAccess.isExistsIncludeHidden(path))) { - // file does not exist, cache as not exists - this.ignoreFileCacheMap.set(key, false); - return false; - } - const file = await this.core.storageAccess.readHiddenFileText(path); - const gitignore = file - .split(/\r?\n/g) - .map((e) => e.replace(/\r$/, "")) - .map((e) => e.trim()); - this.ignoreFileCacheMap.set(key, gitignore); - this._log(`[ignore] Ignore file loaded: ${path}`, LOG_LEVEL_VERBOSE); - return gitignore; - } catch (ex) { - // Failed to read the ignore file, delete cache. - this._log(`[ignore] Failed to read ignore file ${path}`); - this._log(ex, LOG_LEVEL_VERBOSE); - this.ignoreFileCacheMap.set(key, undefined); - return false; - } - } - - private async _isTargetAcceptedByLocalDB(file: string | UXFileInfoStub) { - const filepath = getStoragePathFromUXFileInfo(file); - if (!this.localDatabase?.isTargetFile(filepath)) { - this._log("File is not target by local DB: " + filepath); - return false; - } - this._log("File is target by local DB: " + filepath, LOG_LEVEL_DEBUG); - return await Promise.resolve(true); - } - - private async _isTargetAcceptedFinally(file: string | UXFileInfoStub) { - this._log("File is target finally: " + getStoragePathFromUXFileInfo(file), LOG_LEVEL_DEBUG); - return await Promise.resolve(true); - } - - private async _isTargetAcceptedByIgnoreFiles(file: string | UXFileInfoStub): Promise { - if (!this.settings.useIgnoreFiles) { - return true; - } - const filepath = getStoragePathFromUXFileInfo(file); - this.invalidateIgnoreFileCache(filepath); - this._log("Checking ignore files for: " + filepath, LOG_LEVEL_DEBUG); - if (!(await isAcceptedAll(filepath, this.ignoreFiles, (filename) => this.getIgnoreFile(filename)))) { - this._log("File is ignored by ignore files: " + filepath); - return false; - } - this._log("File is not ignored by ignore files: " + filepath, LOG_LEVEL_DEBUG); - return true; - } - - private async _isTargetIgnoredByIgnoreFiles(file: string | UXFileInfoStub) { - const result = await this._isTargetAcceptedByIgnoreFiles(file); - return !result; - } - - override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { - services.vault.markFileListPossiblyChanged.setHandler(this._markFileListPossiblyChanged.bind(this)); - services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this)); - services.vault.isIgnoredByIgnoreFile.setHandler(this._isTargetIgnoredByIgnoreFiles.bind(this)); - services.vault.isTargetFile.addHandler(this._isTargetAcceptedByFileNameDuplication.bind(this), 10); - services.vault.isTargetFile.addHandler(this._isTargetAcceptedByIgnoreFiles.bind(this), 20); - services.vault.isTargetFile.addHandler(this._isTargetAcceptedByLocalDB.bind(this), 30); - services.vault.isTargetFile.addHandler(this._isTargetAcceptedFinally.bind(this), 100); - services.setting.onSettingRealised.addHandler(this.refreshSettings.bind(this)); - } -} diff --git a/src/modules/essential/ModuleMigration.ts b/src/modules/essential/ModuleMigration.ts index 8ff2227..edd172c 100644 --- a/src/modules/essential/ModuleMigration.ts +++ b/src/modules/essential/ModuleMigration.ts @@ -262,9 +262,7 @@ export class ModuleMigration extends AbstractModule { // Check local database for compromised chunks const localCompromised = await countCompromisedChunks(this.localDatabase.localDatabase); const remote = this.services.replicator.getActiveReplicator(); - const remoteCompromised = this.core.managers.networkManager.isOnline - ? await remote?.countCompromisedChunks() - : 0; + const remoteCompromised = this.services.API.isOnline ? await remote?.countCompromisedChunks() : 0; if (localCompromised === false) { Logger(`Failed to count compromised chunks in local database`, LOG_LEVEL_NOTICE); return false; diff --git a/src/modules/essentialObsidian/ModuleCheckRemoteSize.ts b/src/modules/essentialObsidian/ModuleCheckRemoteSize.ts index 3dedf56..b27c286 100644 --- a/src/modules/essentialObsidian/ModuleCheckRemoteSize.ts +++ b/src/modules/essentialObsidian/ModuleCheckRemoteSize.ts @@ -12,7 +12,7 @@ export class ModuleCheckRemoteSize extends AbstractModule { } private async _allScanStat(): Promise { - if (this.core.managers.networkManager.isOnline === false) { + if (this.services.API.isOnline === false) { this._log("Network is offline, skipping remote size check.", LOG_LEVEL_INFO); return true; } diff --git a/src/modules/essentialObsidian/ModuleObsidianAPI.ts b/src/modules/essentialObsidian/ModuleObsidianAPI.ts deleted file mode 100644 index 49e81a1..0000000 --- a/src/modules/essentialObsidian/ModuleObsidianAPI.ts +++ /dev/null @@ -1,291 +0,0 @@ -import { AbstractObsidianModule } from "../AbstractObsidianModule.ts"; -import { - LEVEL_INFO, - LEVEL_NOTICE, - LOG_LEVEL_DEBUG, - LOG_LEVEL_NOTICE, - LOG_LEVEL_VERBOSE, - type LOG_LEVEL, -} from "octagonal-wheels/common/logger"; -import { Notice, requestUrl, type RequestUrlParam, type RequestUrlResponse } from "../../deps.ts"; -import { type CouchDBCredentials, type EntryDoc } from "../../lib/src/common/types.ts"; -import { isCloudantURI, isValidRemoteCouchDBURI } from "../../lib/src/pouchdb/utils_couchdb.ts"; -import { replicationFilter } from "@lib/pouchdb/compress.ts"; -import { disableEncryption } from "@lib/pouchdb/encryption.ts"; -import { enableEncryption } from "@lib/pouchdb/encryption.ts"; -import { setNoticeClass } from "../../lib/src/mock_and_interop/wrapper.ts"; -import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.ts"; -import { AuthorizationHeaderGenerator } from "../../lib/src/replication/httplib.ts"; -import type { LiveSyncCore } from "../../main.ts"; -import { EVENT_ON_UNRESOLVED_ERROR, eventHub } from "../../common/events.ts"; -import { MARK_LOG_NETWORK_ERROR } from "@lib/services/lib/logUtils.ts"; - -setNoticeClass(Notice); - -async function fetchByAPI(request: RequestUrlParam, errorAsResult = false): Promise { - const ret = await requestUrl({ ...request, throw: !errorAsResult }); - return ret; -} -export class ModuleObsidianAPI extends AbstractObsidianModule { - _authHeader = new AuthorizationHeaderGenerator(); - _previousErrors = new Set(); - - showError(msg: string, max_log_level: LOG_LEVEL = LEVEL_NOTICE) { - const level = this._previousErrors.has(msg) ? LEVEL_INFO : max_log_level; - this._log(msg, level); - if (!this._previousErrors.has(msg)) { - this._previousErrors.add(msg); - eventHub.emitEvent(EVENT_ON_UNRESOLVED_ERROR); - } - } - clearErrors() { - this._previousErrors.clear(); - eventHub.emitEvent(EVENT_ON_UNRESOLVED_ERROR); - } - last_successful_post = false; - - _getLastPostFailedBySize(): boolean { - return !this.last_successful_post; - } - - async __fetchByAPI(url: string, authHeader: string, opts?: RequestInit): Promise { - const body = opts?.body as string; - - const optHeaders = {} as Record; - if (opts && "headers" in opts) { - if (opts.headers instanceof Headers) { - // For Compatibility, mostly headers.entries() is supported, but not all environments. - opts.headers.forEach((value, key) => { - optHeaders[key] = value; - }); - } else { - for (const [key, value] of Object.entries(opts.headers as Record)) { - optHeaders[key] = value; - } - } - } - const transformedHeaders = { ...optHeaders }; - if (authHeader != "") transformedHeaders["authorization"] = authHeader; - delete transformedHeaders["host"]; - delete transformedHeaders["Host"]; - delete transformedHeaders["content-length"]; - delete transformedHeaders["Content-Length"]; - const requestParam: RequestUrlParam = { - url, - method: opts?.method, - body: body, - headers: transformedHeaders, - contentType: - transformedHeaders?.["content-type"] ?? transformedHeaders?.["Content-Type"] ?? "application/json", - }; - const r = await fetchByAPI(requestParam, true); - return new Response(r.arrayBuffer, { - headers: r.headers, - status: r.status, - statusText: `${r.status}`, - }); - } - - async fetchByAPI( - url: string, - localURL: string, - method: string, - authHeader: string, - opts?: RequestInit - ): Promise { - const body = opts?.body as string; - const size = body ? ` (${body.length})` : ""; - try { - const r = await this.__fetchByAPI(url, authHeader, opts); - this.services.API.requestCount.value = this.services.API.requestCount.value + 1; - if (method == "POST" || method == "PUT") { - this.last_successful_post = r.status - (r.status % 100) == 200; - } else { - this.last_successful_post = true; - } - this._log(`HTTP:${method}${size} to:${localURL} -> ${r.status}`, LOG_LEVEL_DEBUG); - return r; - } catch (ex) { - this._log(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL_VERBOSE); - // limit only in bulk_docs. - if (url.toString().indexOf("_bulk_docs") !== -1) { - this.last_successful_post = false; - } - this._log(ex); - throw ex; - } finally { - this.services.API.responseCount.value = this.services.API.responseCount.value + 1; - } - } - - async _connectRemoteCouchDB( - uri: string, - auth: CouchDBCredentials, - disableRequestURI: boolean, - passphrase: string | false, - useDynamicIterationCount: boolean, - performSetup: boolean, - skipInfo: boolean, - compression: boolean, - customHeaders: Record, - useRequestAPI: boolean, - getPBKDF2Salt: () => Promise> - ): Promise; info: PouchDB.Core.DatabaseInfo }> { - if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid"; - if (uri.toLowerCase() != uri) return "Remote URI and database name could not contain capital letters."; - if (uri.indexOf(" ") !== -1) return "Remote URI and database name could not contain spaces."; - if (!this.core.managers.networkManager.isOnline) { - return "Network is offline"; - } - // let authHeader = await this._authHeader.getAuthorizationHeader(auth); - const conf: PouchDB.HttpAdapter.HttpAdapterConfiguration = { - adapter: "http", - auth: "username" in auth ? auth : undefined, - skip_setup: !performSetup, - fetch: async (url: string | Request, opts?: RequestInit) => { - const authHeader = await this._authHeader.getAuthorizationHeader(auth); - let size = ""; - const localURL = url.toString().substring(uri.length); - const method = opts?.method ?? "GET"; - if (opts?.body) { - const opts_length = opts.body.toString().length; - if (opts_length > 1000 * 1000 * 10) { - // over 10MB - if (isCloudantURI(uri)) { - this.last_successful_post = false; - this._log("This request should fail on IBM Cloudant.", LOG_LEVEL_VERBOSE); - throw new Error("This request should fail on IBM Cloudant."); - } - } - size = ` (${opts_length})`; - } - try { - const headers = new Headers(opts?.headers); - if (customHeaders) { - for (const [key, value] of Object.entries(customHeaders)) { - if (key && value) { - headers.append(key, value); - } - } - } - if (!("username" in auth)) { - headers.append("authorization", authHeader); - } - try { - this.services.API.requestCount.value = this.services.API.requestCount.value + 1; - const response: Response = await (useRequestAPI - ? this.__fetchByAPI(url.toString(), authHeader, { ...opts, headers }) - : fetch(url, { ...opts, headers })); - if (method == "POST" || method == "PUT") { - this.last_successful_post = response.ok; - } else { - this.last_successful_post = true; - } - this._log(`HTTP:${method}${size} to:${localURL} -> ${response.status}`, LOG_LEVEL_DEBUG); - if (Math.floor(response.status / 100) !== 2) { - if (response.status == 404) { - if (method === "GET" && localURL.indexOf("/_local/") === -1) { - this._log( - `Just checkpoint or some server information has been missing. The 404 error shown above is not an error.`, - LOG_LEVEL_VERBOSE - ); - } - } else { - const r = response.clone(); - this._log( - `The request may have failed. The reason sent by the server: ${r.status}: ${r.statusText}`, - LOG_LEVEL_NOTICE - ); - try { - const result = await r.text(); - this._log(result, LOG_LEVEL_VERBOSE); - } catch (_) { - this._log("Cloud not fetch response body", LOG_LEVEL_VERBOSE); - this._log(_, LOG_LEVEL_VERBOSE); - } - } - } - this.clearErrors(); - return response; - } catch (ex) { - if (ex instanceof TypeError) { - if (useRequestAPI) { - this._log("Failed to request by API."); - throw ex; - } - this._log( - "Failed to fetch by native fetch API. Trying to fetch by API to get more information." - ); - const resp2 = await this.fetchByAPI(url.toString(), localURL, method, authHeader, { - ...opts, - headers, - }); - if (resp2.status / 100 == 2) { - this.showError( - "The request was successful by API. But the native fetch API failed! Please check CORS settings on the remote database!. While this condition, you cannot enable LiveSync", - LOG_LEVEL_NOTICE - ); - return resp2; - } - const r2 = resp2.clone(); - const msg = await r2.text(); - this.showError(`Failed to fetch by API. ${resp2.status}: ${msg}`, LOG_LEVEL_NOTICE); - return resp2; - } - throw ex; - } - } catch (ex: any) { - this._log(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL_VERBOSE); - const msg = ex instanceof Error ? `${ex?.name}:${ex?.message}` : ex?.toString(); - this.showError(`${MARK_LOG_NETWORK_ERROR}Network Error: Failed to fetch: ${msg}`); // Do not show notice, due to throwing below - this._log(ex, LOG_LEVEL_VERBOSE); - // limit only in bulk_docs. - if (url.toString().indexOf("_bulk_docs") !== -1) { - this.last_successful_post = false; - } - this._log(ex); - throw ex; - } finally { - this.services.API.responseCount.value = this.services.API.responseCount.value + 1; - } - - // return await fetch(url, opts); - }, - }; - - const db: PouchDB.Database = new PouchDB(uri, conf); - replicationFilter(db, compression); - disableEncryption(); - if (passphrase !== "false" && typeof passphrase === "string") { - enableEncryption( - db, - passphrase, - useDynamicIterationCount, - false, - getPBKDF2Salt, - this.settings.E2EEAlgorithm - ); - } - if (skipInfo) { - return { db: db, info: { db_name: "", doc_count: 0, update_seq: "" } }; - } - try { - const info = await db.info(); - return { db: db, info: info }; - } catch (ex: any) { - const msg = `${ex?.name}:${ex?.message}`; - this._log(ex, LOG_LEVEL_VERBOSE); - return msg; - } - } - - private _reportUnresolvedMessages(): Promise<(string | Error)[]> { - return Promise.resolve([...this._previousErrors]); - } - - override onBindFunction(core: LiveSyncCore, services: typeof core.services) { - services.API.isLastPostFailedDueToPayloadSize.setHandler(this._getLastPostFailedBySize.bind(this)); - services.remote.connect.setHandler(this._connectRemoteCouchDB.bind(this)); - services.appLifecycle.getUnresolvedMessages.addHandler(this._reportUnresolvedMessages.bind(this)); - } -} diff --git a/src/modules/services/ObsidianAPIService.ts b/src/modules/services/ObsidianAPIService.ts index 5e5e580..6ec05c0 100644 --- a/src/modules/services/ObsidianAPIService.ts +++ b/src/modules/services/ObsidianAPIService.ts @@ -4,7 +4,7 @@ import { Platform, type Command, type ViewCreator } from "obsidian"; import { ObsHttpHandler } from "../essentialObsidian/APILib/ObsHttpHandler"; import { ObsidianConfirm } from "./ObsidianConfirm"; import type { Confirm } from "@lib/interfaces/Confirm"; - +import { requestUrl, type RequestUrlParam } from "@/deps"; // All Services will be migrated to be based on Plain Services, not Injectable Services. // This is a migration step. @@ -102,7 +102,73 @@ export class ObsidianAPIService extends InjectableAPIService any): HTMLElement { return this.context.plugin.addRibbonIcon(icon, title, callback); } + registerProtocolHandler(action: string, handler: (params: Record) => any): void { return this.context.plugin.registerObsidianProtocolHandler(action, handler); } + + /** + * In Obsidian, we will use the native `requestUrl` function as the default fetch handler, + * to address unavoidable CORS issues. + */ + override async nativeFetch(req: string | Request, opts?: RequestInit): Promise { + const url = typeof req === "string" ? req : req.url; + let body: string | ArrayBuffer | undefined = undefined; + const method = + typeof opts?.method === "string" + ? opts.method + : req instanceof Request && typeof req.method === "string" + ? req.method + : "GET"; + if (typeof req !== "string") { + if (opts?.body) { + body = typeof opts.body === "string" ? opts.body : await new Response(opts.body).arrayBuffer(); + } else if (req.body) { + body = await new Response(req.body).arrayBuffer(); + } + } else { + body = opts?.body as string; + } + const reqHeaders = new Headers(req instanceof Request ? req.headers : {}); + + const optHeaders = {} as Record; + // Merge headers from the Request object and the options, with options taking precedence + reqHeaders.forEach((value, key) => { + optHeaders[key] = value; + }); + if (opts && "headers" in opts) { + if (opts.headers instanceof Headers) { + // For Compatibility, mostly headers.entries() is supported, but not all environments. + opts.headers.forEach((value, key) => { + optHeaders[key] = value; + }); + } else { + for (const [key, value] of Object.entries(opts.headers as Record)) { + optHeaders[key] = value; + } + } + } + const transformedHeaders = { ...optHeaders }; + // Delete headers that should not be sent by native fetch, + // they are controlled by the browser and may cause CORS preflight failure if sent manually. + delete transformedHeaders["host"]; + delete transformedHeaders["Host"]; + delete transformedHeaders["content-length"]; + delete transformedHeaders["Content-Length"]; + const contentType = + transformedHeaders["content-type"] ?? transformedHeaders["Content-Type"] ?? "application/json"; + const requestParam: RequestUrlParam = { + url, + method: method, + body: body, + headers: transformedHeaders, + contentType: contentType, + }; + const r = await requestUrl({ ...requestParam, throw: false }); + return new Response(r.arrayBuffer, { + headers: r.headers, + status: r.status, + statusText: `${r.status}`, + }); + } } diff --git a/src/modules/services/ObsidianServiceHub.ts b/src/modules/services/ObsidianServiceHub.ts index 9ed3663..99cb133 100644 --- a/src/modules/services/ObsidianServiceHub.ts +++ b/src/modules/services/ObsidianServiceHub.ts @@ -33,7 +33,6 @@ export class ObsidianServiceHub extends InjectableServiceHub = Pick; -export type RequiredServiceModules = Pick; - -export type NecessaryServices = { - services: RequiredServices; - serviceModules: RequiredServiceModules; -}; - -export type ServiceFeatureFunction = ( - host: NecessaryServices -) => TR; -type ServiceFeatureContext = T & { - _log: LogFunction; -}; -export type ServiceFeatureFunctionWithContext = ( - host: NecessaryServices, - context: ServiceFeatureContext -) => TR; - -/** - * Helper function to create a service feature with proper typing. - * @param featureFunction The feature function to be wrapped. - * @returns The same feature function with proper typing. - * @example - * const myFeatureDef = createServiceFeature(({ services: { API }, serviceModules: { storageAccess } }) => { - * // ... - * }); - * const myFeature = myFeatureDef.bind(null, this); // <- `this` may `ObsidianLiveSyncPlugin` or a custom context object - * appLifecycle.onLayoutReady(myFeature); - */ -export function createServiceFeature( - featureFunction: ServiceFeatureFunction -): ServiceFeatureFunction { - return featureFunction; -} - -type ContextFactory = ( - host: NecessaryServices -) => ServiceFeatureContext; - -export function serviceFeature() { - return { - create(featureFunction: ServiceFeatureFunction) { - return featureFunction; - }, - withContext(ContextFactory: ContextFactory) { - return { - create: - (featureFunction: ServiceFeatureFunctionWithContext) => - (host: NecessaryServices, context: ServiceFeatureContext) => - featureFunction(host, ContextFactory(host)), - }; - }, - }; -} diff --git a/test/harness/obsidian-mock.ts b/test/harness/obsidian-mock.ts index b7fde70..a07f902 100644 --- a/test/harness/obsidian-mock.ts +++ b/test/harness/obsidian-mock.ts @@ -481,7 +481,7 @@ export class Plugin { } export class Notice { - private _key:number; + private _key: number; private static _counter = 0; constructor(message: string) { this._key = Notice._counter++; diff --git a/tsconfig.json b/tsconfig.json index 5e8bd6c..49caa41 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,6 +24,6 @@ "@lib/*": ["src/lib/src/*"] } }, - "include": ["**/*.ts", "test/**/*.test.ts"], - "exclude": ["pouchdb-browser-webpack", "utils", "src/apps", "src/**/*.test.ts", "**/_test/**", "src/**/*.test.ts"] + "include": ["**/*.ts", "test/**/*.test.ts", "**/*.unit.spec.ts"], + "exclude": ["pouchdb-browser-webpack", "utils", "src/apps", "src/**/*.test.ts", "**/_test/**"] } diff --git a/vitest.config.unit.ts b/vitest.config.unit.ts index 119cb33..778e46d 100644 --- a/vitest.config.unit.ts +++ b/vitest.config.unit.ts @@ -6,7 +6,7 @@ export default mergeConfig( defineConfig({ test: { name: "unit-tests", - include: ["**/*unit.test.ts"], + include: ["**/*unit.test.ts", "**/*.unit.spec.ts"], exclude: ["test/**"], coverage: { include: ["src/**/*.ts"], From f61a3eb85b2e8aac38680ebab2904e9bd3a1bfc3 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 25 Feb 2026 09:39:55 +0000 Subject: [PATCH 036/339] bump --- manifest.json | 2 +- package-lock.json | 4 +-- package.json | 2 +- updates.md | 14 ++++++++++ updates_old.md | 70 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 88 insertions(+), 4 deletions(-) diff --git a/manifest.json b/manifest.json index 801f131..2264612 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.44", + "version": "0.25.45", "minAppVersion": "0.9.12", "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 0d66536..7b62f7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.44", + "version": "0.25.45", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.44", + "version": "0.25.45", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 1d97e7a..3503011 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.44", + "version": "0.25.45", "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 a10357e..3c98d3b 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,20 @@ 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.45 + +25th February, 2026 + +As a result of recent refactoring, we are able to write tests more easily now! + +### Refactored + +- `ModuleTargetFilter`, which was responsible for checking if a file is a target file, has been ported to a serviceFeature. + - And also tests have been added. The middleware-style-power. +- `ModuleObsidianAPI` has been removed and implemented in `APIService` and `RemoteService`. +- Now `APIService` is responsible for the network-online-status, not `databaseService.managers.networkManager`. + + ## 0.25.44 24th February, 2026 diff --git a/updates_old.md b/updates_old.md index 5c0c3b0..19fe78d 100644 --- a/updates_old.md +++ b/updates_old.md @@ -3,6 +3,76 @@ 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.45 + +25th February, 2026 + +As a result of recent refactoring, we are able to write tests more easily now! + +### Refactored + +- `ModuleTargetFilter`, which was responsible for checking if a file is a target file, has been ported to a serviceFeature. + - And also tests have been added. The middleware-style-power. +- `ModuleObsidianAPI` has been removed and implemented in `APIService` and `RemoteService`. +- Now `APIService` is responsible for the network-online-status, not `databaseService.managers.networkManager`. + +## 0.25.44 + +24th February, 2026 + +This release represents a significant architectural overhaul of the plug-in, focusing on modularity, testability, and stability. While many changes are internal, they pave the way for more robust features and easier maintenance. +However, as this update is very substantial, please do feel free to let me know if you encounter any issues. + +### Fixed + +- Ignore files (e.g., `.ignore`) are now handled efficiently. +- Replication & Database: + - Replication statistics are now correctly reset after switching replicators. +- Fixed `File already exists` for .md files has been merged (PR #802) So thanks @waspeer for the contribution! + +### Improved + +- Now we can configure network-error banners as icons, or hide them completely with the new `Network Warning Style` setting in the `General` pane of the settings dialogue. (#770, PR #804) + - Thanks so much to @A-wry! + +### Refactored + +#### Architectural Overhaul: + +- A major transition from Class-based Modules to a Service/Middleware architecture has begun. + - Many modules (for example, `ModulePouchDB`, `ModuleLocalDatabaseObsidian`, `ModuleKeyValueDB`) have been removed or integrated into specific Services (`database`, `keyValueDB`, etc.). + - Reduced reliance on dynamic binding and inverted dependencies; dependencies are now explicit. + - `ObsidianLiveSyncPlugin` properties (`replicator`, `localDatabase`, `storageAccess`, etc.) have been moved to their respective services for better separation of concerns. + - In this refactoring, the Service will henceforth, as a rule, cease to use setHandler, that is to say, simple lazy binding. + - They will be implemented directly in the service. + - However, not everything will be middlewarised. Modules that maintain state or make decisions based on the results of multiple handlers are permitted. +- Lifecycle: + - Application LifeCycle now starts in `Main` rather than `ServiceHub` or `ObsidianMenuModule`, ensuring smoother startup coordination. + +#### New Services & Utilities: + +- Added a `control` service to orchestrate other services (for example, handling stop/start logic during settings realisation). +- Added `UnresolvedErrorManager` to handle and display unresolved errors in a unified way. +- Added `logUtils` to unify logging injection and formatting. +- `VaultService.isTargetFile` now uses multiple, distinct checkers for better extensibility. + +#### Code Separation: + +- Separated Obsidian-specific logic from base logic for `StorageEventManager` and `FileAccess` modules. +- Moved reactive state values and statistics from the main plug-in instance to the services responsible for them. + +#### Internal Cleanups: + +- Many functions have been renamed for clarity (for example, `_isTargetFileByLocalDB` is now `_isTargetAcceptedByLocalDB`). +- Added `override` keywords to overridden items and removed dynamic binding for clearer code inheritance. +- Moved common functions to the common library. + +#### Dependencies: + +- Bumped dependencies simply to a point where they can be considered problem-free (by human-powered-artefacts-diff). + - Svelte, terser, and more something will be bumped later. They have a significant impact on the diff and paint it totally. + - You may be surprised, but when I bump the library, I am actually checking for any unintended code. + ## 0.25.43-patched-9 a.k.a. 0.25.44-rc1 We are finally ready for release. I think I will go ahead and release it after using it for a few days. From 392f76fd363cfd10731c271a1cf47325e03d238a Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 26 Feb 2026 08:59:54 +0000 Subject: [PATCH 037/339] ### Refactored - `ModuleCheckRemoteSize` has been ported to a serviceFeature, and also tests have been added. - Some unnecessary things have been removed. --- src/common/events.ts | 3 +-- src/lib | 2 +- src/main.ts | 6 ++++-- src/modules/services/ObsidianServiceHub.ts | 1 - updates.md | 6 ++++++ 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/common/events.ts b/src/common/events.ts index 0ef7d04..0372882 100644 --- a/src/common/events.ts +++ b/src/common/events.ts @@ -24,7 +24,7 @@ export const EVENT_REQUEST_RUN_FIX_INCOMPLETE = "request-run-fix-incomplete"; export const EVENT_ANALYSE_DB_USAGE = "analyse-db-usage"; export const EVENT_REQUEST_PERFORM_GC_V3 = "request-perform-gc-v3"; -export const EVENT_REQUEST_CHECK_REMOTE_SIZE = "request-check-remote-size"; +// export const EVENT_REQUEST_CHECK_REMOTE_SIZE = "request-check-remote-size"; // export const EVENT_FILE_CHANGED = "file-changed"; declare global { @@ -44,7 +44,6 @@ declare global { [EVENT_REQUEST_RUN_DOCTOR]: string; [EVENT_REQUEST_RUN_FIX_INCOMPLETE]: undefined; [EVENT_ANALYSE_DB_USAGE]: undefined; - [EVENT_REQUEST_CHECK_REMOTE_SIZE]: undefined; [EVENT_REQUEST_PERFORM_GC_V3]: undefined; } } diff --git a/src/lib b/src/lib index 4af350b..1335a01 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 4af350bb67a6033018c6a61845bccf9ed887a927 +Subproject commit 1335a017441ae561bd098fca286ad337f442ca2d diff --git a/src/main.ts b/src/main.ts index 53d62a7..bca8082 100644 --- a/src/main.ts +++ b/src/main.ts @@ -18,7 +18,7 @@ import type { IObsidianModule } from "./modules/AbstractObsidianModule.ts"; import { ModuleDev } from "./modules/extras/ModuleDev.ts"; import { ModuleMigration } from "./modules/essential/ModuleMigration.ts"; -import { ModuleCheckRemoteSize } from "./modules/essentialObsidian/ModuleCheckRemoteSize.ts"; +// import { ModuleCheckRemoteSize } from "./modules/essentialObsidian/ModuleCheckRemoteSize.ts"; import { ModuleConflictResolver } from "./modules/coreFeatures/ModuleConflictResolver.ts"; import { ModuleInteractiveConflictResolver } from "./modules/features/ModuleInteractiveConflictResolver.ts"; import { ModuleLog } from "./modules/features/ModuleLog.ts"; @@ -64,6 +64,7 @@ import { onLayoutReadyFeatures } from "./serviceFeatures/onLayoutReady.ts"; import type { ServiceModules } from "./types.ts"; import { useTargetFilters } from "@lib/serviceFeatures/targetFilter.ts"; import { setNoticeClass } from "@lib/mock_and_interop/wrapper.ts"; +import { useCheckRemoteSize } from "./lib/src/serviceFeatures/checkRemoteSize.ts"; export default class ObsidianLiveSyncPlugin extends Plugin @@ -177,7 +178,7 @@ export default class ObsidianLiveSyncPlugin this._registerModule(new ModuleRedFlag(this)); this._registerModule(new ModuleInteractiveConflictResolver(this, this)); this._registerModule(new ModuleObsidianGlobalHistory(this, this)); - this._registerModule(new ModuleCheckRemoteSize(this)); + // this._registerModule(new ModuleCheckRemoteSize(this)); // Test and Dev Modules this._registerModule(new ModuleDev(this, this)); this._registerModule(new ModuleReplicateTest(this, this)); @@ -421,6 +422,7 @@ export default class ObsidianLiveSyncPlugin } // enable target filter feature. useTargetFilters(this); + useCheckRemoteSize(this); } constructor(app: App, manifest: PluginManifest) { diff --git a/src/modules/services/ObsidianServiceHub.ts b/src/modules/services/ObsidianServiceHub.ts index 99cb133..7b6c83e 100644 --- a/src/modules/services/ObsidianServiceHub.ts +++ b/src/modules/services/ObsidianServiceHub.ts @@ -77,7 +77,6 @@ export class ObsidianServiceHub extends InjectableServiceHub Date: Thu, 26 Feb 2026 11:30:57 +0000 Subject: [PATCH 038/339] ### Fixed - Unexpected errors no longer occurred when the plug-in was unloaded. - Hidden File Sync now respects selectors. - Registering protocol-handlers now works safely without causing unexpected errors. ### Refactored - LiveSyncManagers has now explicit dependencies. - LiveSyncLocalDB is now responsible for LiveSyncManagers, not accepting the managers as dependencies. - This is to avoid circular dependencies and clarify the ownership of the managers. - ChangeManager has been refactored. This had a potential issue, so something had been fixed, possibly. - Some tests have been ported from Deno's test runner to Vitest to accumulate coverage. --- .../HiddenFileSync/CmdHiddenFileSync.ts | 26 +++++++++++----- src/lib | 2 +- src/main.ts | 7 ----- src/modules/features/ModuleSetupObsidian.ts | 30 ++++++++++++------- src/modules/services/ObsidianServiceHub.ts | 1 + 5 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/features/HiddenFileSync/CmdHiddenFileSync.ts b/src/features/HiddenFileSync/CmdHiddenFileSync.ts index d8c4d60..92baa9c 100644 --- a/src/features/HiddenFileSync/CmdHiddenFileSync.ts +++ b/src/features/HiddenFileSync/CmdHiddenFileSync.ts @@ -92,7 +92,7 @@ export class HiddenFileSync extends LiveSyncCommands { return this.plugin.kvDB; } getConflictedDoc(path: FilePathWithPrefix, rev: string) { - return this.plugin.managers.conflictManager.getConflictedDoc(path, rev); + return this.localDatabase.managers.conflictManager.getConflictedDoc(path, rev); } onunload() { this.periodicInternalFileScanProcessor?.disable(); @@ -244,13 +244,23 @@ export class HiddenFileSync extends LiveSyncCommands { if (this.isThisModuleEnabled()) { //system file const filename = this.getPath(doc); - if (await this.services.vault.isTargetFile(filename)) { - // this.procInternalFile(filename); - await this.processReplicationResult(doc); + const unprefixedPath = stripAllPrefixes(filename); + // No need to check via vaultService + // if (!await this.services.vault.isTargetFile(unprefixedPath)) { + // this._log(`Skipped processing sync file:${unprefixedPath} (Not target)`, LOG_LEVEL_VERBOSE); + // return true; + // } + if (!(await this.isTargetFile(stripAllPrefixes(unprefixedPath)))) { + this._log( + `Skipped processing sync file:${unprefixedPath} (Not Hidden File Sync target)`, + LOG_LEVEL_VERBOSE + ); + // We should return true, we made sure that document is a internalMetadata. return true; - } else { - this._log(`Skipped (Not target:${filename})`, LOG_LEVEL_VERBOSE); - return false; + } + if (!(await this.processReplicationResult(doc))) { + this._log(`Failed to process sync file:${unprefixedPath}`, LOG_LEVEL_NOTICE); + // Do not yield false, this file had been processed. } } return true; @@ -700,7 +710,7 @@ Offline Changed files: ${processFiles.length}`; revFrom._revs_info ?.filter((e) => e.status == "available" && Number(e.rev.split("-")[0]) < conflictedRevNo) .first()?.rev ?? ""; - const result = await this.plugin.managers.conflictManager.mergeObject( + const result = await this.localDatabase.managers.conflictManager.mergeObject( doc.path, commonBase, doc._rev, diff --git a/src/lib b/src/lib index 1335a01..29f2a6a 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 1335a017441ae561bd098fca286ad337f442ca2d +Subproject commit 29f2a6aa4f2cf3451c720811533395bb7aa3a78a diff --git a/src/main.ts b/src/main.ts index bca8082..ba52092 100644 --- a/src/main.ts +++ b/src/main.ts @@ -239,13 +239,6 @@ export default class ObsidianLiveSyncPlugin return this.services.database.localDatabase; } - /** - * @obsolete Use services.database.managers instead. The database managers, including entry manager, revision manager, etc. - */ - get managers() { - return this.services.database.managers; - } - /** * @obsolete Use services.database.localDatabase instead. Get the PouchDB database instance. Note that this is not the same as the local database instance, which is a wrapper around the PouchDB database. * @returns The PouchDB database instance. diff --git a/src/modules/features/ModuleSetupObsidian.ts b/src/modules/features/ModuleSetupObsidian.ts index d09dbf4..2c715d5 100644 --- a/src/modules/features/ModuleSetupObsidian.ts +++ b/src/modules/features/ModuleSetupObsidian.ts @@ -1,4 +1,4 @@ -import { type ObsidianLiveSyncSettings, LOG_LEVEL_NOTICE } from "../../lib/src/common/types.ts"; +import { type ObsidianLiveSyncSettings, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "../../lib/src/common/types.ts"; import { configURIBase } from "../../common/types.ts"; // import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.js"; import { fireAndForget } from "../../lib/src/common/utils.ts"; @@ -25,16 +25,24 @@ export class ModuleSetupObsidian extends AbstractModule { private _setupManager!: SetupManager; private _everyOnload(): Promise { this._setupManager = this.core.getModule(SetupManager); - this.registerObsidianProtocolHandler("setuplivesync", async (conf: any) => { - if (conf.settings) { - await this._setupManager.onUseSetupURI( - UserMode.Unknown, - `${configURIBase}${encodeURIComponent(conf.settings)}` - ); - } else if (conf.settingsQR) { - await this._setupManager.decodeQR(conf.settingsQR); - } - }); + try { + this.registerObsidianProtocolHandler("setuplivesync", async (conf: any) => { + if (conf.settings) { + await this._setupManager.onUseSetupURI( + UserMode.Unknown, + `${configURIBase}${encodeURIComponent(conf.settings)}` + ); + } else if (conf.settingsQR) { + await this._setupManager.decodeQR(conf.settingsQR); + } + }); + } catch (e) { + this._log( + "Failed to register protocol handler. This feature may not work in some environments.", + LOG_LEVEL_NOTICE + ); + this._log(e, LOG_LEVEL_VERBOSE); + } this.addCommand({ id: "livesync-setting-qr", name: "Show settings as a QR code", diff --git a/src/modules/services/ObsidianServiceHub.ts b/src/modules/services/ObsidianServiceHub.ts index 7b6c83e..9a2a85c 100644 --- a/src/modules/services/ObsidianServiceHub.ts +++ b/src/modules/services/ObsidianServiceHub.ts @@ -59,6 +59,7 @@ export class ObsidianServiceHub extends InjectableServiceHub Date: Thu, 26 Feb 2026 11:34:14 +0000 Subject: [PATCH 039/339] bump --- manifest.json | 2 +- package-lock.json | 437 +++++++++++++++++++++++++++------------------- package.json | 2 +- updates.md | 28 ++- 4 files changed, 276 insertions(+), 193 deletions(-) diff --git a/manifest.json b/manifest.json index 2264612..fc3b93c 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.45", + "version": "0.25.46", "minAppVersion": "0.9.12", "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 7b62f7f..02323e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.45", + "version": "0.25.46", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.45", + "version": "0.25.46", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", @@ -3793,9 +3793,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", - "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", "cpu": [ "arm" ], @@ -3807,9 +3807,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", - "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", "cpu": [ "arm64" ], @@ -3821,9 +3821,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", - "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", "cpu": [ "arm64" ], @@ -3835,9 +3835,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", - "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", "cpu": [ "x64" ], @@ -3849,9 +3849,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", - "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", "cpu": [ "arm64" ], @@ -3863,9 +3863,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", - "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", "cpu": [ "x64" ], @@ -3877,9 +3877,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", - "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", "cpu": [ "arm" ], @@ -3891,9 +3891,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", - "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", "cpu": [ "arm" ], @@ -3905,9 +3905,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", - "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", "cpu": [ "arm64" ], @@ -3919,9 +3919,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", - "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", "cpu": [ "arm64" ], @@ -3933,9 +3933,23 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", - "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", "cpu": [ "loong64" ], @@ -3947,9 +3961,23 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", - "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", "cpu": [ "ppc64" ], @@ -3961,9 +3989,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", - "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", "cpu": [ "riscv64" ], @@ -3975,9 +4003,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", - "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", "cpu": [ "riscv64" ], @@ -3989,9 +4017,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", - "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", "cpu": [ "s390x" ], @@ -4003,9 +4031,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", - "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", "cpu": [ "x64" ], @@ -4017,9 +4045,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", - "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", "cpu": [ "x64" ], @@ -4030,10 +4058,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", - "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", "cpu": [ "arm64" ], @@ -4045,9 +4087,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", - "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", "cpu": [ "arm64" ], @@ -4059,9 +4101,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", - "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", "cpu": [ "ia32" ], @@ -4073,9 +4115,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", - "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", "cpu": [ "x64" ], @@ -4087,9 +4129,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", - "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "cpu": [ "x64" ], @@ -13204,9 +13246,9 @@ "license": "MIT" }, "node_modules/rollup": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", - "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "dev": true, "license": "MIT", "dependencies": { @@ -13220,28 +13262,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.52.5", - "@rollup/rollup-android-arm64": "4.52.5", - "@rollup/rollup-darwin-arm64": "4.52.5", - "@rollup/rollup-darwin-x64": "4.52.5", - "@rollup/rollup-freebsd-arm64": "4.52.5", - "@rollup/rollup-freebsd-x64": "4.52.5", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", - "@rollup/rollup-linux-arm-musleabihf": "4.52.5", - "@rollup/rollup-linux-arm64-gnu": "4.52.5", - "@rollup/rollup-linux-arm64-musl": "4.52.5", - "@rollup/rollup-linux-loong64-gnu": "4.52.5", - "@rollup/rollup-linux-ppc64-gnu": "4.52.5", - "@rollup/rollup-linux-riscv64-gnu": "4.52.5", - "@rollup/rollup-linux-riscv64-musl": "4.52.5", - "@rollup/rollup-linux-s390x-gnu": "4.52.5", - "@rollup/rollup-linux-x64-gnu": "4.52.5", - "@rollup/rollup-linux-x64-musl": "4.52.5", - "@rollup/rollup-openharmony-arm64": "4.52.5", - "@rollup/rollup-win32-arm64-msvc": "4.52.5", - "@rollup/rollup-win32-ia32-msvc": "4.52.5", - "@rollup/rollup-win32-x64-gnu": "4.52.5", - "@rollup/rollup-win32-x64-msvc": "4.52.5", + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" } }, @@ -19063,156 +19108,177 @@ } }, "@rollup/rollup-android-arm-eabi": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", - "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", "dev": true, "optional": true }, "@rollup/rollup-android-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", - "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", "dev": true, "optional": true }, "@rollup/rollup-darwin-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", - "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", "dev": true, "optional": true }, "@rollup/rollup-darwin-x64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", - "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", "dev": true, "optional": true }, "@rollup/rollup-freebsd-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", - "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", "dev": true, "optional": true }, "@rollup/rollup-freebsd-x64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", - "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", - "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", - "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", - "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", - "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", "dev": true, "optional": true }, "@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", - "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", "dev": true, "optional": true }, "@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", - "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", "dev": true, "optional": true }, "@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", - "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", "dev": true, "optional": true }, "@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", - "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", "dev": true, "optional": true }, "@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", - "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", - "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", - "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", "dev": true, "optional": true }, "@rollup/rollup-openharmony-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", - "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", "dev": true, "optional": true }, "@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", - "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", "dev": true, "optional": true }, "@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", - "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", "dev": true, "optional": true }, "@rollup/rollup-win32-x64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", - "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", "dev": true, "optional": true }, "@rollup/rollup-win32-x64-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", - "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "dev": true, "optional": true }, @@ -25613,33 +25679,36 @@ "dev": true }, "rollup": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", - "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "dev": true, "requires": { - "@rollup/rollup-android-arm-eabi": "4.52.5", - "@rollup/rollup-android-arm64": "4.52.5", - "@rollup/rollup-darwin-arm64": "4.52.5", - "@rollup/rollup-darwin-x64": "4.52.5", - "@rollup/rollup-freebsd-arm64": "4.52.5", - "@rollup/rollup-freebsd-x64": "4.52.5", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", - "@rollup/rollup-linux-arm-musleabihf": "4.52.5", - "@rollup/rollup-linux-arm64-gnu": "4.52.5", - "@rollup/rollup-linux-arm64-musl": "4.52.5", - "@rollup/rollup-linux-loong64-gnu": "4.52.5", - "@rollup/rollup-linux-ppc64-gnu": "4.52.5", - "@rollup/rollup-linux-riscv64-gnu": "4.52.5", - "@rollup/rollup-linux-riscv64-musl": "4.52.5", - "@rollup/rollup-linux-s390x-gnu": "4.52.5", - "@rollup/rollup-linux-x64-gnu": "4.52.5", - "@rollup/rollup-linux-x64-musl": "4.52.5", - "@rollup/rollup-openharmony-arm64": "4.52.5", - "@rollup/rollup-win32-arm64-msvc": "4.52.5", - "@rollup/rollup-win32-ia32-msvc": "4.52.5", - "@rollup/rollup-win32-x64-gnu": "4.52.5", - "@rollup/rollup-win32-x64-msvc": "4.52.5", + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", "@types/estree": "1.0.8", "fsevents": "~2.3.2" } diff --git a/package.json b/package.json index 3503011..48f4cca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.45", + "version": "0.25.46", "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 7d59bbd..fb58883 100644 --- a/updates.md +++ b/updates.md @@ -3,11 +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. -## -- in progress -- +## 0.25.46 + +26th February, 2026 + +### Fixed + +- Unexpected errors no longer occurred when the plug-in was unloaded. +- Hidden File Sync now respects selectors. +- Registering protocol-handlers now works safely without causing unexpected errors. ### Refactored -- `ModuleCheckRemoteSize` has been ported to a serviceFeature, and also tests have been added. + +- `ModuleCheckRemoteSize` has been ported to a serviceFeature, and tests have also been added. - Some unnecessary things have been removed. +- LiveSyncManagers has now explicit dependencies. +- LiveSyncLocalDB is now responsible for LiveSyncManagers, not accepting the managers as dependencies. + - This is to avoid circular dependencies and clarify the ownership of the managers. +- ChangeManager has been refactored. This had a potential issue, so something had been fixed, possibly. +- Some tests have been ported from Deno's test runner to Vitest to accumulate coverage. + ## 0.25.45 @@ -18,11 +33,10 @@ As a result of recent refactoring, we are able to write tests more easily now! ### Refactored - `ModuleTargetFilter`, which was responsible for checking if a file is a target file, has been ported to a serviceFeature. - - And also tests have been added. The middleware-style-power. + - And also tests have been added. The middleware-style-power. - `ModuleObsidianAPI` has been removed and implemented in `APIService` and `RemoteService`. - Now `APIService` is responsible for the network-online-status, not `databaseService.managers.networkManager`. - ## 0.25.44 24th February, 2026 @@ -40,7 +54,7 @@ However, as this update is very substantial, please do feel free to let me know ### Improved - Now we can configure network-error banners as icons, or hide them completely with the new `Network Warning Style` setting in the `General` pane of the settings dialogue. (#770, PR #804) - - Thanks so much to @A-wry! + - Thanks so much to @A-wry! ### Refactored @@ -77,8 +91,8 @@ However, as this update is very substantial, please do feel free to let me know #### Dependencies: - Bumped dependencies simply to a point where they can be considered problem-free (by human-powered-artefacts-diff). - - Svelte, terser, and more something will be bumped later. They have a significant impact on the diff and paint it totally. - - You may be surprised, but when I bump the library, I am actually checking for any unintended code. + - Svelte, terser, and more something will be bumped later. They have a significant impact on the diff and paint it totally. + - You may be surprised, but when I bump the library, I am actually checking for any unintended code. ## 0.25.43 From e08fbbd2237b953c5097c623352099f312c0d8c1 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Fri, 27 Feb 2026 11:00:30 +0000 Subject: [PATCH 040/339] ### Fixed and refactored - Fixed the inexplicable behaviour when retrieving chunks from the network. - Chunk manager has been layered to responsible its own areas and duties. e.g., `DatabaseWriteLayer`, `DatabaseReadLayer`, `NetworkLayer`, `CacheLayer`,and `ArrivalWaitLayer`. - All layers have test now! - `LayeredChunkManager` has been implemented to manage these layers. Also tested. - `EntryManager` has been mostly rewritten, and also tested. - Now we can configure `Never warn` for remote storage size notification, again. --- devs.md | 8 ++++++-- src/lib | 2 +- ...Size.ts => ModuleCheckRemoteSize_obsolete.ts} | 0 vitest.config.common.ts | 1 - vitest.config.ts | 6 ++++++ vitest.config.unit.ts | 16 +++++++++++++++- 6 files changed, 28 insertions(+), 5 deletions(-) rename src/modules/essentialObsidian/{ModuleCheckRemoteSize.ts => ModuleCheckRemoteSize_obsolete.ts} (100%) diff --git a/devs.md b/devs.md index bb5d584..9bd24fc 100644 --- a/devs.md +++ b/devs.md @@ -52,6 +52,7 @@ Hence, the new feature should be implemented as follows: ### Commands ```bash +npm run test:unit # Run unit tests with vitest (or `npm run test:unit:coverage` for coverage) npm run check # TypeScript and svelte type checking npm run dev # Development build with auto-rebuild (uses .env for test vault paths) npm run build # Production build @@ -67,8 +68,11 @@ npm test # Run vitest tests (requires Docker services) ### Testing Infrastructure -- **Deno Tests**: Unit tests for platform-independent code (e.g., `HashManager.test.ts`) -- **Vitest** (`vitest.config.ts`): E2E test by Browser-based-harness using Playwright +- ~~**Deno Tests**: Unit tests for platform-independent code (e.g., `HashManager.test.ts`)~~ + - This is now obsolete, migrated to vitest. +- **Vitest** (`vitest.config.ts`): E2E test by Browser-based-harness using Playwright, unit tests. + - Unit tests should be `*.unit.spec.ts` and placed alongside the implementation file (e.g., `ChunkFetcher.unit.spec.ts`). + - **Docker Services**: Tests require CouchDB, MinIO (S3), and P2P services: ```bash npm run test:docker-all:start # Start all test services diff --git a/src/lib b/src/lib index 29f2a6a..2ed1925 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 29f2a6aa4f2cf3451c720811533395bb7aa3a78a +Subproject commit 2ed1925ca7e3d7d9d0b497b74a108bbb881b92b7 diff --git a/src/modules/essentialObsidian/ModuleCheckRemoteSize.ts b/src/modules/essentialObsidian/ModuleCheckRemoteSize_obsolete.ts similarity index 100% rename from src/modules/essentialObsidian/ModuleCheckRemoteSize.ts rename to src/modules/essentialObsidian/ModuleCheckRemoteSize_obsolete.ts diff --git a/vitest.config.common.ts b/vitest.config.common.ts index 4f8dd4a..44c0747 100644 --- a/vitest.config.common.ts +++ b/vitest.config.common.ts @@ -89,7 +89,6 @@ export default defineConfig({ ], resolve: { alias: { - obsidian: path.resolve(__dirname, "./test/harness/obsidian-mock.ts"), "@": path.resolve(__dirname, "./src"), "@lib": path.resolve(__dirname, "./src/lib/src"), src: path.resolve(__dirname, "./src"), diff --git a/vitest.config.ts b/vitest.config.ts index 94e8352..8818ee8 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,6 +1,7 @@ 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 { grantClipboardPermissions, openWebPeer, closeWebPeer, acceptWebPeer } from "./test/lib/commands"; const defEnv = dotenv.config({ path: ".env" }).parsed; @@ -12,6 +13,11 @@ const headless = !debuggerEnabled && !enableUI; export default mergeConfig( viteConfig, defineConfig({ + resolve: { + alias: { + obsidian: path.resolve(__dirname, "./test/harness/obsidian-mock.ts"), + }, + }, test: { env: env, testTimeout: 40000, diff --git a/vitest.config.unit.ts b/vitest.config.unit.ts index 778e46d..6fbbaa5 100644 --- a/vitest.config.unit.ts +++ b/vitest.config.unit.ts @@ -1,16 +1,30 @@ import { defineConfig, mergeConfig } from "vitest/config"; import viteConfig from "./vitest.config.common"; +const importOnlyFiles = ["**/encryption/encryptHKDF.ts"]; export default mergeConfig( viteConfig, defineConfig({ + resolve: { + alias: { + obsidian: "", // prevent accidental imports of obsidian types in unit tests, + }, + }, test: { name: "unit-tests", include: ["**/*unit.test.ts", "**/*.unit.spec.ts"], exclude: ["test/**"], coverage: { include: ["src/**/*.ts"], - exclude: ["**/*.test.ts", "src/lib/**/*.test.ts", "**/_*", "src/lib/apps", "src/lib/src/cli"], + exclude: [ + "**/*.test.ts", + "src/lib/**/*.test.ts", + "**/_*", + "src/lib/apps", + "src/lib/src/cli", + "**/*_obsolete.ts", + ...importOnlyFiles, + ], provider: "v8", reporter: ["text", "json", "html"], }, From 28e06a21e4f1bac2e83dbb861af5bba21952edb7 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Fri, 27 Feb 2026 11:15:23 +0000 Subject: [PATCH 041/339] bump --- manifest.json | 2 +- package-lock.json | 2206 +++++++++++++++++++++------------------------ package.json | 2 +- updates.md | 22 +- 4 files changed, 1045 insertions(+), 1187 deletions(-) diff --git a/manifest.json b/manifest.json index fc3b93c..4649f12 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.46", + "version": "0.25.47", "minAppVersion": "0.9.12", "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 02323e2..713ec8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.46", + "version": "0.25.47", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.46", + "version": "0.25.47", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", @@ -357,89 +357,24 @@ "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.982.0.tgz", - "integrity": "sha512-qJrIiivmvujdGqJ0ldSUvhN3k3N7GtPesoOI1BSt0fNXovVnMz4C/JmnkhZihU7hJhDvxJaBROLYTU+lpild4w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/middleware-host-header": "^3.972.3", - "@aws-sdk/middleware-logger": "^3.972.3", - "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.6", - "@aws-sdk/region-config-resolver": "^3.972.3", - "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.982.0", - "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.4", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.12", - "@smithy/middleware-retry": "^4.4.29", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.28", - "@smithy/util-defaults-mode-node": "^4.2.31", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz", - "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.1", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/@aws-sdk/core": { - "version": "3.973.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.6.tgz", - "integrity": "sha512-pz4ZOw3BLG0NdF25HoB9ymSYyPbMiIjwQJ2aROXRhAzt+b+EOxStfFv8s5iZyP6Kiw7aYhyWxj5G3NhmkoOTKw==", + "version": "3.973.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.15.tgz", + "integrity": "sha512-AlC0oQ1/mdJ8vCIqu524j5RB7M8i8E24bbkZmya1CuiQxkY7SdIZAyw7NDNMGaNINQFq/8oGRMX0HeOfCVsl/A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.1", - "@aws-sdk/xml-builder": "^3.972.4", - "@smithy/core": "^3.22.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", + "@aws-sdk/types": "^3.973.4", + "@aws-sdk/xml-builder": "^3.972.8", + "@smithy/core": "^3.23.6", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/property-provider": "^4.2.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/signature-v4": "^5.3.10", + "@smithy/smithy-client": "^4.12.0", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.1", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" }, "engines": { @@ -447,12 +382,12 @@ } }, "node_modules/@aws-sdk/crc64-nvme": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.0.tgz", - "integrity": "sha512-ThlLhTqX68jvoIVv+pryOdb5coP1cX1/MaTbB9xkGDCbWbsqQcLqzPxuSoW1DCnAAIacmXCWpzUNOB9pv+xXQw==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.3.tgz", + "integrity": "sha512-UExeK+EFiq5LAcbHm96CQLSia+5pvpUVSAsVApscBzayb7/6dJBJKwV4/onsk4VbWSmqxDMcfuTD+pC4RxgZHg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -460,15 +395,15 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.4.tgz", - "integrity": "sha512-/8dnc7+XNMmViEom2xsNdArQxQPSgy4Z/lm6qaFPTrMFesT1bV3PsBhb19n09nmxHdrtQskYmViddUIjUQElXg==", + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.13.tgz", + "integrity": "sha512-6ljXKIQ22WFKyIs1jbORIkGanySBHaPPTOI4OxACP5WXgbcR0nDYfqNJfXEGwCK7IzHdNbCSFsNKKs0qCexR8Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/types": "^3.973.1", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/types": "^3.973.4", + "@smithy/property-provider": "^4.2.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -476,20 +411,20 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.6.tgz", - "integrity": "sha512-5ERWqRljiZv44AIdvIRQ3k+EAV0Sq2WeJHvXuK7gL7bovSxOf8Al7MLH7Eh3rdovH4KHFnlIty7J71mzvQBl5Q==", + "version": "3.972.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.15.tgz", + "integrity": "sha512-dJuSTreu/T8f24SHDNTjd7eQ4rabr0TzPh2UTCwYexQtzG3nTDKm1e5eIdhiroTMDkPEJeY+WPkA6F9wod/20A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/types": "^3.973.1", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/types": "^3.973.4", + "@smithy/fetch-http-handler": "^5.3.11", + "@smithy/node-http-handler": "^4.4.12", + "@smithy/property-provider": "^4.2.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/smithy-client": "^4.12.0", + "@smithy/types": "^4.13.0", + "@smithy/util-stream": "^4.5.15", "tslib": "^2.6.2" }, "engines": { @@ -497,24 +432,24 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.4.tgz", - "integrity": "sha512-eRUg+3HaUKuXWn/lEMirdiA5HOKmEl8hEHVuszIDt2MMBUKgVX5XNGmb3XmbgU17h6DZ+RtjbxQpjhz3SbTjZg==", + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.13.tgz", + "integrity": "sha512-JKSoGb7XeabZLBJptpqoZIFbROUIS65NuQnEHGOpuT9GuuZwag2qciKANiDLFiYk4u8nSrJC9JIOnWKVvPVjeA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/credential-provider-env": "^3.972.4", - "@aws-sdk/credential-provider-http": "^3.972.6", - "@aws-sdk/credential-provider-login": "^3.972.4", - "@aws-sdk/credential-provider-process": "^3.972.4", - "@aws-sdk/credential-provider-sso": "^3.972.4", - "@aws-sdk/credential-provider-web-identity": "^3.972.4", - "@aws-sdk/nested-clients": "3.982.0", - "@aws-sdk/types": "^3.973.1", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/credential-provider-env": "^3.972.13", + "@aws-sdk/credential-provider-http": "^3.972.15", + "@aws-sdk/credential-provider-login": "^3.972.13", + "@aws-sdk/credential-provider-process": "^3.972.13", + "@aws-sdk/credential-provider-sso": "^3.972.13", + "@aws-sdk/credential-provider-web-identity": "^3.972.13", + "@aws-sdk/nested-clients": "^3.996.3", + "@aws-sdk/types": "^3.973.4", + "@smithy/credential-provider-imds": "^4.2.10", + "@smithy/property-provider": "^4.2.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -522,18 +457,18 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.4.tgz", - "integrity": "sha512-nLGjXuvWWDlQAp505xIONI7Gam0vw2p7Qu3P6on/W2q7rjJXtYjtpHbcsaOjJ/pAju3eTvEQuSuRedcRHVQIAQ==", + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.13.tgz", + "integrity": "sha512-RtYcrxdnJHKY8MFQGLltCURcjuMjnaQpAxPE6+/QEdDHHItMKZgabRe/KScX737F9vJMQsmJy9EmMOkCnoC1JQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/nested-clients": "3.982.0", - "@aws-sdk/types": "^3.973.1", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/nested-clients": "^3.996.3", + "@aws-sdk/types": "^3.973.4", + "@smithy/property-provider": "^4.2.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -541,22 +476,22 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.5.tgz", - "integrity": "sha512-VWXKgSISQCI2GKN3zakTNHSiZ0+mux7v6YHmmbLQp/o3fvYUQJmKGcLZZzg2GFA+tGGBStplra9VFNf/WwxpYg==", + "version": "3.972.14", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.14.tgz", + "integrity": "sha512-WqoC2aliIjQM/L3oFf6j+op/enT2i9Cc4UTxxMEKrJNECkq4/PlKE5BOjSYFcq6G9mz65EFbXJh7zOU4CvjSKQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.4", - "@aws-sdk/credential-provider-http": "^3.972.6", - "@aws-sdk/credential-provider-ini": "^3.972.4", - "@aws-sdk/credential-provider-process": "^3.972.4", - "@aws-sdk/credential-provider-sso": "^3.972.4", - "@aws-sdk/credential-provider-web-identity": "^3.972.4", - "@aws-sdk/types": "^3.973.1", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", + "@aws-sdk/credential-provider-env": "^3.972.13", + "@aws-sdk/credential-provider-http": "^3.972.15", + "@aws-sdk/credential-provider-ini": "^3.972.13", + "@aws-sdk/credential-provider-process": "^3.972.13", + "@aws-sdk/credential-provider-sso": "^3.972.13", + "@aws-sdk/credential-provider-web-identity": "^3.972.13", + "@aws-sdk/types": "^3.973.4", + "@smithy/credential-provider-imds": "^4.2.10", + "@smithy/property-provider": "^4.2.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -564,16 +499,16 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.4.tgz", - "integrity": "sha512-TCZpWUnBQN1YPk6grvd5x419OfXjHvhj5Oj44GYb84dOVChpg/+2VoEj+YVA4F4E/6huQPNnX7UYbTtxJqgihw==", + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.13.tgz", + "integrity": "sha512-rsRG0LQA4VR+jnDyuqtXi2CePYSmfm5GNL9KxiW8DSe25YwJSr06W8TdUfONAC+rjsTI+aIH2rBGG5FjMeANrw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/types": "^3.973.1", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/types": "^3.973.4", + "@smithy/property-provider": "^4.2.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -581,18 +516,18 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.4.tgz", - "integrity": "sha512-wzsGwv9mKlwJ3vHLyembBvGE/5nPUIwRR2I51B1cBV4Cb4ql9nIIfpmHzm050XYTY5fqTOKJQnhLj7zj89VG8g==", + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.13.tgz", + "integrity": "sha512-fr0UU1wx8kNHDhTQBXioc/YviSW8iXuAxHvnH7eQUtn8F8o/FU3uu6EUMvAQgyvn7Ne5QFnC0Cj0BFlwCk+RFw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.982.0", - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/token-providers": "3.982.0", - "@aws-sdk/types": "^3.973.1", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/nested-clients": "^3.996.3", + "@aws-sdk/token-providers": "3.999.0", + "@aws-sdk/types": "^3.973.4", + "@smithy/property-provider": "^4.2.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -600,17 +535,17 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.4.tgz", - "integrity": "sha512-hIzw2XzrG8jzsUSEatehmpkd5rWzASg5IHUfA+m01k/RtvfAML7ZJVVohuKdhAYx+wV2AThLiQJVzqn7F0khrw==", + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.13.tgz", + "integrity": "sha512-a6iFMh1pgUH0TdcouBppLJUfPM7Yd3R9S1xFodPtCRoLqCz2RQFA3qjA8x4112PVYXEd4/pHX2eihapq39w0rA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/nested-clients": "3.982.0", - "@aws-sdk/types": "^3.973.1", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/nested-clients": "^3.996.3", + "@aws-sdk/types": "^3.973.4", + "@smithy/property-provider": "^4.2.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -651,24 +586,24 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.972.4.tgz", - "integrity": "sha512-xOxsUkF3O3BtIe3tf54OpPo94eZepjFm3z0Dd2TZKbsPxMiRTFXurC04wJ58o/wPW9YHVO9VqZik3MfoPfrKlw==", + "version": "3.973.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.973.1.tgz", + "integrity": "sha512-QLXsxsI6VW8LuGK+/yx699wzqP/NMCGk/hSGP+qtB+Lcff+23UlbahyouLlk+nfT7Iu021SkXBhnAuVd6IZcPw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/crc64-nvme": "3.972.0", - "@aws-sdk/types": "^3.973.1", - "@smithy/is-array-buffer": "^4.2.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.10", - "@smithy/util-utf8": "^4.2.0", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/crc64-nvme": "^3.972.3", + "@aws-sdk/types": "^3.973.4", + "@smithy/is-array-buffer": "^4.2.1", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-stream": "^4.5.15", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" }, "engines": { @@ -676,14 +611,14 @@ } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.3.tgz", - "integrity": "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA==", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.6.tgz", + "integrity": "sha512-5XHwjPH1lHB+1q4bfC7T8Z5zZrZXfaLcjSMwTd1HPSPrCmPFMbg3UQ5vgNWcVj0xoX4HWqTGkSf2byrjlnRg5w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.1", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", + "@aws-sdk/types": "^3.973.4", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -705,13 +640,13 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.3.tgz", - "integrity": "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA==", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.6.tgz", + "integrity": "sha512-iFnaMFMQdljAPrvsCVKYltPt2j40LQqukAbXvW7v0aL5I+1GO7bZ/W8m12WxW3gwyK5p5u1WlHg8TSAizC5cZw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.1", - "@smithy/types": "^4.12.0", + "@aws-sdk/types": "^3.973.4", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -719,15 +654,15 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.3.tgz", - "integrity": "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q==", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.6.tgz", + "integrity": "sha512-dY4v3of5EEMvik6+UDwQ96KfUFDk8m1oZDdkSc5lwi4o7rFrjnv0A+yTV+gu230iybQZnKgDLg/rt2P3H+Vscw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.1", + "@aws-sdk/types": "^3.973.4", "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -735,24 +670,24 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.6.tgz", - "integrity": "sha512-Xq7wM6kbgJN1UO++8dvH/efPb1nTwWqFCpZCR7RCLOETP7xAUAhVo7JmsCnML5Di/iC4Oo5VrJ4QmkYcMZniLw==", + "version": "3.972.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.15.tgz", + "integrity": "sha512-WDLgssevOU5BFx1s8jA7jj6cE5HuImz28sy9jKOaVtz0AW1lYqSzotzdyiybFaBcQTs5zxXOb2pUfyMxgEKY3Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/types": "^3.973.1", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/types": "^3.973.4", "@aws-sdk/util-arn-parser": "^3.972.2", - "@smithy/core": "^3.22.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", - "@smithy/types": "^4.12.0", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.10", - "@smithy/util-utf8": "^4.2.0", + "@smithy/core": "^3.23.6", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/signature-v4": "^5.3.10", + "@smithy/smithy-client": "^4.12.0", + "@smithy/types": "^4.13.0", + "@smithy/util-config-provider": "^4.2.1", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-stream": "^4.5.15", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" }, "engines": { @@ -774,17 +709,17 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.6.tgz", - "integrity": "sha512-TehLN8W/kivl0U9HcS+keryElEWORROpghDXZBLfnb40DXM7hx/i+7OOjkogXQOF3QtUraJVRkHQ07bPhrWKlw==", + "version": "3.972.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.15.tgz", + "integrity": "sha512-ABlFVcIMmuRAwBT+8q5abAxOr7WmaINirDJBnqGY5b5jSDo00UMlg/G4a0xoAgwm6oAECeJcwkvDlxDwKf58fQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.982.0", - "@smithy/core": "^3.22.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/types": "^3.973.4", + "@aws-sdk/util-endpoints": "^3.996.3", + "@smithy/core": "^3.23.6", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -792,15 +727,15 @@ } }, "node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/util-endpoints": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz", - "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==", + "version": "3.996.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.3.tgz", + "integrity": "sha512-yWIQSNiCjykLL+ezN5A+DfBb1gfXTytBxm57e64lYmwxDHNmInYHRJYYRAGWG1o77vKEiWaw4ui28e3yb1k5aQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.1", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", + "@aws-sdk/types": "^3.973.4", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.10", + "@smithy/util-endpoints": "^3.3.1", "tslib": "^2.6.2" }, "engines": { @@ -808,48 +743,48 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.982.0.tgz", - "integrity": "sha512-VVkaH27digrJfdVrT64rjkllvOp4oRiZuuJvrylLXAKl18ujToJR7AqpDldL/LS63RVne3QWIpkygIymxFtliQ==", + "version": "3.996.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.3.tgz", + "integrity": "sha512-AU5TY1V29xqwg/MxmA2odwysTez+ccFAhmfRJk+QZT5HNv90UTA9qKd1J9THlsQkvmH7HWTEV1lDNxkQO5PzNw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/middleware-host-header": "^3.972.3", - "@aws-sdk/middleware-logger": "^3.972.3", - "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.6", - "@aws-sdk/region-config-resolver": "^3.972.3", - "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.982.0", - "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.4", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.12", - "@smithy/middleware-retry": "^4.4.29", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.28", - "@smithy/util-defaults-mode-node": "^4.2.31", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/middleware-host-header": "^3.972.6", + "@aws-sdk/middleware-logger": "^3.972.6", + "@aws-sdk/middleware-recursion-detection": "^3.972.6", + "@aws-sdk/middleware-user-agent": "^3.972.15", + "@aws-sdk/region-config-resolver": "^3.972.6", + "@aws-sdk/types": "^3.973.4", + "@aws-sdk/util-endpoints": "^3.996.3", + "@aws-sdk/util-user-agent-browser": "^3.972.6", + "@aws-sdk/util-user-agent-node": "^3.973.0", + "@smithy/config-resolver": "^4.4.9", + "@smithy/core": "^3.23.6", + "@smithy/fetch-http-handler": "^5.3.11", + "@smithy/hash-node": "^4.2.10", + "@smithy/invalid-dependency": "^4.2.10", + "@smithy/middleware-content-length": "^4.2.10", + "@smithy/middleware-endpoint": "^4.4.20", + "@smithy/middleware-retry": "^4.4.37", + "@smithy/middleware-serde": "^4.2.11", + "@smithy/middleware-stack": "^4.2.10", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/node-http-handler": "^4.4.12", + "@smithy/protocol-http": "^5.3.10", + "@smithy/smithy-client": "^4.12.0", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.10", + "@smithy/util-base64": "^4.3.1", + "@smithy/util-body-length-browser": "^4.2.1", + "@smithy/util-body-length-node": "^4.2.2", + "@smithy/util-defaults-mode-browser": "^4.3.36", + "@smithy/util-defaults-mode-node": "^4.2.39", + "@smithy/util-endpoints": "^3.3.1", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-retry": "^4.2.10", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" }, "engines": { @@ -857,15 +792,15 @@ } }, "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz", - "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==", + "version": "3.996.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.3.tgz", + "integrity": "sha512-yWIQSNiCjykLL+ezN5A+DfBb1gfXTytBxm57e64lYmwxDHNmInYHRJYYRAGWG1o77vKEiWaw4ui28e3yb1k5aQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.1", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", + "@aws-sdk/types": "^3.973.4", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.10", + "@smithy/util-endpoints": "^3.3.1", "tslib": "^2.6.2" }, "engines": { @@ -873,15 +808,15 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.3.tgz", - "integrity": "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow==", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.6.tgz", + "integrity": "sha512-Aa5PusHLXAqLTX1UKDvI3pHQJtIsF7Q+3turCHqfz/1F61/zDMWfbTC8evjhrrYVAtz9Vsv3SJ/waSUeu7B6gw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", + "@aws-sdk/types": "^3.973.4", + "@smithy/config-resolver": "^4.4.9", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -906,17 +841,17 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.982.0.tgz", - "integrity": "sha512-v3M0KYp2TVHYHNBT7jHD9lLTWAdS9CaWJ2jboRKt0WAB65bA7iUEpR+k4VqKYtpQN4+8kKSc4w+K6kUNZkHKQw==", + "version": "3.999.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.999.0.tgz", + "integrity": "sha512-cx0hHUlgXULfykx4rdu/ciNAJaa3AL5xz3rieCz7NKJ68MJwlj3664Y8WR5MGgxfyYJBdamnkjNSx5Kekuc0cg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/nested-clients": "3.982.0", - "@aws-sdk/types": "^3.973.1", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/nested-clients": "^3.996.3", + "@aws-sdk/types": "^3.973.4", + "@smithy/property-provider": "^4.2.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -924,12 +859,12 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.973.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.1.tgz", - "integrity": "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg==", + "version": "3.973.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.4.tgz", + "integrity": "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -976,27 +911,27 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.3.tgz", - "integrity": "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw==", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.6.tgz", + "integrity": "sha512-Fwr/llD6GOrFgQnKaI2glhohdGuBDfHfora6iG9qsBBBR8xv1SdCSwbtf5CWlUdCw5X7g76G/9Hf0Inh0EmoxA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.1", - "@smithy/types": "^4.12.0", + "@aws-sdk/types": "^3.973.4", + "@smithy/types": "^4.13.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.4.tgz", - "integrity": "sha512-3WFCBLiM8QiHDfosQq3Py+lIMgWlFWwFQliUHUqwEiRqLnKyhgbU3AKa7AWJF7lW2Oc/2kFNY4MlAYVnVc0i8A==", + "version": "3.973.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.0.tgz", + "integrity": "sha512-A9J2G4Nf236e9GpaC1JnA8wRn6u6GjnOXiTwBLA6NUJhlBTIGfrTy+K1IazmF8y+4OFdW3O5TZlhyspJMqiqjA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.6", - "@aws-sdk/types": "^3.973.1", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", + "@aws-sdk/middleware-user-agent": "^3.972.15", + "@aws-sdk/types": "^3.973.4", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -1012,12 +947,12 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.5.tgz", - "integrity": "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.8.tgz", + "integrity": "sha512-Ql8elcUdYCha83Ol7NznBsgN5GVZnv3vUd86fEc6waU6oUdY0T1O9NODkEEOS/Uaogr87avDrUC6DSeM4oXjZg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" }, @@ -1772,9 +1707,9 @@ } }, "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "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": { @@ -1835,9 +1770,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "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": { @@ -2735,13 +2670,13 @@ } }, "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -4162,12 +4097,12 @@ } }, "node_modules/@smithy/abort-controller": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.8.tgz", - "integrity": "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.10.tgz", + "integrity": "sha512-qocxM/X4XGATqQtUkbE9SPUB6wekBi+FyJOMbPj0AhvyvFGYEmOlz6VB22iMePCQsFmMIvFSeViDvA7mZJG47g==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -4200,16 +4135,16 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz", - "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==", + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.9.tgz", + "integrity": "sha512-ejQvXqlcU30h7liR9fXtj7PIAau1t/sFbJpgWPfiYDs7zd16jpH0IsSXKcba2jF6ChTXvIjACs27kNMc5xxE2Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/types": "^4.13.0", + "@smithy/util-config-provider": "^4.2.1", + "@smithy/util-endpoints": "^3.3.1", + "@smithy/util-middleware": "^4.2.10", "tslib": "^2.6.2" }, "engines": { @@ -4217,20 +4152,20 @@ } }, "node_modules/@smithy/core": { - "version": "3.22.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.22.1.tgz", - "integrity": "sha512-x3ie6Crr58MWrm4viHqqy2Du2rHYZjwu8BekasrQx4ca+Y24dzVAwq3yErdqIbc2G3I0kLQA13PQ+/rde+u65g==", + "version": "3.23.6", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.6.tgz", + "integrity": "sha512-4xE+0L2NrsFKpEVFlFELkIHQddBvMbQ41LRIP74dGCXnY1zQ9DgksrBcRBDJT+iOzGy4VEJIeU3hkUK5mn06kg==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.2.9", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.11", - "@smithy/util-utf8": "^4.2.0", - "@smithy/uuid": "^1.1.0", + "@smithy/middleware-serde": "^4.2.11", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.1", + "@smithy/util-body-length-browser": "^4.2.1", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-stream": "^4.5.15", + "@smithy/util-utf8": "^4.2.1", + "@smithy/uuid": "^1.1.1", "tslib": "^2.6.2" }, "engines": { @@ -4238,15 +4173,15 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.8.tgz", - "integrity": "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.10.tgz", + "integrity": "sha512-3bsMLJJLTZGZqVGGeBVFfLzuRulVsGTj12BzRKODTHqUABpIr0jMN1vN3+u6r2OfyhAQ2pXaMZWX/swBK5I6PQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/property-provider": "^4.2.10", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.10", "tslib": "^2.6.2" }, "engines": { @@ -4324,14 +4259,14 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.10.tgz", - "integrity": "sha512-qF4EcrEtEf2P6f2kGGuSVe1lan26cn7PsWJBC3vZJ6D16Fm5FSN06udOMVoW6hjzQM3W7VDFwtyUG2szQY50dA==", + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.11.tgz", + "integrity": "sha512-wbTRjOxdFuyEg0CpumjZO0hkUl+fetJFqxNROepuLIoijQh51aMBmzFLfoQdwRjxsuuS2jizzIUTjPWgd8pd7g==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.9", - "@smithy/querystring-builder": "^4.2.9", - "@smithy/types": "^4.12.1", + "@smithy/protocol-http": "^5.3.10", + "@smithy/querystring-builder": "^4.2.10", + "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.1", "tslib": "^2.6.2" }, @@ -4355,14 +4290,14 @@ } }, "node_modules/@smithy/hash-node": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.8.tgz", - "integrity": "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.10.tgz", + "integrity": "sha512-1VzIOI5CcsvMDvP3iv1vG/RfLJVVVc67dCRyLSB2Hn9SWCZrDO3zvcIzj3BfEtqRW5kcMg5KAeVf1K3dR6nD3w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.12.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@smithy/types": "^4.13.0", + "@smithy/util-buffer-from": "^4.2.1", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" }, "engines": { @@ -4384,12 +4319,12 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.8.tgz", - "integrity": "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.10.tgz", + "integrity": "sha512-vy9KPNSFUU0ajFYk0sDZIYiUlAWGEAhRfehIr5ZkdFrRFTAuXEPUd41USuqHU6vvLX4r6Q9X7MKBco5+Il0Org==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -4438,13 +4373,13 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz", - "integrity": "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.10.tgz", + "integrity": "sha512-TQZ9kX5c6XbjhaEBpvhSvMEZ0klBs1CFtOdPFwATZSbC9UeQfKHPLPN9Y+I6wZGMOavlYTOlHEPDrt42PMSH9w==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -4452,18 +4387,18 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.13.tgz", - "integrity": "sha512-x6vn0PjYmGdNuKh/juUJJewZh7MoQ46jYaJ2mvekF4EesMuFfrl4LaW/k97Zjf8PTCPQmPgMvwewg7eNoH9n5w==", + "version": "4.4.20", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.20.tgz", + "integrity": "sha512-9W6Np4ceBP3XCYAGLoMCmn8t2RRVzuD1ndWPLBbv7H9CrwM9Bprf6Up6BM9ZA/3alodg0b7Kf6ftBK9R1N04vw==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.22.1", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-middleware": "^4.2.8", + "@smithy/core": "^3.23.6", + "@smithy/middleware-serde": "^4.2.11", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.10", + "@smithy/util-middleware": "^4.2.10", "tslib": "^2.6.2" }, "engines": { @@ -4471,19 +4406,19 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.4.30", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.30.tgz", - "integrity": "sha512-CBGyFvN0f8hlnqKH/jckRDz78Snrp345+PVk8Ux7pnkUCW97Iinse59lY78hBt04h1GZ6hjBN94BRwZy1xC8Bg==", + "version": "4.4.37", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.37.tgz", + "integrity": "sha512-/1psZZllBBSQ7+qo5+hhLz7AEPGLx3Z0+e3ramMBEuPK2PfvLK4SrncDB9VegX5mBn+oP/UTDrM6IHrFjvX1ZA==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/service-error-classification": "^4.2.8", - "@smithy/smithy-client": "^4.11.2", - "@smithy/types": "^4.12.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/uuid": "^1.1.0", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/service-error-classification": "^4.2.10", + "@smithy/smithy-client": "^4.12.0", + "@smithy/types": "^4.13.0", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-retry": "^4.2.10", + "@smithy/uuid": "^1.1.1", "tslib": "^2.6.2" }, "engines": { @@ -4491,13 +4426,13 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz", - "integrity": "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.11.tgz", + "integrity": "sha512-STQdONGPwbbC7cusL60s7vOa6He6A9w2jWhoapL0mgVjmR19pr26slV+yoSP76SIssMTX/95e5nOZ6UQv6jolg==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -4505,12 +4440,12 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz", - "integrity": "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.10.tgz", + "integrity": "sha512-pmts/WovNcE/tlyHa8z/groPeOtqtEpp61q3W0nW1nDJuMq/x+hWa/OVQBtgU0tBqupeXq0VBOLA4UZwE8I0YA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -4518,14 +4453,14 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz", - "integrity": "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==", + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.10.tgz", + "integrity": "sha512-UALRbJtVX34AdP2VECKVlnNgidLHA2A7YgcJzwSBg1hzmnO/bZBHl/LDQQyYifzUwp1UOODnl9JJ3KNawpUJ9w==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", + "@smithy/property-provider": "^4.2.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -4533,15 +4468,15 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.9.tgz", - "integrity": "sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w==", + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.12.tgz", + "integrity": "sha512-zo1+WKJkR9x7ZtMeMDAAsq2PufwiLDmkhcjpWPRRkmeIuOm6nq1qjFICSZbnjBvD09ei8KMo26BWxsu2BUU+5w==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/querystring-builder": "^4.2.8", - "@smithy/types": "^4.12.0", + "@smithy/abort-controller": "^4.2.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/querystring-builder": "^4.2.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -4549,12 +4484,12 @@ } }, "node_modules/@smithy/property-provider": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.8.tgz", - "integrity": "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.10.tgz", + "integrity": "sha512-5jm60P0CU7tom0eNrZ7YrkgBaoLFXzmqB0wVS+4uK8PPGmosSrLNf6rRd50UBvukztawZ7zyA8TxlrKpF5z9jw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -4562,12 +4497,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.9.tgz", - "integrity": "sha512-PRy4yZqsKI3Eab8TLc16Dj2NzC4dnw/8E95+++Jc+wwlkjBpAq3tNLqkLHMmSvDfxKQ+X5PmmCYt+rM/GcMKPA==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.10.tgz", + "integrity": "sha512-2NzVWpYY0tRdfeCJLsgrR89KE3NTWT2wGulhNUxYlRmtRmPwLQwKzhrfVaiNlA9ZpJvbW7cjTVChYKgnkqXj1A==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.12.1", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -4575,12 +4510,12 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.9.tgz", - "integrity": "sha512-/AIDaq0+ehv+QfeyAjCUFShwHIt+FA1IodsV/2AZE5h4PUZcQYv5sjmy9V67UWfsBoTjOPKUFYSRfGoNW9T2UQ==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.10.tgz", + "integrity": "sha512-HeN7kEvuzO2DmAzLukE9UryiUvejD3tMp9a1D1NJETerIfKobBUCLfviP6QEk500166eD2IATaXM59qgUI+YDA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.12.1", + "@smithy/types": "^4.13.0", "@smithy/util-uri-escape": "^4.2.1", "tslib": "^2.6.2" }, @@ -4589,12 +4524,12 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz", - "integrity": "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.10.tgz", + "integrity": "sha512-4Mh18J26+ao1oX5wXJfWlTT+Q1OpDR8ssiC9PDOuEgVBGloqg18Fw7h5Ct8DyT9NBYwJgtJ2nLjKKFU6RP1G1Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -4602,24 +4537,24 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.8.tgz", - "integrity": "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.10.tgz", + "integrity": "sha512-0R/+/Il5y8nB/By90o8hy/bWVYptbIfvoTYad0igYQO5RefhNCDmNzqxaMx7K1t/QWo0d6UynqpqN5cCQt1MCg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.12.0" + "@smithy/types": "^4.13.0" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.3.tgz", - "integrity": "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.5.tgz", + "integrity": "sha512-pHgASxl50rrtOztgQCPmOXFjRW+mCd7ALr/3uXNzRrRoGV5G2+78GOsQ3HlQuBVHCh9o6xqMNvlIKZjWn4Euug==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -4627,18 +4562,18 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.3.8", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.8.tgz", - "integrity": "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.10.tgz", + "integrity": "sha512-Wab3wW8468WqTKIxI+aZe3JYO52/RYT/8sDOdzkUhjnLakLe9qoQqIcfih/qxcF4qWEFoWBszY0mj5uxffaVXA==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-uri-escape": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@smithy/is-array-buffer": "^4.2.1", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", + "@smithy/util-hex-encoding": "^4.2.1", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-uri-escape": "^4.2.1", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" }, "engines": { @@ -4646,17 +4581,17 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.2.tgz", - "integrity": "sha512-SCkGmFak/xC1n7hKRsUr6wOnBTJ3L22Qd4e8H1fQIuKTAjntwgU8lrdMe7uHdiT2mJAOWA/60qaW9tiMu69n1A==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.0.tgz", + "integrity": "sha512-R8bQ9K3lCcXyZmBnQqUZJF4ChZmtWT5NLi6x5kgWx5D+/j0KorXcA0YcFg/X5TOgnTCy1tbKc6z2g2y4amFupQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.22.1", - "@smithy/middleware-endpoint": "^4.4.13", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.11", + "@smithy/core": "^3.23.6", + "@smithy/middleware-endpoint": "^4.4.20", + "@smithy/middleware-stack": "^4.2.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", + "@smithy/util-stream": "^4.5.15", "tslib": "^2.6.2" }, "engines": { @@ -4664,9 +4599,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.1.tgz", - "integrity": "sha512-ow30Ze/DD02KH2p0eMyIF2+qJzGyNb0kFrnTRtPpuOkQ4hrgvLdaU4YC6r/K8aOrCML4FH0Cmm0aI4503L1Hwg==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.0.tgz", + "integrity": "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4676,13 +4611,13 @@ } }, "node_modules/@smithy/url-parser": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.8.tgz", - "integrity": "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.10.tgz", + "integrity": "sha512-uypjF7fCDsRk26u3qHmFI/ePL7bxxB9vKkE+2WKEciHhz+4QtbzWiHRVNRJwU3cKhrYDYQE3b0MRFtqfLYdA4A==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.2.8", - "@smithy/types": "^4.12.0", + "@smithy/querystring-parser": "^4.2.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -4704,9 +4639,9 @@ } }, "node_modules/@smithy/util-body-length-browser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", - "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.1.tgz", + "integrity": "sha512-SiJeLiozrAoCrgDBUgsVbmqHmMgg/2bA15AzcbcW+zan7SuyAVHN4xTSbq0GlebAIwlcaX32xacnrG488/J/6g==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4716,9 +4651,9 @@ } }, "node_modules/@smithy/util-body-length-node": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", - "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.2.tgz", + "integrity": "sha512-4rHqBvxtJEBvsZcFQSPQqXP2b/yy/YlB66KlcEgcH2WNoOKCKB03DSLzXmOsXjbl8dJ4OEYTn31knhdznwk7zw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4741,9 +4676,9 @@ } }, "node_modules/@smithy/util-config-provider": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", - "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.1.tgz", + "integrity": "sha512-462id/00U8JWFw6qBuTSWfN5TxOHvDu4WliI97qOIOnuC/g+NDAknTU8eoGXEPlLkRVgWEr03jJBLV4o2FL8+A==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4753,14 +4688,14 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.29", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.29.tgz", - "integrity": "sha512-nIGy3DNRmOjaYaaKcQDzmWsro9uxlaqUOhZDHQed9MW/GmkBZPtnU70Pu1+GT9IBmUXwRdDuiyaeiy9Xtpn3+Q==", + "version": "4.3.36", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.36.tgz", + "integrity": "sha512-R0smq7EHQXRVMxkAxtH5akJ/FvgAmNF6bUy/GwY/N20T4GrwjT633NFm0VuRpC+8Bbv8R9A0DoJ9OiZL/M3xew==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.11.2", - "@smithy/types": "^4.12.0", + "@smithy/property-provider": "^4.2.10", + "@smithy/smithy-client": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -4768,17 +4703,17 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.32", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.32.tgz", - "integrity": "sha512-7dtFff6pu5fsjqrVve0YMhrnzJtccCWDacNKOkiZjJ++fmjGExmmSu341x+WU6Oc1IccL7lDuaUj7SfrHpWc5Q==", + "version": "4.2.39", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.39.tgz", + "integrity": "sha512-otWuoDm35btJV1L8MyHrPl462B07QCdMTktKc7/yM+Psv6KbED/ziXiHnmr7yPHUjfIwE9S8Max0LO24Mo3ZVg==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.4.6", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.11.2", - "@smithy/types": "^4.12.0", + "@smithy/config-resolver": "^4.4.9", + "@smithy/credential-provider-imds": "^4.2.10", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/property-provider": "^4.2.10", + "@smithy/smithy-client": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -4786,13 +4721,13 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz", - "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.1.tgz", + "integrity": "sha512-xyctc4klmjmieQiF9I1wssBWleRV0RhJ2DpO8+8yzi2LO1Z+4IWOZNGZGNj4+hq9kdo+nyfrRLmQTzc16Op2Vg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -4800,9 +4735,9 @@ } }, "node_modules/@smithy/util-hex-encoding": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", - "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.1.tgz", + "integrity": "sha512-c1hHtkgAWmE35/50gmdKajgGAKV3ePJ7t6UtEmpfCWJmQE9BQAQPz0URUVI89eSkcDqCtzqllxzG28IQoZPvwA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4812,12 +4747,12 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz", - "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.10.tgz", + "integrity": "sha512-LxaQIWLp4y0r72eA8mwPNQ9va4h5KeLM0I3M/HV9klmFaY2kN766wf5vsTzmaOpNNb7GgXAd9a25P3h8T49PSA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -4825,13 +4760,13 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.8.tgz", - "integrity": "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.10.tgz", + "integrity": "sha512-HrBzistfpyE5uqTwiyLsFHscgnwB0kgv8vySp7q5kZ0Eltn/tjosaSGGDj/jJ9ys7pWzIP/icE2d+7vMKXLv7A==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.8", - "@smithy/types": "^4.12.0", + "@smithy/service-error-classification": "^4.2.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "engines": { @@ -4839,18 +4774,18 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.11.tgz", - "integrity": "sha512-lKmZ0S/3Qj2OF5H1+VzvDLb6kRxGzZHq6f3rAsoSu5cTLGsn3v3VQBA8czkNNXlLjoFEtVu3OQT2jEeOtOE2CA==", + "version": "4.5.15", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.15.tgz", + "integrity": "sha512-OlOKnaqnkU9X+6wEkd7mN+WB7orPbCVDauXOj22Q7VtiTkvy7ZdSsOg4QiNAZMgI4OkvNf+/VLUC3VXkxuWJZw==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.9", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@smithy/fetch-http-handler": "^5.3.11", + "@smithy/node-http-handler": "^4.4.12", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.1", + "@smithy/util-buffer-from": "^4.2.1", + "@smithy/util-hex-encoding": "^4.2.1", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" }, "engines": { @@ -4897,9 +4832,9 @@ } }, "node_modules/@smithy/uuid": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", - "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.1.tgz", + "integrity": "sha512-dSfDCeihDmZlV2oyr0yWPTUfh07suS+R5OB+FZGiv/hHyK3hrFBW5rR1UYjfa57vBsrP9lciFkRPzebaV1Qujw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -5177,9 +5112,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "24.10.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", - "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", + "version": "24.10.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.15.tgz", + "integrity": "sha512-BgjLoRuSr0MTI5wA6gMw9Xy0sFudAaUuvrnjgGx9wZ522fYYLA5SYJ+1Y30vTcJEG+DRCyDHx/gzQVfofYzSdg==", "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -6214,27 +6149,14 @@ "node": ">=18.20.0" } }, - "node_modules/@wdio/config/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/@wdio/config/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" + "balanced-match": "^1.0.0" } }, "node_modules/@wdio/config/node_modules/glob": { @@ -6260,13 +6182,13 @@ } }, "node_modules/@wdio/config/node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "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": "^5.0.2" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -6336,13 +6258,13 @@ } }, "node_modules/@wdio/logger/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -6372,9 +6294,9 @@ } }, "node_modules/@wdio/repl/node_modules/@types/node": { - "version": "20.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", - "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "version": "20.19.35", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz", + "integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6395,9 +6317,9 @@ } }, "node_modules/@wdio/types/node_modules/@types/node": { - "version": "20.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", - "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "version": "20.19.35", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz", + "integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6593,27 +6515,14 @@ "node": ">= 14" } }, - "node_modules/archiver-utils/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/archiver-utils/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" + "balanced-match": "^1.0.0" } }, "node_modules/archiver-utils/node_modules/glob": { @@ -6638,13 +6547,13 @@ } }, "node_modules/archiver-utils/node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "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": "^5.0.2" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -8581,9 +8490,9 @@ } }, "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "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": { @@ -8742,9 +8651,9 @@ } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "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": { @@ -9590,9 +9499,9 @@ } }, "node_modules/globby/node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "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": { @@ -11438,9 +11347,9 @@ } }, "node_modules/minimatch": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", - "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.2" @@ -11462,9 +11371,9 @@ } }, "node_modules/minimatch/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -13064,9 +12973,9 @@ } }, "node_modules/readdir-glob/node_modules/minimatch": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.7.tgz", - "integrity": "sha512-FjiwU9HaHW6YB3H4a1sFudnv93lvydNjz2lmyUXR6IwKhGI+bgL3SOZrBGn6kvvX2pJvhEkGSGjyTHN47O4rqA==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "license": "ISC", "dependencies": { @@ -16048,9 +15957,9 @@ } }, "node_modules/webdriver/node_modules/@types/node": { - "version": "20.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", - "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "version": "20.19.35", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz", + "integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==", "dev": true, "license": "MIT", "dependencies": { @@ -16113,9 +16022,9 @@ } }, "node_modules/webdriverio/node_modules/@types/node": { - "version": "20.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", - "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "version": "20.19.35", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz", + "integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==", "dev": true, "license": "MIT", "dependencies": { @@ -16795,217 +16704,158 @@ "tslib": "^2.6.2" } }, - "@aws-sdk/client-sso": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.982.0.tgz", - "integrity": "sha512-qJrIiivmvujdGqJ0ldSUvhN3k3N7GtPesoOI1BSt0fNXovVnMz4C/JmnkhZihU7hJhDvxJaBROLYTU+lpild4w==", - "requires": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/middleware-host-header": "^3.972.3", - "@aws-sdk/middleware-logger": "^3.972.3", - "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.6", - "@aws-sdk/region-config-resolver": "^3.972.3", - "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.982.0", - "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.4", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.12", - "@smithy/middleware-retry": "^4.4.29", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.28", - "@smithy/util-defaults-mode-node": "^4.2.31", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "dependencies": { - "@aws-sdk/util-endpoints": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz", - "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==", - "requires": { - "@aws-sdk/types": "^3.973.1", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" - } - } - } - }, "@aws-sdk/core": { - "version": "3.973.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.6.tgz", - "integrity": "sha512-pz4ZOw3BLG0NdF25HoB9ymSYyPbMiIjwQJ2aROXRhAzt+b+EOxStfFv8s5iZyP6Kiw7aYhyWxj5G3NhmkoOTKw==", + "version": "3.973.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.15.tgz", + "integrity": "sha512-AlC0oQ1/mdJ8vCIqu524j5RB7M8i8E24bbkZmya1CuiQxkY7SdIZAyw7NDNMGaNINQFq/8oGRMX0HeOfCVsl/A==", "requires": { - "@aws-sdk/types": "^3.973.1", - "@aws-sdk/xml-builder": "^3.972.4", - "@smithy/core": "^3.22.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", + "@aws-sdk/types": "^3.973.4", + "@aws-sdk/xml-builder": "^3.972.8", + "@smithy/core": "^3.23.6", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/property-provider": "^4.2.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/signature-v4": "^5.3.10", + "@smithy/smithy-client": "^4.12.0", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.1", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "@aws-sdk/crc64-nvme": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.0.tgz", - "integrity": "sha512-ThlLhTqX68jvoIVv+pryOdb5coP1cX1/MaTbB9xkGDCbWbsqQcLqzPxuSoW1DCnAAIacmXCWpzUNOB9pv+xXQw==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.3.tgz", + "integrity": "sha512-UExeK+EFiq5LAcbHm96CQLSia+5pvpUVSAsVApscBzayb7/6dJBJKwV4/onsk4VbWSmqxDMcfuTD+pC4RxgZHg==", "requires": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@aws-sdk/credential-provider-env": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.4.tgz", - "integrity": "sha512-/8dnc7+XNMmViEom2xsNdArQxQPSgy4Z/lm6qaFPTrMFesT1bV3PsBhb19n09nmxHdrtQskYmViddUIjUQElXg==", + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.13.tgz", + "integrity": "sha512-6ljXKIQ22WFKyIs1jbORIkGanySBHaPPTOI4OxACP5WXgbcR0nDYfqNJfXEGwCK7IzHdNbCSFsNKKs0qCexR8Q==", "requires": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/types": "^3.973.1", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/types": "^3.973.4", + "@smithy/property-provider": "^4.2.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@aws-sdk/credential-provider-http": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.6.tgz", - "integrity": "sha512-5ERWqRljiZv44AIdvIRQ3k+EAV0Sq2WeJHvXuK7gL7bovSxOf8Al7MLH7Eh3rdovH4KHFnlIty7J71mzvQBl5Q==", + "version": "3.972.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.15.tgz", + "integrity": "sha512-dJuSTreu/T8f24SHDNTjd7eQ4rabr0TzPh2UTCwYexQtzG3nTDKm1e5eIdhiroTMDkPEJeY+WPkA6F9wod/20A==", "requires": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/types": "^3.973.1", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/types": "^3.973.4", + "@smithy/fetch-http-handler": "^5.3.11", + "@smithy/node-http-handler": "^4.4.12", + "@smithy/property-provider": "^4.2.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/smithy-client": "^4.12.0", + "@smithy/types": "^4.13.0", + "@smithy/util-stream": "^4.5.15", "tslib": "^2.6.2" } }, "@aws-sdk/credential-provider-ini": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.4.tgz", - "integrity": "sha512-eRUg+3HaUKuXWn/lEMirdiA5HOKmEl8hEHVuszIDt2MMBUKgVX5XNGmb3XmbgU17h6DZ+RtjbxQpjhz3SbTjZg==", + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.13.tgz", + "integrity": "sha512-JKSoGb7XeabZLBJptpqoZIFbROUIS65NuQnEHGOpuT9GuuZwag2qciKANiDLFiYk4u8nSrJC9JIOnWKVvPVjeA==", "requires": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/credential-provider-env": "^3.972.4", - "@aws-sdk/credential-provider-http": "^3.972.6", - "@aws-sdk/credential-provider-login": "^3.972.4", - "@aws-sdk/credential-provider-process": "^3.972.4", - "@aws-sdk/credential-provider-sso": "^3.972.4", - "@aws-sdk/credential-provider-web-identity": "^3.972.4", - "@aws-sdk/nested-clients": "3.982.0", - "@aws-sdk/types": "^3.973.1", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/credential-provider-env": "^3.972.13", + "@aws-sdk/credential-provider-http": "^3.972.15", + "@aws-sdk/credential-provider-login": "^3.972.13", + "@aws-sdk/credential-provider-process": "^3.972.13", + "@aws-sdk/credential-provider-sso": "^3.972.13", + "@aws-sdk/credential-provider-web-identity": "^3.972.13", + "@aws-sdk/nested-clients": "^3.996.3", + "@aws-sdk/types": "^3.973.4", + "@smithy/credential-provider-imds": "^4.2.10", + "@smithy/property-provider": "^4.2.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@aws-sdk/credential-provider-login": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.4.tgz", - "integrity": "sha512-nLGjXuvWWDlQAp505xIONI7Gam0vw2p7Qu3P6on/W2q7rjJXtYjtpHbcsaOjJ/pAju3eTvEQuSuRedcRHVQIAQ==", + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.13.tgz", + "integrity": "sha512-RtYcrxdnJHKY8MFQGLltCURcjuMjnaQpAxPE6+/QEdDHHItMKZgabRe/KScX737F9vJMQsmJy9EmMOkCnoC1JQ==", "requires": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/nested-clients": "3.982.0", - "@aws-sdk/types": "^3.973.1", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/nested-clients": "^3.996.3", + "@aws-sdk/types": "^3.973.4", + "@smithy/property-provider": "^4.2.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@aws-sdk/credential-provider-node": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.5.tgz", - "integrity": "sha512-VWXKgSISQCI2GKN3zakTNHSiZ0+mux7v6YHmmbLQp/o3fvYUQJmKGcLZZzg2GFA+tGGBStplra9VFNf/WwxpYg==", + "version": "3.972.14", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.14.tgz", + "integrity": "sha512-WqoC2aliIjQM/L3oFf6j+op/enT2i9Cc4UTxxMEKrJNECkq4/PlKE5BOjSYFcq6G9mz65EFbXJh7zOU4CvjSKQ==", "requires": { - "@aws-sdk/credential-provider-env": "^3.972.4", - "@aws-sdk/credential-provider-http": "^3.972.6", - "@aws-sdk/credential-provider-ini": "^3.972.4", - "@aws-sdk/credential-provider-process": "^3.972.4", - "@aws-sdk/credential-provider-sso": "^3.972.4", - "@aws-sdk/credential-provider-web-identity": "^3.972.4", - "@aws-sdk/types": "^3.973.1", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", + "@aws-sdk/credential-provider-env": "^3.972.13", + "@aws-sdk/credential-provider-http": "^3.972.15", + "@aws-sdk/credential-provider-ini": "^3.972.13", + "@aws-sdk/credential-provider-process": "^3.972.13", + "@aws-sdk/credential-provider-sso": "^3.972.13", + "@aws-sdk/credential-provider-web-identity": "^3.972.13", + "@aws-sdk/types": "^3.973.4", + "@smithy/credential-provider-imds": "^4.2.10", + "@smithy/property-provider": "^4.2.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@aws-sdk/credential-provider-process": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.4.tgz", - "integrity": "sha512-TCZpWUnBQN1YPk6grvd5x419OfXjHvhj5Oj44GYb84dOVChpg/+2VoEj+YVA4F4E/6huQPNnX7UYbTtxJqgihw==", + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.13.tgz", + "integrity": "sha512-rsRG0LQA4VR+jnDyuqtXi2CePYSmfm5GNL9KxiW8DSe25YwJSr06W8TdUfONAC+rjsTI+aIH2rBGG5FjMeANrw==", "requires": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/types": "^3.973.1", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/types": "^3.973.4", + "@smithy/property-provider": "^4.2.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@aws-sdk/credential-provider-sso": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.4.tgz", - "integrity": "sha512-wzsGwv9mKlwJ3vHLyembBvGE/5nPUIwRR2I51B1cBV4Cb4ql9nIIfpmHzm050XYTY5fqTOKJQnhLj7zj89VG8g==", + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.13.tgz", + "integrity": "sha512-fr0UU1wx8kNHDhTQBXioc/YviSW8iXuAxHvnH7eQUtn8F8o/FU3uu6EUMvAQgyvn7Ne5QFnC0Cj0BFlwCk+RFw==", "requires": { - "@aws-sdk/client-sso": "3.982.0", - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/token-providers": "3.982.0", - "@aws-sdk/types": "^3.973.1", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/nested-clients": "^3.996.3", + "@aws-sdk/token-providers": "3.999.0", + "@aws-sdk/types": "^3.973.4", + "@smithy/property-provider": "^4.2.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@aws-sdk/credential-provider-web-identity": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.4.tgz", - "integrity": "sha512-hIzw2XzrG8jzsUSEatehmpkd5rWzASg5IHUfA+m01k/RtvfAML7ZJVVohuKdhAYx+wV2AThLiQJVzqn7F0khrw==", + "version": "3.972.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.13.tgz", + "integrity": "sha512-a6iFMh1pgUH0TdcouBppLJUfPM7Yd3R9S1xFodPtCRoLqCz2RQFA3qjA8x4112PVYXEd4/pHX2eihapq39w0rA==", "requires": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/nested-clients": "3.982.0", - "@aws-sdk/types": "^3.973.1", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/nested-clients": "^3.996.3", + "@aws-sdk/types": "^3.973.4", + "@smithy/property-provider": "^4.2.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, @@ -17035,34 +16885,34 @@ } }, "@aws-sdk/middleware-flexible-checksums": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.972.4.tgz", - "integrity": "sha512-xOxsUkF3O3BtIe3tf54OpPo94eZepjFm3z0Dd2TZKbsPxMiRTFXurC04wJ58o/wPW9YHVO9VqZik3MfoPfrKlw==", + "version": "3.973.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.973.1.tgz", + "integrity": "sha512-QLXsxsI6VW8LuGK+/yx699wzqP/NMCGk/hSGP+qtB+Lcff+23UlbahyouLlk+nfT7Iu021SkXBhnAuVd6IZcPw==", "requires": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/crc64-nvme": "3.972.0", - "@aws-sdk/types": "^3.973.1", - "@smithy/is-array-buffer": "^4.2.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.10", - "@smithy/util-utf8": "^4.2.0", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/crc64-nvme": "^3.972.3", + "@aws-sdk/types": "^3.973.4", + "@smithy/is-array-buffer": "^4.2.1", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-stream": "^4.5.15", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "@aws-sdk/middleware-host-header": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.3.tgz", - "integrity": "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA==", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.6.tgz", + "integrity": "sha512-5XHwjPH1lHB+1q4bfC7T8Z5zZrZXfaLcjSMwTd1HPSPrCmPFMbg3UQ5vgNWcVj0xoX4HWqTGkSf2byrjlnRg5w==", "requires": { - "@aws-sdk/types": "^3.973.1", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", + "@aws-sdk/types": "^3.973.4", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, @@ -17077,45 +16927,45 @@ } }, "@aws-sdk/middleware-logger": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.3.tgz", - "integrity": "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA==", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.6.tgz", + "integrity": "sha512-iFnaMFMQdljAPrvsCVKYltPt2j40LQqukAbXvW7v0aL5I+1GO7bZ/W8m12WxW3gwyK5p5u1WlHg8TSAizC5cZw==", "requires": { - "@aws-sdk/types": "^3.973.1", - "@smithy/types": "^4.12.0", + "@aws-sdk/types": "^3.973.4", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@aws-sdk/middleware-recursion-detection": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.3.tgz", - "integrity": "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q==", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.6.tgz", + "integrity": "sha512-dY4v3of5EEMvik6+UDwQ96KfUFDk8m1oZDdkSc5lwi4o7rFrjnv0A+yTV+gu230iybQZnKgDLg/rt2P3H+Vscw==", "requires": { - "@aws-sdk/types": "^3.973.1", + "@aws-sdk/types": "^3.973.4", "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@aws-sdk/middleware-sdk-s3": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.6.tgz", - "integrity": "sha512-Xq7wM6kbgJN1UO++8dvH/efPb1nTwWqFCpZCR7RCLOETP7xAUAhVo7JmsCnML5Di/iC4Oo5VrJ4QmkYcMZniLw==", + "version": "3.972.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.15.tgz", + "integrity": "sha512-WDLgssevOU5BFx1s8jA7jj6cE5HuImz28sy9jKOaVtz0AW1lYqSzotzdyiybFaBcQTs5zxXOb2pUfyMxgEKY3Q==", "requires": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/types": "^3.973.1", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/types": "^3.973.4", "@aws-sdk/util-arn-parser": "^3.972.2", - "@smithy/core": "^3.22.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", - "@smithy/types": "^4.12.0", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.10", - "@smithy/util-utf8": "^4.2.0", + "@smithy/core": "^3.23.6", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/signature-v4": "^5.3.10", + "@smithy/smithy-client": "^4.12.0", + "@smithy/types": "^4.13.0", + "@smithy/util-config-provider": "^4.2.1", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-stream": "^4.5.15", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, @@ -17130,101 +16980,101 @@ } }, "@aws-sdk/middleware-user-agent": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.6.tgz", - "integrity": "sha512-TehLN8W/kivl0U9HcS+keryElEWORROpghDXZBLfnb40DXM7hx/i+7OOjkogXQOF3QtUraJVRkHQ07bPhrWKlw==", + "version": "3.972.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.15.tgz", + "integrity": "sha512-ABlFVcIMmuRAwBT+8q5abAxOr7WmaINirDJBnqGY5b5jSDo00UMlg/G4a0xoAgwm6oAECeJcwkvDlxDwKf58fQ==", "requires": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.982.0", - "@smithy/core": "^3.22.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/types": "^3.973.4", + "@aws-sdk/util-endpoints": "^3.996.3", + "@smithy/core": "^3.23.6", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "dependencies": { "@aws-sdk/util-endpoints": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz", - "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==", + "version": "3.996.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.3.tgz", + "integrity": "sha512-yWIQSNiCjykLL+ezN5A+DfBb1gfXTytBxm57e64lYmwxDHNmInYHRJYYRAGWG1o77vKEiWaw4ui28e3yb1k5aQ==", "requires": { - "@aws-sdk/types": "^3.973.1", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", + "@aws-sdk/types": "^3.973.4", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.10", + "@smithy/util-endpoints": "^3.3.1", "tslib": "^2.6.2" } } } }, "@aws-sdk/nested-clients": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.982.0.tgz", - "integrity": "sha512-VVkaH27digrJfdVrT64rjkllvOp4oRiZuuJvrylLXAKl18ujToJR7AqpDldL/LS63RVne3QWIpkygIymxFtliQ==", + "version": "3.996.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.3.tgz", + "integrity": "sha512-AU5TY1V29xqwg/MxmA2odwysTez+ccFAhmfRJk+QZT5HNv90UTA9qKd1J9THlsQkvmH7HWTEV1lDNxkQO5PzNw==", "requires": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/middleware-host-header": "^3.972.3", - "@aws-sdk/middleware-logger": "^3.972.3", - "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.6", - "@aws-sdk/region-config-resolver": "^3.972.3", - "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.982.0", - "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.4", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.12", - "@smithy/middleware-retry": "^4.4.29", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.28", - "@smithy/util-defaults-mode-node": "^4.2.31", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/middleware-host-header": "^3.972.6", + "@aws-sdk/middleware-logger": "^3.972.6", + "@aws-sdk/middleware-recursion-detection": "^3.972.6", + "@aws-sdk/middleware-user-agent": "^3.972.15", + "@aws-sdk/region-config-resolver": "^3.972.6", + "@aws-sdk/types": "^3.973.4", + "@aws-sdk/util-endpoints": "^3.996.3", + "@aws-sdk/util-user-agent-browser": "^3.972.6", + "@aws-sdk/util-user-agent-node": "^3.973.0", + "@smithy/config-resolver": "^4.4.9", + "@smithy/core": "^3.23.6", + "@smithy/fetch-http-handler": "^5.3.11", + "@smithy/hash-node": "^4.2.10", + "@smithy/invalid-dependency": "^4.2.10", + "@smithy/middleware-content-length": "^4.2.10", + "@smithy/middleware-endpoint": "^4.4.20", + "@smithy/middleware-retry": "^4.4.37", + "@smithy/middleware-serde": "^4.2.11", + "@smithy/middleware-stack": "^4.2.10", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/node-http-handler": "^4.4.12", + "@smithy/protocol-http": "^5.3.10", + "@smithy/smithy-client": "^4.12.0", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.10", + "@smithy/util-base64": "^4.3.1", + "@smithy/util-body-length-browser": "^4.2.1", + "@smithy/util-body-length-node": "^4.2.2", + "@smithy/util-defaults-mode-browser": "^4.3.36", + "@smithy/util-defaults-mode-node": "^4.2.39", + "@smithy/util-endpoints": "^3.3.1", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-retry": "^4.2.10", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" }, "dependencies": { "@aws-sdk/util-endpoints": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.982.0.tgz", - "integrity": "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ==", + "version": "3.996.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.3.tgz", + "integrity": "sha512-yWIQSNiCjykLL+ezN5A+DfBb1gfXTytBxm57e64lYmwxDHNmInYHRJYYRAGWG1o77vKEiWaw4ui28e3yb1k5aQ==", "requires": { - "@aws-sdk/types": "^3.973.1", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", + "@aws-sdk/types": "^3.973.4", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.10", + "@smithy/util-endpoints": "^3.3.1", "tslib": "^2.6.2" } } } }, "@aws-sdk/region-config-resolver": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.3.tgz", - "integrity": "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow==", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.6.tgz", + "integrity": "sha512-Aa5PusHLXAqLTX1UKDvI3pHQJtIsF7Q+3turCHqfz/1F61/zDMWfbTC8evjhrrYVAtz9Vsv3SJ/waSUeu7B6gw==", "requires": { - "@aws-sdk/types": "^3.973.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", + "@aws-sdk/types": "^3.973.4", + "@smithy/config-resolver": "^4.4.9", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, @@ -17242,25 +17092,25 @@ } }, "@aws-sdk/token-providers": { - "version": "3.982.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.982.0.tgz", - "integrity": "sha512-v3M0KYp2TVHYHNBT7jHD9lLTWAdS9CaWJ2jboRKt0WAB65bA7iUEpR+k4VqKYtpQN4+8kKSc4w+K6kUNZkHKQw==", + "version": "3.999.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.999.0.tgz", + "integrity": "sha512-cx0hHUlgXULfykx4rdu/ciNAJaa3AL5xz3rieCz7NKJ68MJwlj3664Y8WR5MGgxfyYJBdamnkjNSx5Kekuc0cg==", "requires": { - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/nested-clients": "3.982.0", - "@aws-sdk/types": "^3.973.1", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", + "@aws-sdk/core": "^3.973.15", + "@aws-sdk/nested-clients": "^3.996.3", + "@aws-sdk/types": "^3.973.4", + "@smithy/property-provider": "^4.2.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@aws-sdk/types": { - "version": "3.973.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.1.tgz", - "integrity": "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg==", + "version": "3.973.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.4.tgz", + "integrity": "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q==", "requires": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, @@ -17293,34 +17143,34 @@ } }, "@aws-sdk/util-user-agent-browser": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.3.tgz", - "integrity": "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw==", + "version": "3.972.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.6.tgz", + "integrity": "sha512-Fwr/llD6GOrFgQnKaI2glhohdGuBDfHfora6iG9qsBBBR8xv1SdCSwbtf5CWlUdCw5X7g76G/9Hf0Inh0EmoxA==", "requires": { - "@aws-sdk/types": "^3.973.1", - "@smithy/types": "^4.12.0", + "@aws-sdk/types": "^3.973.4", + "@smithy/types": "^4.13.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "@aws-sdk/util-user-agent-node": { - "version": "3.972.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.4.tgz", - "integrity": "sha512-3WFCBLiM8QiHDfosQq3Py+lIMgWlFWwFQliUHUqwEiRqLnKyhgbU3AKa7AWJF7lW2Oc/2kFNY4MlAYVnVc0i8A==", + "version": "3.973.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.0.tgz", + "integrity": "sha512-A9J2G4Nf236e9GpaC1JnA8wRn6u6GjnOXiTwBLA6NUJhlBTIGfrTy+K1IazmF8y+4OFdW3O5TZlhyspJMqiqjA==", "requires": { - "@aws-sdk/middleware-user-agent": "^3.972.6", - "@aws-sdk/types": "^3.973.1", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", + "@aws-sdk/middleware-user-agent": "^3.972.15", + "@aws-sdk/types": "^3.973.4", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@aws-sdk/xml-builder": { - "version": "3.972.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.5.tgz", - "integrity": "sha512-mCae5Ys6Qm1LDu0qdGwx2UQ63ONUe+FHw908fJzLDqFKTDBK4LDZUqKWm4OkTCNFq19bftjsBSESIGLD/s3/rA==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.8.tgz", + "integrity": "sha512-Ql8elcUdYCha83Ol7NznBsgN5GVZnv3vUd86fEc6waU6oUdY0T1O9NODkEEOS/Uaogr87avDrUC6DSeM4oXjZg==", "requires": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } @@ -17722,9 +17572,9 @@ }, "dependencies": { "minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -17768,9 +17618,9 @@ }, "dependencies": { "minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -18372,12 +18222,12 @@ } }, "strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "requires": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" } }, "wrap-ansi": { @@ -19294,11 +19144,11 @@ "integrity": "sha512-KV321z5m/0nuAg83W1dPLy85HpHDk7Sdi4fJbwvacWsEhAh+rZUW4ZfGcXmUIvjZg4ss2bcwNlRhJ7GBEUG08w==" }, "@smithy/abort-controller": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.8.tgz", - "integrity": "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.10.tgz", + "integrity": "sha512-qocxM/X4XGATqQtUkbE9SPUB6wekBi+FyJOMbPj0AhvyvFGYEmOlz6VB22iMePCQsFmMIvFSeViDvA7mZJG47g==", "requires": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, @@ -19320,44 +19170,44 @@ } }, "@smithy/config-resolver": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz", - "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==", + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.9.tgz", + "integrity": "sha512-ejQvXqlcU30h7liR9fXtj7PIAau1t/sFbJpgWPfiYDs7zd16jpH0IsSXKcba2jF6ChTXvIjACs27kNMc5xxE2Q==", "requires": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/types": "^4.13.0", + "@smithy/util-config-provider": "^4.2.1", + "@smithy/util-endpoints": "^3.3.1", + "@smithy/util-middleware": "^4.2.10", "tslib": "^2.6.2" } }, "@smithy/core": { - "version": "3.22.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.22.1.tgz", - "integrity": "sha512-x3ie6Crr58MWrm4viHqqy2Du2rHYZjwu8BekasrQx4ca+Y24dzVAwq3yErdqIbc2G3I0kLQA13PQ+/rde+u65g==", + "version": "3.23.6", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.6.tgz", + "integrity": "sha512-4xE+0L2NrsFKpEVFlFELkIHQddBvMbQ41LRIP74dGCXnY1zQ9DgksrBcRBDJT+iOzGy4VEJIeU3hkUK5mn06kg==", "requires": { - "@smithy/middleware-serde": "^4.2.9", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.11", - "@smithy/util-utf8": "^4.2.0", - "@smithy/uuid": "^1.1.0", + "@smithy/middleware-serde": "^4.2.11", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.1", + "@smithy/util-body-length-browser": "^4.2.1", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-stream": "^4.5.15", + "@smithy/util-utf8": "^4.2.1", + "@smithy/uuid": "^1.1.1", "tslib": "^2.6.2" } }, "@smithy/credential-provider-imds": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.8.tgz", - "integrity": "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.10.tgz", + "integrity": "sha512-3bsMLJJLTZGZqVGGeBVFfLzuRulVsGTj12BzRKODTHqUABpIr0jMN1vN3+u6r2OfyhAQ2pXaMZWX/swBK5I6PQ==", "requires": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/property-provider": "^4.2.10", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.10", "tslib": "^2.6.2" } }, @@ -19412,13 +19262,13 @@ } }, "@smithy/fetch-http-handler": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.10.tgz", - "integrity": "sha512-qF4EcrEtEf2P6f2kGGuSVe1lan26cn7PsWJBC3vZJ6D16Fm5FSN06udOMVoW6hjzQM3W7VDFwtyUG2szQY50dA==", + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.11.tgz", + "integrity": "sha512-wbTRjOxdFuyEg0CpumjZO0hkUl+fetJFqxNROepuLIoijQh51aMBmzFLfoQdwRjxsuuS2jizzIUTjPWgd8pd7g==", "requires": { - "@smithy/protocol-http": "^5.3.9", - "@smithy/querystring-builder": "^4.2.9", - "@smithy/types": "^4.12.1", + "@smithy/protocol-http": "^5.3.10", + "@smithy/querystring-builder": "^4.2.10", + "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.1", "tslib": "^2.6.2" } @@ -19435,13 +19285,13 @@ } }, "@smithy/hash-node": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.8.tgz", - "integrity": "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.10.tgz", + "integrity": "sha512-1VzIOI5CcsvMDvP3iv1vG/RfLJVVVc67dCRyLSB2Hn9SWCZrDO3zvcIzj3BfEtqRW5kcMg5KAeVf1K3dR6nD3w==", "requires": { - "@smithy/types": "^4.12.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@smithy/types": "^4.13.0", + "@smithy/util-buffer-from": "^4.2.1", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, @@ -19456,11 +19306,11 @@ } }, "@smithy/invalid-dependency": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.8.tgz", - "integrity": "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.10.tgz", + "integrity": "sha512-vy9KPNSFUU0ajFYk0sDZIYiUlAWGEAhRfehIr5ZkdFrRFTAuXEPUd41USuqHU6vvLX4r6Q9X7MKBco5+Il0Org==", "requires": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, @@ -19494,186 +19344,186 @@ } }, "@smithy/middleware-content-length": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz", - "integrity": "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.10.tgz", + "integrity": "sha512-TQZ9kX5c6XbjhaEBpvhSvMEZ0klBs1CFtOdPFwATZSbC9UeQfKHPLPN9Y+I6wZGMOavlYTOlHEPDrt42PMSH9w==", "requires": { - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@smithy/middleware-endpoint": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.13.tgz", - "integrity": "sha512-x6vn0PjYmGdNuKh/juUJJewZh7MoQ46jYaJ2mvekF4EesMuFfrl4LaW/k97Zjf8PTCPQmPgMvwewg7eNoH9n5w==", + "version": "4.4.20", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.20.tgz", + "integrity": "sha512-9W6Np4ceBP3XCYAGLoMCmn8t2RRVzuD1ndWPLBbv7H9CrwM9Bprf6Up6BM9ZA/3alodg0b7Kf6ftBK9R1N04vw==", "requires": { - "@smithy/core": "^3.22.1", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-middleware": "^4.2.8", + "@smithy/core": "^3.23.6", + "@smithy/middleware-serde": "^4.2.11", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", + "@smithy/url-parser": "^4.2.10", + "@smithy/util-middleware": "^4.2.10", "tslib": "^2.6.2" } }, "@smithy/middleware-retry": { - "version": "4.4.30", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.30.tgz", - "integrity": "sha512-CBGyFvN0f8hlnqKH/jckRDz78Snrp345+PVk8Ux7pnkUCW97Iinse59lY78hBt04h1GZ6hjBN94BRwZy1xC8Bg==", + "version": "4.4.37", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.37.tgz", + "integrity": "sha512-/1psZZllBBSQ7+qo5+hhLz7AEPGLx3Z0+e3ramMBEuPK2PfvLK4SrncDB9VegX5mBn+oP/UTDrM6IHrFjvX1ZA==", "requires": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/service-error-classification": "^4.2.8", - "@smithy/smithy-client": "^4.11.2", - "@smithy/types": "^4.12.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/uuid": "^1.1.0", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/service-error-classification": "^4.2.10", + "@smithy/smithy-client": "^4.12.0", + "@smithy/types": "^4.13.0", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-retry": "^4.2.10", + "@smithy/uuid": "^1.1.1", "tslib": "^2.6.2" } }, "@smithy/middleware-serde": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz", - "integrity": "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.11.tgz", + "integrity": "sha512-STQdONGPwbbC7cusL60s7vOa6He6A9w2jWhoapL0mgVjmR19pr26slV+yoSP76SIssMTX/95e5nOZ6UQv6jolg==", "requires": { - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@smithy/middleware-stack": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz", - "integrity": "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.10.tgz", + "integrity": "sha512-pmts/WovNcE/tlyHa8z/groPeOtqtEpp61q3W0nW1nDJuMq/x+hWa/OVQBtgU0tBqupeXq0VBOLA4UZwE8I0YA==", "requires": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@smithy/node-config-provider": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz", - "integrity": "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==", + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.10.tgz", + "integrity": "sha512-UALRbJtVX34AdP2VECKVlnNgidLHA2A7YgcJzwSBg1hzmnO/bZBHl/LDQQyYifzUwp1UOODnl9JJ3KNawpUJ9w==", "requires": { - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", + "@smithy/property-provider": "^4.2.10", + "@smithy/shared-ini-file-loader": "^4.4.5", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@smithy/node-http-handler": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.9.tgz", - "integrity": "sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w==", + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.12.tgz", + "integrity": "sha512-zo1+WKJkR9x7ZtMeMDAAsq2PufwiLDmkhcjpWPRRkmeIuOm6nq1qjFICSZbnjBvD09ei8KMo26BWxsu2BUU+5w==", "requires": { - "@smithy/abort-controller": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/querystring-builder": "^4.2.8", - "@smithy/types": "^4.12.0", + "@smithy/abort-controller": "^4.2.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/querystring-builder": "^4.2.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@smithy/property-provider": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.8.tgz", - "integrity": "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.10.tgz", + "integrity": "sha512-5jm60P0CU7tom0eNrZ7YrkgBaoLFXzmqB0wVS+4uK8PPGmosSrLNf6rRd50UBvukztawZ7zyA8TxlrKpF5z9jw==", "requires": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@smithy/protocol-http": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.9.tgz", - "integrity": "sha512-PRy4yZqsKI3Eab8TLc16Dj2NzC4dnw/8E95+++Jc+wwlkjBpAq3tNLqkLHMmSvDfxKQ+X5PmmCYt+rM/GcMKPA==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.10.tgz", + "integrity": "sha512-2NzVWpYY0tRdfeCJLsgrR89KE3NTWT2wGulhNUxYlRmtRmPwLQwKzhrfVaiNlA9ZpJvbW7cjTVChYKgnkqXj1A==", "requires": { - "@smithy/types": "^4.12.1", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@smithy/querystring-builder": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.9.tgz", - "integrity": "sha512-/AIDaq0+ehv+QfeyAjCUFShwHIt+FA1IodsV/2AZE5h4PUZcQYv5sjmy9V67UWfsBoTjOPKUFYSRfGoNW9T2UQ==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.10.tgz", + "integrity": "sha512-HeN7kEvuzO2DmAzLukE9UryiUvejD3tMp9a1D1NJETerIfKobBUCLfviP6QEk500166eD2IATaXM59qgUI+YDA==", "requires": { - "@smithy/types": "^4.12.1", + "@smithy/types": "^4.13.0", "@smithy/util-uri-escape": "^4.2.1", "tslib": "^2.6.2" } }, "@smithy/querystring-parser": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz", - "integrity": "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.10.tgz", + "integrity": "sha512-4Mh18J26+ao1oX5wXJfWlTT+Q1OpDR8ssiC9PDOuEgVBGloqg18Fw7h5Ct8DyT9NBYwJgtJ2nLjKKFU6RP1G1Q==", "requires": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@smithy/service-error-classification": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.8.tgz", - "integrity": "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.10.tgz", + "integrity": "sha512-0R/+/Il5y8nB/By90o8hy/bWVYptbIfvoTYad0igYQO5RefhNCDmNzqxaMx7K1t/QWo0d6UynqpqN5cCQt1MCg==", "requires": { - "@smithy/types": "^4.12.0" + "@smithy/types": "^4.13.0" } }, "@smithy/shared-ini-file-loader": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.3.tgz", - "integrity": "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.5.tgz", + "integrity": "sha512-pHgASxl50rrtOztgQCPmOXFjRW+mCd7ALr/3uXNzRrRoGV5G2+78GOsQ3HlQuBVHCh9o6xqMNvlIKZjWn4Euug==", "requires": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@smithy/signature-v4": { - "version": "5.3.8", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.8.tgz", - "integrity": "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.10.tgz", + "integrity": "sha512-Wab3wW8468WqTKIxI+aZe3JYO52/RYT/8sDOdzkUhjnLakLe9qoQqIcfih/qxcF4qWEFoWBszY0mj5uxffaVXA==", "requires": { - "@smithy/is-array-buffer": "^4.2.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-uri-escape": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@smithy/is-array-buffer": "^4.2.1", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", + "@smithy/util-hex-encoding": "^4.2.1", + "@smithy/util-middleware": "^4.2.10", + "@smithy/util-uri-escape": "^4.2.1", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "@smithy/smithy-client": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.2.tgz", - "integrity": "sha512-SCkGmFak/xC1n7hKRsUr6wOnBTJ3L22Qd4e8H1fQIuKTAjntwgU8lrdMe7uHdiT2mJAOWA/60qaW9tiMu69n1A==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.0.tgz", + "integrity": "sha512-R8bQ9K3lCcXyZmBnQqUZJF4ChZmtWT5NLi6x5kgWx5D+/j0KorXcA0YcFg/X5TOgnTCy1tbKc6z2g2y4amFupQ==", "requires": { - "@smithy/core": "^3.22.1", - "@smithy/middleware-endpoint": "^4.4.13", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.11", + "@smithy/core": "^3.23.6", + "@smithy/middleware-endpoint": "^4.4.20", + "@smithy/middleware-stack": "^4.2.10", + "@smithy/protocol-http": "^5.3.10", + "@smithy/types": "^4.13.0", + "@smithy/util-stream": "^4.5.15", "tslib": "^2.6.2" } }, "@smithy/types": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.1.tgz", - "integrity": "sha512-ow30Ze/DD02KH2p0eMyIF2+qJzGyNb0kFrnTRtPpuOkQ4hrgvLdaU4YC6r/K8aOrCML4FH0Cmm0aI4503L1Hwg==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.0.tgz", + "integrity": "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw==", "requires": { "tslib": "^2.6.2" } }, "@smithy/url-parser": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.8.tgz", - "integrity": "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.10.tgz", + "integrity": "sha512-uypjF7fCDsRk26u3qHmFI/ePL7bxxB9vKkE+2WKEciHhz+4QtbzWiHRVNRJwU3cKhrYDYQE3b0MRFtqfLYdA4A==", "requires": { - "@smithy/querystring-parser": "^4.2.8", - "@smithy/types": "^4.12.0", + "@smithy/querystring-parser": "^4.2.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, @@ -19688,17 +19538,17 @@ } }, "@smithy/util-body-length-browser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", - "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.1.tgz", + "integrity": "sha512-SiJeLiozrAoCrgDBUgsVbmqHmMgg/2bA15AzcbcW+zan7SuyAVHN4xTSbq0GlebAIwlcaX32xacnrG488/J/6g==", "requires": { "tslib": "^2.6.2" } }, "@smithy/util-body-length-node": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", - "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.2.tgz", + "integrity": "sha512-4rHqBvxtJEBvsZcFQSPQqXP2b/yy/YlB66KlcEgcH2WNoOKCKB03DSLzXmOsXjbl8dJ4OEYTn31knhdznwk7zw==", "requires": { "tslib": "^2.6.2" } @@ -19713,87 +19563,87 @@ } }, "@smithy/util-config-provider": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", - "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.1.tgz", + "integrity": "sha512-462id/00U8JWFw6qBuTSWfN5TxOHvDu4WliI97qOIOnuC/g+NDAknTU8eoGXEPlLkRVgWEr03jJBLV4o2FL8+A==", "requires": { "tslib": "^2.6.2" } }, "@smithy/util-defaults-mode-browser": { - "version": "4.3.29", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.29.tgz", - "integrity": "sha512-nIGy3DNRmOjaYaaKcQDzmWsro9uxlaqUOhZDHQed9MW/GmkBZPtnU70Pu1+GT9IBmUXwRdDuiyaeiy9Xtpn3+Q==", + "version": "4.3.36", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.36.tgz", + "integrity": "sha512-R0smq7EHQXRVMxkAxtH5akJ/FvgAmNF6bUy/GwY/N20T4GrwjT633NFm0VuRpC+8Bbv8R9A0DoJ9OiZL/M3xew==", "requires": { - "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.11.2", - "@smithy/types": "^4.12.0", + "@smithy/property-provider": "^4.2.10", + "@smithy/smithy-client": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@smithy/util-defaults-mode-node": { - "version": "4.2.32", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.32.tgz", - "integrity": "sha512-7dtFff6pu5fsjqrVve0YMhrnzJtccCWDacNKOkiZjJ++fmjGExmmSu341x+WU6Oc1IccL7lDuaUj7SfrHpWc5Q==", + "version": "4.2.39", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.39.tgz", + "integrity": "sha512-otWuoDm35btJV1L8MyHrPl462B07QCdMTktKc7/yM+Psv6KbED/ziXiHnmr7yPHUjfIwE9S8Max0LO24Mo3ZVg==", "requires": { - "@smithy/config-resolver": "^4.4.6", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.11.2", - "@smithy/types": "^4.12.0", + "@smithy/config-resolver": "^4.4.9", + "@smithy/credential-provider-imds": "^4.2.10", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/property-provider": "^4.2.10", + "@smithy/smithy-client": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@smithy/util-endpoints": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz", - "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.1.tgz", + "integrity": "sha512-xyctc4klmjmieQiF9I1wssBWleRV0RhJ2DpO8+8yzi2LO1Z+4IWOZNGZGNj4+hq9kdo+nyfrRLmQTzc16Op2Vg==", "requires": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", + "@smithy/node-config-provider": "^4.3.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@smithy/util-hex-encoding": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", - "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.1.tgz", + "integrity": "sha512-c1hHtkgAWmE35/50gmdKajgGAKV3ePJ7t6UtEmpfCWJmQE9BQAQPz0URUVI89eSkcDqCtzqllxzG28IQoZPvwA==", "requires": { "tslib": "^2.6.2" } }, "@smithy/util-middleware": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz", - "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.10.tgz", + "integrity": "sha512-LxaQIWLp4y0r72eA8mwPNQ9va4h5KeLM0I3M/HV9klmFaY2kN766wf5vsTzmaOpNNb7GgXAd9a25P3h8T49PSA==", "requires": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@smithy/util-retry": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.8.tgz", - "integrity": "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.10.tgz", + "integrity": "sha512-HrBzistfpyE5uqTwiyLsFHscgnwB0kgv8vySp7q5kZ0Eltn/tjosaSGGDj/jJ9ys7pWzIP/icE2d+7vMKXLv7A==", "requires": { - "@smithy/service-error-classification": "^4.2.8", - "@smithy/types": "^4.12.0", + "@smithy/service-error-classification": "^4.2.10", + "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "@smithy/util-stream": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.11.tgz", - "integrity": "sha512-lKmZ0S/3Qj2OF5H1+VzvDLb6kRxGzZHq6f3rAsoSu5cTLGsn3v3VQBA8czkNNXlLjoFEtVu3OQT2jEeOtOE2CA==", + "version": "4.5.15", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.15.tgz", + "integrity": "sha512-OlOKnaqnkU9X+6wEkd7mN+WB7orPbCVDauXOj22Q7VtiTkvy7ZdSsOg4QiNAZMgI4OkvNf+/VLUC3VXkxuWJZw==", "requires": { - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.9", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@smithy/fetch-http-handler": "^5.3.11", + "@smithy/node-http-handler": "^4.4.12", + "@smithy/types": "^4.13.0", + "@smithy/util-base64": "^4.3.1", + "@smithy/util-buffer-from": "^4.2.1", + "@smithy/util-hex-encoding": "^4.2.1", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, @@ -19825,9 +19675,9 @@ } }, "@smithy/uuid": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", - "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.1.tgz", + "integrity": "sha512-dSfDCeihDmZlV2oyr0yWPTUfh07suS+R5OB+FZGiv/hHyK3hrFBW5rR1UYjfa57vBsrP9lciFkRPzebaV1Qujw==", "requires": { "tslib": "^2.6.2" } @@ -20058,9 +19908,9 @@ "dev": true }, "@types/node": { - "version": "24.10.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", - "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", + "version": "24.10.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.15.tgz", + "integrity": "sha512-BgjLoRuSr0MTI5wA6gMw9Xy0sFudAaUuvrnjgGx9wZ522fYYLA5SYJ+1Y30vTcJEG+DRCyDHx/gzQVfofYzSdg==", "requires": { "undici-types": "~7.16.0" }, @@ -20815,19 +20665,13 @@ "jiti": "^2.6.1" }, "dependencies": { - "balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true - }, "brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "requires": { - "balanced-match": "^4.0.2" + "balanced-match": "^1.0.0" } }, "glob": { @@ -20845,12 +20689,12 @@ } }, "minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "requires": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" } }, "path-scurry": { @@ -20891,12 +20735,12 @@ "dev": true }, "strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "requires": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" } } } @@ -20917,9 +20761,9 @@ }, "dependencies": { "@types/node": { - "version": "20.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", - "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "version": "20.19.35", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz", + "integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==", "dev": true, "requires": { "undici-types": "~6.21.0" @@ -20937,9 +20781,9 @@ }, "dependencies": { "@types/node": { - "version": "20.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", - "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "version": "20.19.35", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz", + "integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==", "dev": true, "requires": { "undici-types": "~6.21.0" @@ -21077,19 +20921,13 @@ "readable-stream": "^4.0.0" }, "dependencies": { - "balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true - }, "brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "requires": { - "balanced-match": "^4.0.2" + "balanced-match": "^1.0.0" } }, "glob": { @@ -21107,12 +20945,12 @@ } }, "minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "requires": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" } }, "path-scurry": { @@ -22356,9 +22194,9 @@ "dev": true }, "minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -22454,9 +22292,9 @@ } }, "minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -23115,9 +22953,9 @@ } }, "minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -24419,9 +24257,9 @@ } }, "minimatch": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", - "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "requires": { "brace-expansion": "^5.0.2" }, @@ -24432,9 +24270,9 @@ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==" }, "brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "requires": { "balanced-match": "^4.0.2" } @@ -25553,9 +25391,9 @@ } }, "minimatch": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.7.tgz", - "integrity": "sha512-FjiwU9HaHW6YB3H4a1sFudnv93lvydNjz2lmyUXR6IwKhGI+bgL3SOZrBGn6kvvX2pJvhEkGSGjyTHN47O4rqA==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -27271,9 +27109,9 @@ }, "dependencies": { "@types/node": { - "version": "20.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", - "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "version": "20.19.35", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz", + "integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==", "dev": true, "requires": { "undici-types": "~6.21.0" @@ -27321,9 +27159,9 @@ }, "dependencies": { "@types/node": { - "version": "20.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", - "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "version": "20.19.35", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz", + "integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==", "dev": true, "requires": { "undici-types": "~6.21.0" diff --git a/package.json b/package.json index 48f4cca..1f8a534 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.46", + "version": "0.25.47", "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 fb58883..2b8043d 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,27 @@ 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.47 + +27th February, 2026 + +Phew, the financial year is still not over yet, but I have got some time to work on the plug-in again! + +### Fixed and refactored + +- Fixed the inexplicable behaviour when retrieving chunks from the network. + - The chunk manager has been layered to be responsible for its own areas and duties. e.g., `DatabaseWriteLayer`, `DatabaseReadLayer`, `NetworkLayer`, `CacheLayer`, and `ArrivalWaitLayer`. + - All layers have been tested now! + - `LayeredChunkManager` has been implemented to manage these layers. Also tested. + - `EntryManager` has been mostly rewritten and also tested. + +- Now we can configure `Never warn` for remote storage size notification again. + +### Tests + +- The following test has been added: + - `ConflictManager`. + ## 0.25.46 26th February, 2026 @@ -23,7 +44,6 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid - ChangeManager has been refactored. This had a potential issue, so something had been fixed, possibly. - Some tests have been ported from Deno's test runner to Vitest to accumulate coverage. - ## 0.25.45 25th February, 2026 From f3e83d404527f1390e442cc1a3462172baaf8e72 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 2 Mar 2026 09:06:23 +0000 Subject: [PATCH 042/339] Refactored: changed the implementation from using overrides to injecting an adapter. --- src/common/utils.ts | 16 +- src/features/ConfigSync/CmdConfigSync.ts | 6 +- .../HiddenFileSync/CmdHiddenFileSync.ts | 8 +- src/lib | 2 +- src/main.ts | 1 + .../ObsidianStorageEventManagerAdapter.ts | 137 ++++++++++++++ src/managers/StorageEventManagerObsidian.ts | 179 +----------------- .../essential/ModuleInitializerFile.ts | 4 +- src/modules/services/ObsidianPathService.ts | 33 ++++ src/serviceModules/DatabaseFileAccess.ts | 11 +- src/serviceModules/FileAccessObsidian.ts | 166 +--------------- src/serviceModules/FileHandler.ts | 23 +-- .../ObsidianConversionAdapter.ts | 18 ++ .../ObsidianFileSystemAdapter.ts | 64 +++++++ .../FileSystemAdapters/ObsidianPathAdapter.ts | 16 ++ .../ObsidianStorageAdapter.ts | 57 ++++++ .../ObsidianTypeGuardAdapter.ts | 16 ++ .../ObsidianVaultAdapter.ts | 51 +++++ src/serviceModules/ServiceFileAccessImpl.ts | 7 +- 19 files changed, 430 insertions(+), 385 deletions(-) create mode 100644 src/managers/ObsidianStorageEventManagerAdapter.ts create mode 100644 src/serviceModules/FileSystemAdapters/ObsidianConversionAdapter.ts create mode 100644 src/serviceModules/FileSystemAdapters/ObsidianFileSystemAdapter.ts create mode 100644 src/serviceModules/FileSystemAdapters/ObsidianPathAdapter.ts create mode 100644 src/serviceModules/FileSystemAdapters/ObsidianStorageAdapter.ts create mode 100644 src/serviceModules/FileSystemAdapters/ObsidianTypeGuardAdapter.ts create mode 100644 src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts diff --git a/src/common/utils.ts b/src/common/utils.ts index ef1eba1..a64c26e 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -257,20 +257,8 @@ export function requestToCouchDBWithCredentials( import { BASE_IS_NEW, EVEN, TARGET_IS_NEW } from "@lib/common/models/shared.const.symbols.ts"; export { BASE_IS_NEW, EVEN, TARGET_IS_NEW }; // Why 2000? : ZIP FILE Does not have enough resolution. -const resolution = 2000; -export function compareMTime( - baseMTime: number, - targetMTime: number -): typeof BASE_IS_NEW | typeof TARGET_IS_NEW | typeof EVEN { - const truncatedBaseMTime = ~~(baseMTime / resolution) * resolution; - const truncatedTargetMTime = ~~(targetMTime / resolution) * resolution; - // Logger(`Resolution MTime ${truncatedBaseMTime} and ${truncatedTargetMTime} `, LOG_LEVEL_VERBOSE); - if (truncatedBaseMTime == truncatedTargetMTime) return EVEN; - if (truncatedBaseMTime > truncatedTargetMTime) return BASE_IS_NEW; - if (truncatedBaseMTime < truncatedTargetMTime) return TARGET_IS_NEW; - throw new Error("Unexpected error"); -} - +import { compareMTime } from "@lib/common/utils.ts"; +export { compareMTime }; function getKey(file: AnyEntry | string | UXFileInfoStub) { const key = typeof file == "string" ? file : stripAllPrefixes(file.path); return key; diff --git a/src/features/ConfigSync/CmdConfigSync.ts b/src/features/ConfigSync/CmdConfigSync.ts index e51af06..0bc732b 100644 --- a/src/features/ConfigSync/CmdConfigSync.ts +++ b/src/features/ConfigSync/CmdConfigSync.ts @@ -53,9 +53,7 @@ import { PeriodicProcessor, disposeMemoObject, isCustomisationSyncMetadata, - isMarkedAsSameChanges, isPluginMetadata, - markChangesAreSame, memoIfNotExist, memoObject, retrieveMemoObject, @@ -1308,7 +1306,7 @@ export class ConfigSync extends LiveSyncCommands { eden: {}, }; } else { - if (isMarkedAsSameChanges(prefixedFileName, [old.mtime, mtime + 1]) == EVEN) { + if (this.services.path.isMarkedAsSameChanges(prefixedFileName, [old.mtime, mtime + 1]) == EVEN) { this._log( `STORAGE --> DB:${prefixedFileName}: (config) Skipped (Already checked the same)`, LOG_LEVEL_DEBUG @@ -1328,7 +1326,7 @@ export class ConfigSync extends LiveSyncCommands { `STORAGE --> DB:${prefixedFileName}: (config) Skipped (the same content)`, LOG_LEVEL_VERBOSE ); - markChangesAreSame(prefixedFileName, old.mtime, mtime + 1); + this.services.path.markChangesAreSame(prefixedFileName, old.mtime, mtime + 1); return true; } saveData = { diff --git a/src/features/HiddenFileSync/CmdHiddenFileSync.ts b/src/features/HiddenFileSync/CmdHiddenFileSync.ts index 92baa9c..2ada77e 100644 --- a/src/features/HiddenFileSync/CmdHiddenFileSync.ts +++ b/src/features/HiddenFileSync/CmdHiddenFileSync.ts @@ -29,9 +29,7 @@ import { } from "../../lib/src/common/utils.ts"; import { compareMTime, - unmarkChanges, isInternalMetadata, - markChangesAreSame, PeriodicProcessor, TARGET_IS_NEW, scheduleTask, @@ -362,13 +360,13 @@ export class HiddenFileSync extends LiveSyncCommands { const dbMTime = getComparingMTime(db); const storageMTime = getComparingMTime(stat); if (dbMTime == 0 || storageMTime == 0) { - unmarkChanges(path); + this.services.path.unmarkChanges(path); } else { - markChangesAreSame(path, getComparingMTime(db), getComparingMTime(stat)); + this.services.path.markChangesAreSame(path, getComparingMTime(db), getComparingMTime(stat)); } } updateLastProcessedDeletion(path: FilePath, db: MetaEntry | LoadedEntry | false) { - unmarkChanges(path); + this.services.path.unmarkChanges(path); if (db) this.updateLastProcessedDatabase(path, db); this.updateLastProcessedFile(path, this.statToKey(null)); } diff --git a/src/lib b/src/lib index 2ed1925..d2d739a 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 2ed1925ca7e3d7d9d0b497b74a108bbb881b92b7 +Subproject commit d2d739a3abd9d08a3052da953df0dd4e0796acbe diff --git a/src/main.ts b/src/main.ts index ba52092..3470c7a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -336,6 +336,7 @@ export default class ObsidianLiveSyncPlugin vaultService: this.services.vault, settingService: this.services.setting, APIService: this.services.API, + pathService: this.services.path, }); const storageEventManager = new StorageEventManagerObsidian(this, this, { fileProcessing: this.services.fileProcessing, diff --git a/src/managers/ObsidianStorageEventManagerAdapter.ts b/src/managers/ObsidianStorageEventManagerAdapter.ts new file mode 100644 index 0000000..5645ac6 --- /dev/null +++ b/src/managers/ObsidianStorageEventManagerAdapter.ts @@ -0,0 +1,137 @@ +import { TFile, TFolder } from "@/deps"; +import type { FilePath, UXFileInfoStub, UXInternalFileInfoStub } from "@lib/common/types"; +import type { FileEventItem } from "@lib/common/types"; +import type { IStorageEventManagerAdapter } from "@lib/managers/adapters"; +import type { + IStorageEventTypeGuardAdapter, + IStorageEventPersistenceAdapter, + IStorageEventWatchAdapter, + IStorageEventStatusAdapter, + IStorageEventConverterAdapter, + IStorageEventWatchHandlers, +} from "@lib/managers/adapters"; +import type { FileEventItemSentinel } from "@lib/managers/StorageEventManager"; +import type ObsidianLiveSyncPlugin from "@/main"; +import type { LiveSyncCore } from "@/main"; +import type { FileProcessingService } from "@lib/services/base/FileProcessingService"; +import { InternalFileToUXFileInfoStub, TFileToUXFileInfoStub } from "@/modules/coreObsidian/storageLib/utilObsidian"; + +/** + * Obsidian-specific type guard adapter + */ +class ObsidianTypeGuardAdapter implements IStorageEventTypeGuardAdapter { + isFile(file: any): file is TFile { + if (file instanceof TFile) { + return true; + } + if (file && typeof file === "object" && "isFolder" in file) { + return !file.isFolder; + } + return false; + } + + isFolder(item: any): item is TFolder { + if (item instanceof TFolder) { + return true; + } + if (item && typeof item === "object" && "isFolder" in item) { + return !!item.isFolder; + } + return false; + } +} + +/** + * Obsidian-specific persistence adapter + */ +class ObsidianPersistenceAdapter implements IStorageEventPersistenceAdapter { + constructor(private core: LiveSyncCore) {} + + async saveSnapshot(snapshot: (FileEventItem | FileEventItemSentinel)[]): Promise { + await this.core.kvDB.set("storage-event-manager-snapshot", snapshot); + } + + async loadSnapshot(): Promise<(FileEventItem | FileEventItemSentinel)[] | null> { + const snapShot = await this.core.kvDB.get<(FileEventItem | FileEventItemSentinel)[]>( + "storage-event-manager-snapshot" + ); + return snapShot; + } +} + +/** + * Obsidian-specific status adapter + */ +class ObsidianStatusAdapter implements IStorageEventStatusAdapter { + constructor(private fileProcessing: FileProcessingService) {} + + updateStatus(status: { batched: number; processing: number; totalQueued: number }): void { + this.fileProcessing.batched.value = status.batched; + this.fileProcessing.processing.value = status.processing; + this.fileProcessing.totalQueued.value = status.totalQueued; + } +} + +/** + * Obsidian-specific converter adapter + */ +class ObsidianConverterAdapter implements IStorageEventConverterAdapter { + toFileInfo(file: TFile, deleted?: boolean): UXFileInfoStub { + return TFileToUXFileInfoStub(file, deleted); + } + + toInternalFileInfo(path: FilePath): UXInternalFileInfoStub { + return InternalFileToUXFileInfoStub(path); + } +} + +/** + * Obsidian-specific watch adapter + */ +class ObsidianWatchAdapter implements IStorageEventWatchAdapter { + constructor(private plugin: ObsidianLiveSyncPlugin) {} + + beginWatch(handlers: IStorageEventWatchHandlers): Promise { + const plugin = this.plugin; + + const boundHandlers = { + onCreate: handlers.onCreate.bind(handlers), + onChange: handlers.onChange.bind(handlers), + onDelete: handlers.onDelete.bind(handlers), + onRename: handlers.onRename.bind(handlers), + onRaw: handlers.onRaw.bind(handlers), + onEditorChange: handlers.onEditorChange?.bind(handlers), + }; + + plugin.registerEvent(plugin.app.vault.on("create", boundHandlers.onCreate)); + plugin.registerEvent(plugin.app.vault.on("modify", boundHandlers.onChange)); + plugin.registerEvent(plugin.app.vault.on("delete", boundHandlers.onDelete)); + plugin.registerEvent(plugin.app.vault.on("rename", boundHandlers.onRename)); + //@ts-ignore : Internal API + plugin.registerEvent(plugin.app.vault.on("raw", boundHandlers.onRaw)); + if (boundHandlers.onEditorChange) { + plugin.registerEvent(plugin.app.workspace.on("editor-change", boundHandlers.onEditorChange)); + } + + return Promise.resolve(); + } +} + +/** + * Composite adapter for Obsidian StorageEventManager + */ +export class ObsidianStorageEventManagerAdapter implements IStorageEventManagerAdapter { + readonly typeGuard: ObsidianTypeGuardAdapter; + readonly persistence: ObsidianPersistenceAdapter; + readonly watch: ObsidianWatchAdapter; + readonly status: ObsidianStatusAdapter; + readonly converter: ObsidianConverterAdapter; + + constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore, fileProcessing: FileProcessingService) { + this.typeGuard = new ObsidianTypeGuardAdapter(); + this.persistence = new ObsidianPersistenceAdapter(core); + this.watch = new ObsidianWatchAdapter(plugin); + this.status = new ObsidianStatusAdapter(fileProcessing); + this.converter = new ObsidianConverterAdapter(); + } +} diff --git a/src/managers/StorageEventManagerObsidian.ts b/src/managers/StorageEventManagerObsidian.ts index b48081b..3f5fdf7 100644 --- a/src/managers/StorageEventManagerObsidian.ts +++ b/src/managers/StorageEventManagerObsidian.ts @@ -1,168 +1,32 @@ -import type { FileEventItem } from "@/common/types"; import { HiddenFileSync } from "@/features/HiddenFileSync/CmdHiddenFileSync"; -import type { FilePath, UXFileInfoStub, UXFolderInfo, UXInternalFileInfoStub } from "@lib/common/types"; -import type { FileEvent } from "@lib/interfaces/StorageEventManager"; -import { TFile, type TAbstractFile, TFolder } from "@/deps"; -import { LOG_LEVEL_DEBUG } from "octagonal-wheels/common/logger"; +import type { FilePath } from "@lib/common/types"; import type ObsidianLiveSyncPlugin from "@/main"; import type { LiveSyncCore } from "@/main"; import { StorageEventManagerBase, - type FileEventItemSentinel, type StorageEventManagerBaseDependencies, } from "@lib/managers/StorageEventManager"; -import { InternalFileToUXFileInfoStub, TFileToUXFileInfoStub } from "@/modules/coreObsidian/storageLib/utilObsidian"; +import { ObsidianStorageEventManagerAdapter } from "./ObsidianStorageEventManagerAdapter"; -export class StorageEventManagerObsidian extends StorageEventManagerBase { +export class StorageEventManagerObsidian extends StorageEventManagerBase { plugin: ObsidianLiveSyncPlugin; core: LiveSyncCore; // Necessary evil. cmdHiddenFileSync: HiddenFileSync; - override isFile(file: UXFileInfoStub | UXInternalFileInfoStub | UXFolderInfo | TFile): boolean { - if (file instanceof TFile) { - return true; - } - if (super.isFile(file)) { - return true; - } - return !file.isFolder; - } - override isFolder(file: UXFileInfoStub | UXInternalFileInfoStub | UXFolderInfo | TFolder): boolean { - if (file instanceof TFolder) { - return true; - } - if (super.isFolder(file)) { - return true; - } - return !!file.isFolder; - } - constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore, dependencies: StorageEventManagerBaseDependencies) { - super(dependencies); + const adapter = new ObsidianStorageEventManagerAdapter(plugin, core, dependencies.fileProcessing); + super(adapter, dependencies); this.plugin = plugin; this.core = core; this.cmdHiddenFileSync = this.plugin.getAddOn(HiddenFileSync.name) as HiddenFileSync; } - async beginWatch() { - await this.snapShotRestored; - const plugin = this.plugin; - this.watchVaultChange = this.watchVaultChange.bind(this); - this.watchVaultCreate = this.watchVaultCreate.bind(this); - this.watchVaultDelete = this.watchVaultDelete.bind(this); - this.watchVaultRename = this.watchVaultRename.bind(this); - this.watchVaultRawEvents = this.watchVaultRawEvents.bind(this); - this.watchEditorChange = this.watchEditorChange.bind(this); - plugin.registerEvent(plugin.app.vault.on("modify", this.watchVaultChange)); - plugin.registerEvent(plugin.app.vault.on("delete", this.watchVaultDelete)); - plugin.registerEvent(plugin.app.vault.on("rename", this.watchVaultRename)); - plugin.registerEvent(plugin.app.vault.on("create", this.watchVaultCreate)); - //@ts-ignore : Internal API - plugin.registerEvent(plugin.app.vault.on("raw", this.watchVaultRawEvents)); - plugin.registerEvent(plugin.app.workspace.on("editor-change", this.watchEditorChange)); - } - watchEditorChange(editor: any, info: any) { - if (!("path" in info)) { - return; - } - if (!this.shouldBatchSave) { - return; - } - const file = info?.file as TFile; - if (!file) return; - if (this.storageAccess.isFileProcessing(file.path as FilePath)) { - // this._log(`Editor change skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE); - return; - } - if (!this.isWaiting(file.path as FilePath)) { - return; - } - const data = info?.data as string; - const fi: FileEvent = { - type: "CHANGED", - file: TFileToUXFileInfoStub(file), - cachedData: data, - }; - void this.appendQueue([fi]); - } - - watchVaultCreate(file: TAbstractFile, ctx?: any) { - if (file instanceof TFolder) return; - if (this.storageAccess.isFileProcessing(file.path as FilePath)) { - // this._log(`File create skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE); - return; - } - const fileInfo = TFileToUXFileInfoStub(file); - void this.appendQueue([{ type: "CREATE", file: fileInfo }], ctx); - } - - watchVaultChange(file: TAbstractFile, ctx?: any) { - if (file instanceof TFolder) return; - if (this.storageAccess.isFileProcessing(file.path as FilePath)) { - // this._log(`File change skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE); - return; - } - const fileInfo = TFileToUXFileInfoStub(file); - void this.appendQueue([{ type: "CHANGED", file: fileInfo }], ctx); - } - - watchVaultDelete(file: TAbstractFile, ctx?: any) { - if (file instanceof TFolder) return; - if (this.storageAccess.isFileProcessing(file.path as FilePath)) { - // this._log(`File delete skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE); - return; - } - const fileInfo = TFileToUXFileInfoStub(file, true); - void this.appendQueue([{ type: "DELETE", file: fileInfo }], ctx); - } - watchVaultRename(file: TAbstractFile, oldFile: string, ctx?: any) { - // vault Rename will not be raised for self-events (Self-hosted LiveSync will not handle 'rename'). - if (file instanceof TFile) { - const fileInfo = TFileToUXFileInfoStub(file); - void this.appendQueue( - [ - { - type: "DELETE", - file: { - path: oldFile as FilePath, - name: file.name, - stat: { - mtime: file.stat.mtime, - ctime: file.stat.ctime, - size: file.stat.size, - type: "file", - }, - deleted: true, - }, - skipBatchWait: true, - }, - { type: "CREATE", file: fileInfo, skipBatchWait: true }, - ], - ctx - ); - } - } - // Watch raw events (Internal API) - watchVaultRawEvents(path: FilePath) { - if (this.storageAccess.isFileProcessing(path)) { - // this._log(`Raw file event skipped because the file is being processed: ${path}`, LOG_LEVEL_VERBOSE); - return; - } - // Only for internal files. - if (!this.settings) return; - // if (this.plugin.settings.useIgnoreFiles && this.plugin.ignoreFiles.some(e => path.endsWith(e.trim()))) { - if (this.settings.useIgnoreFiles) { - // If it is one of ignore files, refresh the cached one. - // (Calling$$isTargetFile will refresh the cache) - void this.vaultService.isTargetFile(path).then(() => this._watchVaultRawEvents(path)); - } else { - void this._watchVaultRawEvents(path); - } - } - - async _watchVaultRawEvents(path: FilePath) { + /** + * Override _watchVaultRawEvents to add Obsidian-specific logic + */ + protected override async _watchVaultRawEvents(path: FilePath) { if (!this.settings.syncInternalFiles && !this.settings.usePluginSync) return; if (!this.settings.watchInternalFileChanges) return; if (!path.startsWith(this.plugin.app.vault.configDir)) return; @@ -177,34 +41,11 @@ export class StorageEventManagerObsidian extends StorageEventManagerBase { [ { type: "INTERNAL", - file: InternalFileToUXFileInfoStub(path), + file: this.adapter.converter.toInternalFileInfo(path), skipBatchWait: true, // Internal files should be processed immediately. }, ], null ); } - - async _saveSnapshot(snapshot: (FileEventItem | FileEventItemSentinel)[]) { - await this.core.kvDB.set("storage-event-manager-snapshot", snapshot); - this._log(`Storage operation snapshot saved: ${snapshot.length} items`, LOG_LEVEL_DEBUG); - } - - async _loadSnapshot() { - const snapShot = await this.core.kvDB.get<(FileEventItem | FileEventItemSentinel)[]>( - "storage-event-manager-snapshot" - ); - return snapShot; - } - - updateStatus() { - const allFileEventItems = this.bufferedQueuedItems.filter((e): e is FileEventItem => "args" in e); - const allItems = allFileEventItems.filter((e) => !e.cancelled); - const totalItems = allItems.length + this.concurrentProcessing.waiting; - const processing = this.processingCount; - const batchedCount = this._waitingMap.size; - this.fileProcessing.batched.value = batchedCount; - this.fileProcessing.processing.value = processing; - this.fileProcessing.totalQueued.value = totalItems + batchedCount + processing; - } } diff --git a/src/modules/essential/ModuleInitializerFile.ts b/src/modules/essential/ModuleInitializerFile.ts index 8112bfe..c4e047b 100644 --- a/src/modules/essential/ModuleInitializerFile.ts +++ b/src/modules/essential/ModuleInitializerFile.ts @@ -1,7 +1,7 @@ import { unique } from "octagonal-wheels/collection"; import { throttle } from "octagonal-wheels/function"; import { EVENT_ON_UNRESOLVED_ERROR, eventHub } from "../../common/events.ts"; -import { BASE_IS_NEW, compareFileFreshness, EVEN, isValidPath, TARGET_IS_NEW } from "../../common/utils.ts"; +import { BASE_IS_NEW, EVEN, isValidPath, TARGET_IS_NEW } from "../../common/utils.ts"; import { type FilePathWithPrefixLC, type FilePathWithPrefix, @@ -308,7 +308,7 @@ export class ModuleInitializerFile extends AbstractModule { } } - const compareResult = compareFileFreshness(file, doc); + const compareResult = this.services.path.compareFileFreshness(file, doc); switch (compareResult) { case BASE_IS_NEW: if (!this.services.vault.isFileSizeTooLarge(file.stat.size)) { diff --git a/src/modules/services/ObsidianPathService.ts b/src/modules/services/ObsidianPathService.ts index 72e5532..82b5c85 100644 --- a/src/modules/services/ObsidianPathService.ts +++ b/src/modules/services/ObsidianPathService.ts @@ -1,7 +1,40 @@ import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext"; import { normalizePath } from "@/deps"; import { PathService } from "@/lib/src/services/base/PathService"; + +import { + type BASE_IS_NEW, + type TARGET_IS_NEW, + type EVEN, + markChangesAreSame, + unmarkChanges, + compareFileFreshness, + isMarkedAsSameChanges, +} from "@/common/utils"; +import type { UXFileInfo, AnyEntry, UXFileInfoStub, FilePathWithPrefix } from "@/lib/src/common/types"; export class ObsidianPathService extends PathService { + override markChangesAreSame( + old: UXFileInfo | AnyEntry | FilePathWithPrefix, + newMtime: number, + oldMtime: number + ): boolean | undefined { + return markChangesAreSame(old, newMtime, oldMtime); + } + override unmarkChanges(file: AnyEntry | FilePathWithPrefix | UXFileInfoStub): void { + return unmarkChanges(file); + } + override compareFileFreshness( + baseFile: UXFileInfoStub | AnyEntry | undefined, + checkTarget: UXFileInfo | AnyEntry | undefined + ): typeof BASE_IS_NEW | typeof TARGET_IS_NEW | typeof EVEN { + return compareFileFreshness(baseFile, checkTarget); + } + override isMarkedAsSameChanges( + file: UXFileInfoStub | AnyEntry | FilePathWithPrefix, + mtimes: number[] + ): undefined | typeof EVEN { + return isMarkedAsSameChanges(file, mtimes); + } protected normalizePath(path: string): string { return normalizePath(path); } diff --git a/src/serviceModules/DatabaseFileAccess.ts b/src/serviceModules/DatabaseFileAccess.ts index 51ffb87..645888e 100644 --- a/src/serviceModules/DatabaseFileAccess.ts +++ b/src/serviceModules/DatabaseFileAccess.ts @@ -1,15 +1,8 @@ -import { markChangesAreSame } from "@/common/utils"; -import type { AnyEntry } from "@lib/common/types"; - import type { DatabaseFileAccess } from "@lib/interfaces/DatabaseFileAccess.ts"; import { ServiceDatabaseFileAccessBase } from "@lib/serviceModules/ServiceDatabaseFileAccessBase"; // markChangesAreSame uses persistent data implicitly, we should refactor it too. // For now, to make the refactoring done once, we just use them directly. // Hence it is not on /src/lib/src/serviceModules. (markChangesAreSame is using indexedDB). -// TODO: REFACTOR -export class ServiceDatabaseFileAccess extends ServiceDatabaseFileAccessBase implements DatabaseFileAccess { - markChangesAreSame(old: AnyEntry, newMtime: number, oldMtime: number): void { - markChangesAreSame(old, newMtime, oldMtime); - } -} +// Refactored, now migrating... +export class ServiceDatabaseFileAccess extends ServiceDatabaseFileAccessBase implements DatabaseFileAccess {} diff --git a/src/serviceModules/FileAccessObsidian.ts b/src/serviceModules/FileAccessObsidian.ts index 823fd16..5b318cc 100644 --- a/src/serviceModules/FileAccessObsidian.ts +++ b/src/serviceModules/FileAccessObsidian.ts @@ -1,160 +1,14 @@ -import { markChangesAreSame } from "@/common/utils"; -import type { FilePath, UXDataWriteOptions, UXFileInfoStub, UXFolderInfo } from "@lib/common/types"; - -import { TFolder, type TAbstractFile, TFile, type Stat, type App, type DataWriteOptions, normalizePath } from "@/deps"; -import { FileAccessBase, toArrayBuffer, type FileAccessBaseDependencies } from "@lib/serviceModules/FileAccessBase.ts"; -import { TFileToUXFileInfoStub } from "@/modules/coreObsidian/storageLib/utilObsidian"; - -declare module "obsidian" { - interface Vault { - getAbstractFileByPathInsensitive(path: string): TAbstractFile | null; - } - interface DataAdapter { - reconcileInternalFile?(path: string): Promise; - } -} - -export class FileAccessObsidian extends FileAccessBase { - app: App; - - override getPath(file: string | TAbstractFile): FilePath { - return (typeof file === "string" ? file : file.path) as FilePath; - } - - override isFile(file: TAbstractFile | null): file is TFile { - return file instanceof TFile; - } - override isFolder(file: TAbstractFile | null): file is TFolder { - return file instanceof TFolder; - } - override _statFromNative(file: TFile): Promise { - return Promise.resolve(file.stat); - } - - override nativeFileToUXFileInfoStub(file: TFile): UXFileInfoStub { - return TFileToUXFileInfoStub(file); - } - override nativeFolderToUXFolder(folder: TFolder): UXFolderInfo { - if (folder instanceof TFolder) { - return this.nativeFolderToUXFolder(folder); - } else { - throw new Error(`Not a folder: ${(folder as TAbstractFile)?.name}`); - } - } +import { type App } from "@/deps"; +import { FileAccessBase, type FileAccessBaseDependencies } from "@lib/serviceModules/FileAccessBase.ts"; +import { ObsidianFileSystemAdapter } from "./FileSystemAdapters/ObsidianFileSystemAdapter"; +/** + * Obsidian-specific implementation of FileAccessBase + * Uses ObsidianFileSystemAdapter for platform-specific operations + */ +export class FileAccessObsidian extends FileAccessBase { constructor(app: App, dependencies: FileAccessBaseDependencies) { - super({ - storageAccessManager: dependencies.storageAccessManager, - vaultService: dependencies.vaultService, - settingService: dependencies.settingService, - APIService: dependencies.APIService, - }); - this.app = app; - } - - protected override _normalisePath(path: string): string { - return normalizePath(path); - } - - protected async _adapterMkdir(path: string) { - await this.app.vault.adapter.mkdir(path); - } - protected _getAbstractFileByPath(path: FilePath) { - return this.app.vault.getAbstractFileByPath(path); - } - protected _getAbstractFileByPathInsensitive(path: FilePath) { - return this.app.vault.getAbstractFileByPathInsensitive(path); - } - - protected async _tryAdapterStat(path: FilePath) { - if (!(await this.app.vault.adapter.exists(path))) return null; - return await this.app.vault.adapter.stat(path); - } - - protected async _adapterStat(path: FilePath) { - return await this.app.vault.adapter.stat(path); - } - - protected async _adapterExists(path: FilePath) { - return await this.app.vault.adapter.exists(path); - } - protected async _adapterRemove(path: FilePath) { - await this.app.vault.adapter.remove(path); - } - - protected async _adapterRead(path: FilePath) { - return await this.app.vault.adapter.read(path); - } - - protected async _adapterReadBinary(path: FilePath) { - return await this.app.vault.adapter.readBinary(path); - } - - _adapterWrite(file: string, data: string, options?: UXDataWriteOptions): Promise { - return this.app.vault.adapter.write(file, data, options); - } - _adapterWriteBinary(file: string, data: ArrayBuffer, options?: UXDataWriteOptions): Promise { - return this.app.vault.adapter.writeBinary(file, toArrayBuffer(data), options); - } - - protected _adapterList(basePath: string): Promise<{ files: string[]; folders: string[] }> { - return Promise.resolve(this.app.vault.adapter.list(basePath)); - } - - async _vaultCacheRead(file: TFile) { - return await this.app.vault.cachedRead(file); - } - - protected async _vaultRead(file: TFile): Promise { - return await this.app.vault.read(file); - } - - protected async _vaultReadBinary(file: TFile): Promise { - return await this.app.vault.readBinary(file); - } - - protected override markChangesAreSame(path: string, mtime: number, newMtime: number) { - return markChangesAreSame(path, mtime, newMtime); - } - - protected override async _vaultModify(file: TFile, data: string, options?: UXDataWriteOptions): Promise { - return await this.app.vault.modify(file, data, options); - } - protected override async _vaultModifyBinary( - file: TFile, - data: ArrayBuffer, - options?: UXDataWriteOptions - ): Promise { - return await this.app.vault.modifyBinary(file, toArrayBuffer(data), options); - } - protected override async _vaultCreate(path: string, data: string, options?: UXDataWriteOptions): Promise { - return await this.app.vault.create(path, data, options); - } - protected override async _vaultCreateBinary( - path: string, - data: ArrayBuffer, - options?: UXDataWriteOptions - ): Promise { - return await this.app.vault.createBinary(path, toArrayBuffer(data), options); - } - - protected override _trigger(name: string, ...data: any[]) { - return this.app.vault.trigger(name, ...data); - } - protected override async _reconcileInternalFile(path: string) { - return await Promise.resolve(this.app.vault.adapter.reconcileInternalFile?.(path)); - } - protected override async _adapterAppend(normalizedPath: string, data: string, options?: DataWriteOptions) { - return await this.app.vault.adapter.append(normalizedPath, data, options); - } - protected override async _delete(file: TFile | TFolder, force = false) { - return await this.app.vault.delete(file, force); - } - protected override async _trash(file: TFile | TFolder, force = false) { - return await this.app.vault.trash(file, force); - } - - protected override _getFiles() { - return this.app.vault.getFiles(); + const adapter = new ObsidianFileSystemAdapter(app); + super(adapter, dependencies); } } diff --git a/src/serviceModules/FileHandler.ts b/src/serviceModules/FileHandler.ts index d5cbff6..c47cc33 100644 --- a/src/serviceModules/FileHandler.ts +++ b/src/serviceModules/FileHandler.ts @@ -1,26 +1,7 @@ -import { - compareFileFreshness, - markChangesAreSame, - type BASE_IS_NEW, - type EVEN, - type TARGET_IS_NEW, -} from "@/common/utils"; -import type { AnyEntry } from "@lib/common/models/db.type"; -import type { UXFileInfo, UXFileInfoStub } from "@lib/common/models/fileaccess.type"; import { ServiceFileHandlerBase } from "@lib/serviceModules/ServiceFileHandlerBase"; // markChangesAreSame uses persistent data implicitly, we should refactor it too. // also, compareFileFreshness depends on marked changes, so we should refactor it as well. For now, to make the refactoring done once, we just use them directly. // Hence it is not on /src/lib/src/serviceModules. (markChangesAreSame is using indexedDB). -// TODO: REFACTOR -export class ServiceFileHandler extends ServiceFileHandlerBase { - override markChangesAreSame(old: UXFileInfo | AnyEntry, newMtime: number, oldMtime: number) { - return markChangesAreSame(old, newMtime, oldMtime); - } - override compareFileFreshness( - baseFile: UXFileInfoStub | AnyEntry | undefined, - checkTarget: UXFileInfo | AnyEntry | undefined - ): typeof TARGET_IS_NEW | typeof BASE_IS_NEW | typeof EVEN { - return compareFileFreshness(baseFile, checkTarget); - } -} +// Refactored: markChangesAreSame, unmarkChanges, compareFileFreshness, isMarkedAsSameChanges are now moved to PathService +export class ServiceFileHandler extends ServiceFileHandlerBase {} diff --git a/src/serviceModules/FileSystemAdapters/ObsidianConversionAdapter.ts b/src/serviceModules/FileSystemAdapters/ObsidianConversionAdapter.ts new file mode 100644 index 0000000..82e6f65 --- /dev/null +++ b/src/serviceModules/FileSystemAdapters/ObsidianConversionAdapter.ts @@ -0,0 +1,18 @@ +import type { UXFileInfoStub, UXFolderInfo } from "@/lib/src/common/types"; +import type { IConversionAdapter } from "@/lib/src/serviceModules/adapters"; +import { TFileToUXFileInfoStub, TFolderToUXFileInfoStub } from "@/modules/coreObsidian/storageLib/utilObsidian"; +import type { TFile, TFolder } from "obsidian"; + +/** + * Conversion adapter implementation for Obsidian + */ + +export class ObsidianConversionAdapter implements IConversionAdapter { + nativeFileToUXFileInfoStub(file: TFile): UXFileInfoStub { + return TFileToUXFileInfoStub(file); + } + + nativeFolderToUXFolder(folder: TFolder): UXFolderInfo { + return TFolderToUXFileInfoStub(folder); + } +} diff --git a/src/serviceModules/FileSystemAdapters/ObsidianFileSystemAdapter.ts b/src/serviceModules/FileSystemAdapters/ObsidianFileSystemAdapter.ts new file mode 100644 index 0000000..d0c261e --- /dev/null +++ b/src/serviceModules/FileSystemAdapters/ObsidianFileSystemAdapter.ts @@ -0,0 +1,64 @@ +import type { FilePath, UXStat } from "@/lib/src/common/types"; +import type { + IFileSystemAdapter, + IPathAdapter, + ITypeGuardAdapter, + IConversionAdapter, + IStorageAdapter, + IVaultAdapter, +} from "@/lib/src/serviceModules/adapters"; +import type { TAbstractFile, TFile, TFolder, Stat, App } from "obsidian"; +import { ObsidianConversionAdapter } from "./ObsidianConversionAdapter"; +import { ObsidianPathAdapter } from "./ObsidianPathAdapter"; +import { ObsidianStorageAdapter } from "./ObsidianStorageAdapter"; +import { ObsidianTypeGuardAdapter } from "./ObsidianTypeGuardAdapter"; +import { ObsidianVaultAdapter } from "./ObsidianVaultAdapter"; + +declare module "obsidian" { + interface Vault { + getAbstractFileByPathInsensitive(path: string): TAbstractFile | null; + } + interface DataAdapter { + reconcileInternalFile?(path: string): Promise; + } +} + +/** + * Complete file system adapter implementation for Obsidian + */ + +export class ObsidianFileSystemAdapter implements IFileSystemAdapter { + readonly path: IPathAdapter; + readonly typeGuard: ITypeGuardAdapter; + readonly conversion: IConversionAdapter; + readonly storage: IStorageAdapter; + readonly vault: IVaultAdapter; + + constructor(private app: App) { + this.path = new ObsidianPathAdapter(); + this.typeGuard = new ObsidianTypeGuardAdapter(); + this.conversion = new ObsidianConversionAdapter(); + this.storage = new ObsidianStorageAdapter(app); + this.vault = new ObsidianVaultAdapter(app); + } + + getAbstractFileByPath(path: FilePath | string): TAbstractFile | null { + return this.app.vault.getAbstractFileByPath(path); + } + + getAbstractFileByPathInsensitive(path: FilePath | string): TAbstractFile | null { + return this.app.vault.getAbstractFileByPathInsensitive(path); + } + + getFiles(): TFile[] { + return this.app.vault.getFiles(); + } + + statFromNative(file: TFile): Promise { + return Promise.resolve({ ...file.stat, type: "file" }); + } + + async reconcileInternalFile(path: string): Promise { + return await Promise.resolve(this.app.vault.adapter.reconcileInternalFile?.(path)); + } +} diff --git a/src/serviceModules/FileSystemAdapters/ObsidianPathAdapter.ts b/src/serviceModules/FileSystemAdapters/ObsidianPathAdapter.ts new file mode 100644 index 0000000..a21ced1 --- /dev/null +++ b/src/serviceModules/FileSystemAdapters/ObsidianPathAdapter.ts @@ -0,0 +1,16 @@ +import { type TAbstractFile, normalizePath } from "@/deps"; +import type { FilePath } from "@lib/common/types"; +import type { IPathAdapter } from "@lib/serviceModules/adapters"; + +/** + * Path adapter implementation for Obsidian + */ +export class ObsidianPathAdapter implements IPathAdapter { + getPath(file: string | TAbstractFile): FilePath { + return (typeof file === "string" ? file : file.path) as FilePath; + } + + normalisePath(path: string): string { + return normalizePath(path); + } +} diff --git a/src/serviceModules/FileSystemAdapters/ObsidianStorageAdapter.ts b/src/serviceModules/FileSystemAdapters/ObsidianStorageAdapter.ts new file mode 100644 index 0000000..fe4818e --- /dev/null +++ b/src/serviceModules/FileSystemAdapters/ObsidianStorageAdapter.ts @@ -0,0 +1,57 @@ +import type { UXDataWriteOptions } from "@/lib/src/common/types"; +import type { IStorageAdapter } from "@/lib/src/serviceModules/adapters"; +import { toArrayBuffer } from "@/lib/src/serviceModules/FileAccessBase"; +import type { Stat, App } from "obsidian"; + +/** + * Storage adapter implementation for Obsidian + */ + +export class ObsidianStorageAdapter implements IStorageAdapter { + constructor(private app: App) {} + + async exists(path: string): Promise { + return await this.app.vault.adapter.exists(path); + } + + async trystat(path: string): Promise { + if (!(await this.app.vault.adapter.exists(path))) return null; + return await this.app.vault.adapter.stat(path); + } + + async stat(path: string): Promise { + return await this.app.vault.adapter.stat(path); + } + + async mkdir(path: string): Promise { + await this.app.vault.adapter.mkdir(path); + } + + async remove(path: string): Promise { + await this.app.vault.adapter.remove(path); + } + + async read(path: string): Promise { + return await this.app.vault.adapter.read(path); + } + + async readBinary(path: string): Promise { + return await this.app.vault.adapter.readBinary(path); + } + + async write(path: string, data: string, options?: UXDataWriteOptions): Promise { + return await this.app.vault.adapter.write(path, data, options); + } + + async writeBinary(path: string, data: ArrayBuffer, options?: UXDataWriteOptions): Promise { + return await this.app.vault.adapter.writeBinary(path, toArrayBuffer(data), options); + } + + async append(path: string, data: string, options?: UXDataWriteOptions): Promise { + return await this.app.vault.adapter.append(path, data, options); + } + + list(basePath: string): Promise<{ files: string[]; folders: string[] }> { + return Promise.resolve(this.app.vault.adapter.list(basePath)); + } +} diff --git a/src/serviceModules/FileSystemAdapters/ObsidianTypeGuardAdapter.ts b/src/serviceModules/FileSystemAdapters/ObsidianTypeGuardAdapter.ts new file mode 100644 index 0000000..455affb --- /dev/null +++ b/src/serviceModules/FileSystemAdapters/ObsidianTypeGuardAdapter.ts @@ -0,0 +1,16 @@ +import type { ITypeGuardAdapter } from "@/lib/src/serviceModules/adapters"; +import { TFile, TFolder } from "obsidian"; + +/** + * Type guard adapter implementation for Obsidian + */ + +export class ObsidianTypeGuardAdapter implements ITypeGuardAdapter { + isFile(file: any): file is TFile { + return file instanceof TFile; + } + + isFolder(item: any): item is TFolder { + return item instanceof TFolder; + } +} diff --git a/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts b/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts new file mode 100644 index 0000000..a7d996c --- /dev/null +++ b/src/serviceModules/FileSystemAdapters/ObsidianVaultAdapter.ts @@ -0,0 +1,51 @@ +import type { UXDataWriteOptions } from "@/lib/src/common/types"; +import type { IVaultAdapter } from "@/lib/src/serviceModules/adapters"; +import { toArrayBuffer } from "@/lib/src/serviceModules/FileAccessBase"; +import type { TFile, App, TFolder } from "obsidian"; + +/** + * Vault adapter implementation for Obsidian + */ +export class ObsidianVaultAdapter implements IVaultAdapter { + constructor(private app: App) {} + + async read(file: TFile): Promise { + return await this.app.vault.read(file); + } + + async cachedRead(file: TFile): Promise { + return await this.app.vault.cachedRead(file); + } + + async readBinary(file: TFile): Promise { + return await this.app.vault.readBinary(file); + } + + async modify(file: TFile, data: string, options?: UXDataWriteOptions): Promise { + return await this.app.vault.modify(file, data, options); + } + + async modifyBinary(file: TFile, data: ArrayBuffer, options?: UXDataWriteOptions): Promise { + return await this.app.vault.modifyBinary(file, toArrayBuffer(data), options); + } + + async create(path: string, data: string, options?: UXDataWriteOptions): Promise { + return await this.app.vault.create(path, data, options); + } + + async createBinary(path: string, data: ArrayBuffer, options?: UXDataWriteOptions): Promise { + return await this.app.vault.createBinary(path, toArrayBuffer(data), options); + } + + async delete(file: TFile | TFolder, force = false): Promise { + return await this.app.vault.delete(file, force); + } + + async trash(file: TFile | TFolder, force = false): Promise { + return await this.app.vault.trash(file, force); + } + + trigger(name: string, ...data: any[]): any { + return this.app.vault.trigger(name, ...data); + } +} diff --git a/src/serviceModules/ServiceFileAccessImpl.ts b/src/serviceModules/ServiceFileAccessImpl.ts index 686dd0b..204855e 100644 --- a/src/serviceModules/ServiceFileAccessImpl.ts +++ b/src/serviceModules/ServiceFileAccessImpl.ts @@ -1,6 +1,5 @@ -import type { TAbstractFile, TFile, TFolder, Stat } from "@/deps"; - import { ServiceFileAccessBase } from "@lib/serviceModules/ServiceFileAccessBase"; +import type { ObsidianFileSystemAdapter } from "./FileSystemAdapters/ObsidianFileSystemAdapter"; -// For typechecking purpose -export class ServiceFileAccessObsidian extends ServiceFileAccessBase {} +// For now, this is just a re-export of ServiceFileAccess with the Obsidian-specific adapter type. +export class ServiceFileAccessObsidian extends ServiceFileAccessBase {} From 7419d0d2a1fa69f606a74d882866133050a4a197 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 2 Mar 2026 09:15:29 +0000 Subject: [PATCH 043/339] Add unit-ci hook to push --- .github/workflows/unit-ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/unit-ci.yml b/.github/workflows/unit-ci.yml index ed7ea76..506ec9a 100644 --- a/.github/workflows/unit-ci.yml +++ b/.github/workflows/unit-ci.yml @@ -3,6 +3,10 @@ name: unit-ci on: workflow_dispatch: + push: + branches: + - main + - beta permissions: contents: read From 4cbb833e9d6937dc51ef92ca24de2c7365a7c112 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 2 Mar 2026 09:36:43 +0000 Subject: [PATCH 044/339] bump --- manifest.json | 2 +- package-lock.json | 4 +-- package.json | 2 +- updates.md | 79 +++++++---------------------------------------- updates_old.md | 53 +++++++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 71 deletions(-) diff --git a/manifest.json b/manifest.json index 4649f12..b5ac92c 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.47", + "version": "0.25.48", "minAppVersion": "0.9.12", "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 713ec8e..39e713b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.47", + "version": "0.25.48", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.47", + "version": "0.25.48", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 1f8a534..6a66e60 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.47", + "version": "0.25.48", "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 2b8043d..366863b 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. +## 0.25.48 + +2nd March, 2026 + +No behavioural changes except unidentified faults. Please report if you find any unexpected behaviour after this update. + +### Refactored + +- Many storage-related functions have been refactored for better maintainability and testability. + - Now all platform-specific logics are supplied as adapters, and the core logic has become platform-agnostic. + - Quite a number of tests have been added for the core logic, and the platform-specific logics are also tested with mocked adapters. + ## 0.25.47 27th February, 2026 @@ -205,72 +217,5 @@ However, this is not a minor refactoring, so please be careful. Let me know if y - Fixed an issue where indexedDB would not close correctly on some environments, causing unexpected errors during database operations. -## 0.25.37 - -15th January, 2026 - -Thank you for your patience until my return! - -This release contains minor changes discovered and fixed during test implementation. -There are no changes affecting usage. - -### Refactored - -- Logging system has been slightly refactored to improve maintainability. -- Some import statements have been unified. - -## 0.25.36 - -25th December, 2025 - -### Improved - -- Now the garbage collector (V3) has been implemented. (Beta) - - This garbage collector ensures that all devices are synchronised to the latest progress to prevent inconsistencies. - - In other words, it makes sure that no new conflicts would have arisen. - - This feature requires additional information (via node information), but it should be more reliable. - - This feature requires all devices have v0.25.36 or later. - - After the garbage collector runs, the database size may be reduced (Compaction will be run automatically after GC). - - We should have an administrative privilege on the remote database to run this garbage collector. -- Now the plug-in and device information is stored in the remote database. - - This information is used for the garbage collector (V3). - - Some additional features may be added in the future using this information. - -## 0.25.35 - -24th December, 2025 - -Sorry for a small release! I would like to keep things moving along like this if possible. After all, the holidays seem to be starting soon. I will be doubled by my business until the 27th though, indeed. - -### Fixed - -- Now the conflict resolution dialogue shows correctly which device only has older APIs (#764). - -## 0.25.34 - -10th December, 2025 - -### Behaviour change - -- The plug-in automatically fetches the missing chunks even if `Fetch chunks on demand` is disabled. - - This change is to avoid loss of data when receiving a bulk of revisions. - - This can be prevented by enabling `Use Only Local Chunks` in the settings. -- Storage application now saved during each event and restored on startup. -- Synchronisation result application is also now saved during each event and restored on startup. - - These may avoid some unexpected loss of data when the editor crashes. - -### Fixed - -- Now the plug-in waits for the application of pended batch changes before the synchronisation starts. - - This may avoid some unexpected loss or unexpected conflicts. - Plug-in sends custom headers correctly when RequestAPI is used. -- No longer causing unexpected chunk creation during `Reset synchronisation on This Device` with bucket sync. - -### Refactored - -- Synchronisation result application process has been refactored. -- Storage application process has been refactored. - - Please report if you find any unexpected behaviour after this update. A bit of large refactoring. - Full notes are in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). diff --git a/updates_old.md b/updates_old.md index 19fe78d..0485f78 100644 --- a/updates_old.md +++ b/updates_old.md @@ -3,6 +3,59 @@ 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.48 + +2nd March, 2026 + +No behavioural changes except unidentified faults. Please report if you find any unexpected behaviour after this update. + +### Refactored + +- Many storage-related functions have been refactored for better maintainability and testability. + - Now all platform-specific logics are supplied as adapters, and the core logic has become platform-agnostic. + - Quite a number of tests have been added for the core logic, and the platform-specific logics are also tested with mocked adapters. + +## 0.25.47 + +27th February, 2026 + +Phew, the financial year is still not over yet, but I have got some time to work on the plug-in again! + +### Fixed and refactored + +- Fixed the inexplicable behaviour when retrieving chunks from the network. + - The chunk manager has been layered to be responsible for its own areas and duties. e.g., `DatabaseWriteLayer`, `DatabaseReadLayer`, `NetworkLayer`, `CacheLayer`, and `ArrivalWaitLayer`. + - All layers have been tested now! + - `LayeredChunkManager` has been implemented to manage these layers. Also tested. + - `EntryManager` has been mostly rewritten and also tested. + +- Now we can configure `Never warn` for remote storage size notification again. + +### Tests + +- The following test has been added: + - `ConflictManager`. + +## 0.25.46 + +26th February, 2026 + +### Fixed + +- Unexpected errors no longer occurred when the plug-in was unloaded. +- Hidden File Sync now respects selectors. +- Registering protocol-handlers now works safely without causing unexpected errors. + +### Refactored + +- `ModuleCheckRemoteSize` has been ported to a serviceFeature, and tests have also been added. +- Some unnecessary things have been removed. +- LiveSyncManagers has now explicit dependencies. +- LiveSyncLocalDB is now responsible for LiveSyncManagers, not accepting the managers as dependencies. + - This is to avoid circular dependencies and clarify the ownership of the managers. +- ChangeManager has been refactored. This had a potential issue, so something had been fixed, possibly. +- Some tests have been ported from Deno's test runner to Vitest to accumulate coverage. + ## 0.25.45 25th February, 2026 From 09115dfe15e0666d50557dfc923e85ecb259e4aa Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 2 Mar 2026 10:52:14 +0000 Subject: [PATCH 045/339] Fixed: Styles on the diff-dialogue has been qualified. --- styles.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/styles.css b/styles.css index 1a51817..72e7ade 100644 --- a/styles.css +++ b/styles.css @@ -1,13 +1,13 @@ -.added { +.op-scrollable .added { color: var(--text-on-accent); background-color: var(--text-accent); } -.normal { +.op-scrollable .normal { color: var(--text-normal); } -.deleted { +.op-scrollable .deleted { color: var(--text-on-accent); background-color: var(--text-muted); } From cf9d2720cec2030e82de0c3362e62e5a154557dd Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Tue, 3 Mar 2026 13:19:22 +0000 Subject: [PATCH 046/339] ### Fixed - No longer deleted files are not clickable in the Global History pane. - Diff view now uses more specific classes (#803). - A message of configuration mismatching slightly added for better understanding. - Now it says `When replication is initiated manually via the command palette or ribbon, a dialogue box will open to address this.` to make it clear that the user can fix the issue by themselves. ### Refactored - `ModuleRedFlag` has been refactored to `serviceFeatures/redFlag` and also tested. - `ModuleInitializerFile` has been refactored to `lib/serviceFeatures/offlineScanner` and also tested. --- .../HiddenFileCommon/JsonResolvePane.svelte | 2 +- src/lib | 2 +- src/main.ts | 12 +- src/managers/StorageEventManagerObsidian.ts | 5 +- ...e.ts => ModuleInitializerFile_obsolete.ts} | 4 +- .../GlobalHistory/GlobalHistory.svelte | 6 +- .../ConflictResolveModal.ts | 2 + src/modules/services/ObsidianVaultService.ts | 5 +- src/serviceFeatures/redFlag.ts | 387 ++++++ src/serviceFeatures/redFlag.unit.spec.ts | 1140 +++++++++++++++++ styles.css | 6 +- 11 files changed, 1554 insertions(+), 17 deletions(-) rename src/modules/essential/{ModuleInitializerFile.ts => ModuleInitializerFile_obsolete.ts} (99%) create mode 100644 src/serviceFeatures/redFlag.ts create mode 100644 src/serviceFeatures/redFlag.unit.spec.ts diff --git a/src/features/HiddenFileCommon/JsonResolvePane.svelte b/src/features/HiddenFileCommon/JsonResolvePane.svelte index 034b02f..bb1b22b 100644 --- a/src/features/HiddenFileCommon/JsonResolvePane.svelte +++ b/src/features/HiddenFileCommon/JsonResolvePane.svelte @@ -143,7 +143,7 @@ {#if selectedObj != false} -
+
{#each diffs as diff} {diff[1]} feature(this); this.services.appLifecycle.onLayoutReady.addHandler(curriedFeature); } + useRedFlagFeatures(this); + useOfflineScanner(this); // enable target filter feature. useTargetFilters(this); useCheckRemoteSize(this); diff --git a/src/managers/StorageEventManagerObsidian.ts b/src/managers/StorageEventManagerObsidian.ts index 3f5fdf7..979ccdc 100644 --- a/src/managers/StorageEventManagerObsidian.ts +++ b/src/managers/StorageEventManagerObsidian.ts @@ -2,10 +2,7 @@ import { HiddenFileSync } from "@/features/HiddenFileSync/CmdHiddenFileSync"; import type { FilePath } from "@lib/common/types"; import type ObsidianLiveSyncPlugin from "@/main"; import type { LiveSyncCore } from "@/main"; -import { - StorageEventManagerBase, - type StorageEventManagerBaseDependencies, -} from "@lib/managers/StorageEventManager"; +import { StorageEventManagerBase, type StorageEventManagerBaseDependencies } from "@lib/managers/StorageEventManager"; import { ObsidianStorageEventManagerAdapter } from "./ObsidianStorageEventManagerAdapter"; export class StorageEventManagerObsidian extends StorageEventManagerBase { diff --git a/src/modules/essential/ModuleInitializerFile.ts b/src/modules/essential/ModuleInitializerFile_obsolete.ts similarity index 99% rename from src/modules/essential/ModuleInitializerFile.ts rename to src/modules/essential/ModuleInitializerFile_obsolete.ts index c4e047b..b122b94 100644 --- a/src/modules/essential/ModuleInitializerFile.ts +++ b/src/modules/essential/ModuleInitializerFile_obsolete.ts @@ -423,7 +423,7 @@ export class ModuleInitializerFile extends AbstractModule { } override onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void { services.appLifecycle.getUnresolvedMessages.addHandler(this._reportDetectedErrors.bind(this)); - services.databaseEvents.initialiseDatabase.setHandler(this._initializeDatabase.bind(this)); - services.vault.scanVault.setHandler(this._performFullScan.bind(this)); + services.databaseEvents.initialiseDatabase.addHandler(this._initializeDatabase.bind(this)); + services.vault.scanVault.addHandler(this._performFullScan.bind(this)); } } diff --git a/src/modules/features/GlobalHistory/GlobalHistory.svelte b/src/modules/features/GlobalHistory/GlobalHistory.svelte index 5e815e0..a30177b 100644 --- a/src/modules/features/GlobalHistory/GlobalHistory.svelte +++ b/src/modules/features/GlobalHistory/GlobalHistory.svelte @@ -250,7 +250,11 @@ - openFile(entry.path)}>{entry.filename} + {#if entry.isDeleted} + {entry.filename} + {:else} + openFile(entry.path)}>{entry.filename} + {/if}
diff --git a/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts b/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts index 826fbec..ad308e5 100644 --- a/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts +++ b/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts @@ -63,6 +63,7 @@ export class ConflictResolveModal extends Modal { contentEl.createEl("span", { text: this.filename }); const div = contentEl.createDiv(""); div.addClass("op-scrollable"); + div.addClass("ls-dialog"); let diff = ""; for (const v of this.result.diff) { const x1 = v[0]; @@ -86,6 +87,7 @@ export class ConflictResolveModal extends Modal { } const div2 = contentEl.createDiv(""); + div2.addClass("ls-dialog"); const date1 = new Date(this.result.left.mtime).toLocaleString() + (this.result.left.deleted ? " (Deleted)" : ""); const date2 = diff --git a/src/modules/services/ObsidianVaultService.ts b/src/modules/services/ObsidianVaultService.ts index 4229e00..480c561 100644 --- a/src/modules/services/ObsidianVaultService.ts +++ b/src/modules/services/ObsidianVaultService.ts @@ -1,4 +1,4 @@ -import { getPathFromTFile } from "@/common/utils"; +import { getPathFromTFile, isValidPath } from "@/common/utils"; import { InjectableVaultService } from "@/lib/src/services/implements/injectable/InjectableVaultService"; import type { ObsidianServiceContext } from "@/lib/src/services/implements/obsidian/ObsidianServiceContext"; import type { FilePath } from "@/lib/src/common/types"; @@ -30,4 +30,7 @@ export class ObsidianVaultService extends InjectableVaultService Promise; + handle: () => Promise; +} + +export async function isFlagFileExist(host: NecessaryServices, path: string) { + const redFlagExist = await host.serviceModules.storageAccess.isExists( + host.serviceModules.storageAccess.normalisePath(path) + ); + if (redFlagExist) { + return true; + } + return false; +} + +export async function deleteFlagFile(host: NecessaryServices, log: LogFunction, path: string) { + try { + const isFlagged = await host.serviceModules.storageAccess.isExists( + host.serviceModules.storageAccess.normalisePath(path) + ); + if (isFlagged) { + await host.serviceModules.storageAccess.delete(path, true); + } + } catch (ex) { + log(`Could not delete ${path}`); + log(ex, LOG_LEVEL_VERBOSE); + } +} +/** + * Factory function to create a fetch all flag handler. + * All logic related to fetch all flag is encapsulated here. + */ +export function createFetchAllFlagHandler( + host: NecessaryServices< + "vault" | "fileProcessing" | "tweakValue" | "UI" | "setting" | "appLifecycle", + "storageAccess" | "rebuilder" + >, + log: LogFunction +): FlagFileHandler { + // Check if fetch all flag is active + const isFlagActive = async () => + (await isFlagFileExist(host, FlagFilesOriginal.FETCH_ALL)) || + (await isFlagFileExist(host, FlagFilesHumanReadable.FETCH_ALL)); + + // Cleanup fetch all flag files + const cleanupFlag = async () => { + await deleteFlagFile(host, log, FlagFilesOriginal.FETCH_ALL); + await deleteFlagFile(host, log, FlagFilesHumanReadable.FETCH_ALL); + }; + + // Handle the fetch all scheduled operation + const onScheduled = async () => { + const method = await host.services.UI.dialogManager.openWithExplicitCancel(FetchEverything); + if (method === "cancelled") { + log("Fetch everything cancelled by user.", LOG_LEVEL_NOTICE); + await cleanupFlag(); + host.services.appLifecycle.performRestart(); + return false; + } + const { vault, extra } = method; + const settings = await host.services.setting.currentSettings(); + // If remote is MinIO, makeLocalChunkBeforeSync is not available. (because no-deduplication on sending). + const makeLocalChunkBeforeSyncAvailable = settings.remoteType !== REMOTE_MINIO; + const mapVaultStateToAction = { + identical: { + makeLocalChunkBeforeSync: makeLocalChunkBeforeSyncAvailable, + makeLocalFilesBeforeSync: false, + }, + independent: { + makeLocalChunkBeforeSync: false, + makeLocalFilesBeforeSync: false, + }, + unbalanced: { + makeLocalChunkBeforeSync: false, + makeLocalFilesBeforeSync: true, + }, + cancelled: { + makeLocalChunkBeforeSync: false, + makeLocalFilesBeforeSync: false, + }, + } as const; + + return await processVaultInitialisation(host, log, async () => { + const settings = host.services.setting.currentSettings(); + await adjustSettingToRemoteIfNeeded(host, log, extra, settings); + const vaultStateToAction = mapVaultStateToAction[vault]; + const { makeLocalChunkBeforeSync, makeLocalFilesBeforeSync } = vaultStateToAction; + log( + `Fetching everything with settings: makeLocalChunkBeforeSync=${makeLocalChunkBeforeSync}, makeLocalFilesBeforeSync=${makeLocalFilesBeforeSync}`, + LOG_LEVEL_INFO + ); + await host.serviceModules.rebuilder.$fetchLocal(makeLocalChunkBeforeSync, !makeLocalFilesBeforeSync); + await cleanupFlag(); + log("Fetch everything operation completed. Vault files will be gradually synced.", LOG_LEVEL_NOTICE); + return true; + }); + }; + + return { + priority: 10, + check: () => isFlagActive(), + handle: async () => { + const res = await onScheduled(); + if (res) { + return await verifyAndUnlockSuspension(host, log); + } + return false; + }, + }; +} + +/** + * Adjust setting to remote configuration. + * @param config current configuration to retrieve remote preferred config + * @returns updated configuration if applied, otherwise null. + */ +export async function adjustSettingToRemote( + host: NecessaryServices<"tweakValue" | "UI" | "setting", any>, + log: LogFunction, + config: ObsidianLiveSyncSettings +) { + // Fetch remote configuration unless prevented. + const SKIP_FETCH = "Skip and proceed"; + const RETRY_FETCH = "Retry (recommended)"; + let canProceed = false; + do { + const remoteTweaks = await host.services.tweakValue.fetchRemotePreferred(config); + if (!remoteTweaks) { + const choice = await host.services.UI.confirm.askSelectStringDialogue( + "Could not fetch configuration from remote. If you are new to the Self-hosted LiveSync, this might be expected. If not, you should check your network or server settings.", + [SKIP_FETCH, RETRY_FETCH] as const, + { + defaultAction: RETRY_FETCH, + timeout: 0, + title: "Fetch Remote Configuration Failed", + } + ); + if (choice === SKIP_FETCH) { + canProceed = true; + } + } else { + const necessary = extractObject(TweakValuesShouldMatchedTemplate, remoteTweaks); + // Check if any necessary tweak value is different from current config. + const differentItems = Object.entries(necessary).filter(([key, value]) => { + return (config as any)[key] !== value; + }); + if (differentItems.length === 0) { + log("Remote configuration matches local configuration. No changes applied.", LOG_LEVEL_NOTICE); + } else { + await host.services.UI.confirm.askSelectStringDialogue( + "Your settings differed slightly from the server's. The plug-in has supplemented the incompatible parts with the server settings!", + ["OK"] as const, + { + defaultAction: "OK", + timeout: 0, + } + ); + } + + config = { + ...config, + ...Object.fromEntries(differentItems), + } satisfies ObsidianLiveSyncSettings; + await host.services.setting.applyPartial(config, true); + log("Remote configuration applied.", LOG_LEVEL_NOTICE); + canProceed = true; + const updatedConfig = host.services.setting.currentSettings(); + return updatedConfig; + } + } while (!canProceed); +} + +/** + * Adjust setting to remote if needed. + * @param extra result of dialogues that may contain preventFetchingConfig flag (e.g, from FetchEverything or RebuildEverything) + * @param config current configuration to retrieve remote preferred config + */ +export async function adjustSettingToRemoteIfNeeded( + host: NecessaryServices<"tweakValue" | "UI" | "setting", any>, + log: LogFunction, + extra: { preventFetchingConfig: boolean }, + config: ObsidianLiveSyncSettings +) { + if (extra && extra.preventFetchingConfig) { + return; + } + + // Remote configuration fetched and applied. + if (await adjustSettingToRemote(host, log, config)) { + config = host.services.setting.currentSettings(); + } else { + log("Remote configuration not applied.", LOG_LEVEL_NOTICE); + } + log(JSON.stringify(config), LOG_LEVEL_VERBOSE); +} + +/** + * Process vault initialisation with suspending file watching and sync. + * @param proc process to be executed during initialisation, should return true if can be continued, false if app is unable to continue the process. + * @param keepSuspending whether to keep suspending file watching after the process. + * @returns result of the process, or false if error occurs. + */ +export async function processVaultInitialisation( + host: NecessaryServices<"setting", any>, + log: LogFunction, + proc: () => Promise, + keepSuspending = false +) { + try { + // Disable batch saving and file watching during initialisation. + await host.services.setting.applyPartial({ batchSave: false }, false); + await host.services.setting.suspendAllSync(); + await host.services.setting.suspendExtraSync(); + await host.services.setting.applyPartial({ suspendFileWatching: true }, true); + try { + const result = await proc(); + return result; + } catch (ex) { + log("Error during vault initialisation process.", LOG_LEVEL_NOTICE); + log(ex, LOG_LEVEL_VERBOSE); + return false; + } + } catch (ex) { + log("Error during vault initialisation.", LOG_LEVEL_NOTICE); + log(ex, LOG_LEVEL_VERBOSE); + return false; + } finally { + if (!keepSuspending) { + // Re-enable file watching after initialisation. + await host.services.setting.applyPartial({ suspendFileWatching: false }, true); + } + } +} + +export async function verifyAndUnlockSuspension( + host: NecessaryServices<"setting" | "appLifecycle" | "UI", any>, + log: LogFunction +) { + if (!host.services.setting.currentSettings().suspendFileWatching) { + return true; + } + if ( + (await host.services.UI.confirm.askYesNoDialog( + "Do you want to resume file and database processing, and restart obsidian now?", + { defaultOption: "Yes", timeout: 15 } + )) != "yes" + ) { + // TODO: Confirm actually proceed to next process. + return true; + } + await host.services.setting.applyPartial({ suspendFileWatching: false }, true); + host.services.appLifecycle.performRestart(); + return false; +} + +/** + * Factory function to create a rebuild flag handler. + * All logic related to rebuild flag is encapsulated here. + */ +export function createRebuildFlagHandler( + host: NecessaryServices<"setting" | "appLifecycle" | "UI" | "tweakValue", "storageAccess" | "rebuilder">, + log: LogFunction +) { + // Check if rebuild flag is active + const isFlagActive = async () => + (await isFlagFileExist(host, FlagFilesOriginal.REBUILD_ALL)) || + (await isFlagFileExist(host, FlagFilesHumanReadable.REBUILD_ALL)); + + // Cleanup rebuild flag files + const cleanupFlag = async () => { + await deleteFlagFile(host, log, FlagFilesOriginal.REBUILD_ALL); + await deleteFlagFile(host, log, FlagFilesHumanReadable.REBUILD_ALL); + }; + + // Handle the rebuild everything scheduled operation + const onScheduled = async () => { + const method = await host.services.UI.dialogManager.openWithExplicitCancel(RebuildEverything); + if (method === "cancelled") { + log("Rebuild everything cancelled by user.", LOG_LEVEL_NOTICE); + await cleanupFlag(); + host.services.appLifecycle.performRestart(); + return false; + } + const { extra } = method; + const settings = host.services.setting.currentSettings(); + await adjustSettingToRemoteIfNeeded(host, log, extra, settings); + return await processVaultInitialisation(host, log, async () => { + await host.serviceModules.rebuilder.$rebuildEverything(); + await cleanupFlag(); + log("Rebuild everything operation completed.", LOG_LEVEL_NOTICE); + return true; + }); + }; + + return { + priority: 20, + check: () => isFlagActive(), + handle: async () => { + const res = await onScheduled(); + if (res) { + return await verifyAndUnlockSuspension(host, log); + } + return false; + }, + }; +} + +/** + * Factory function to create a suspend all flag handler. + * All logic related to suspend flag is encapsulated here. + */ +export function createSuspendFlagHandler( + host: NecessaryServices<"setting", "storageAccess">, + log: LogFunction +): FlagFileHandler { + // Check if suspend flag is active + const isFlagActive = async () => await isFlagFileExist(host, FlagFilesOriginal.SUSPEND_ALL); + + // Handle the suspend all scheduled operation + const onScheduled = async () => { + log("SCRAM is detected. All operations are suspended.", LOG_LEVEL_NOTICE); + return await processVaultInitialisation( + host, + log, + async () => { + log( + "All operations are suspended as per SCRAM.\nLogs will be written to the file. This might be a performance impact.", + LOG_LEVEL_NOTICE + ); + await host.services.setting.applyPartial({ writeLogToTheFile: true }, true); + return Promise.resolve(false); + }, + true + ); + }; + + return { + priority: 5, + check: () => isFlagActive(), + handle: () => onScheduled(), + }; +} + +export function flagHandlerToEventHandler(flagHandler: FlagFileHandler) { + return async () => { + if (await flagHandler.check()) { + return await flagHandler.handle(); + } + return true; + }; +} + +export function useRedFlagFeatures( + host: NecessaryServices< + "API" | "appLifecycle" | "UI" | "setting" | "tweakValue" | "fileProcessing" | "vault", + "storageAccess" | "rebuilder" + > +) { + const log = createInstanceLogFunction("SF:RedFlag", host.services.API); + const handlerFetch = createFetchAllFlagHandler(host, log); + const handlerRebuild = createRebuildFlagHandler(host, log); + const handlerSuspend = createSuspendFlagHandler(host, log); + host.services.appLifecycle.onLayoutReady.addHandler(flagHandlerToEventHandler(handlerFetch), handlerFetch.priority); + host.services.appLifecycle.onLayoutReady.addHandler( + flagHandlerToEventHandler(handlerRebuild), + handlerRebuild.priority + ); + host.services.appLifecycle.onLayoutReady.addHandler( + flagHandlerToEventHandler(handlerSuspend), + handlerSuspend.priority + ); +} diff --git a/src/serviceFeatures/redFlag.unit.spec.ts b/src/serviceFeatures/redFlag.unit.spec.ts new file mode 100644 index 0000000..65fcef4 --- /dev/null +++ b/src/serviceFeatures/redFlag.unit.spec.ts @@ -0,0 +1,1140 @@ +import { describe, it, expect, vi } from "vitest"; +import type { LogFunction } from "@lib/services/lib/logUtils"; +import { FlagFilesHumanReadable, FlagFilesOriginal } from "@lib/common/models/redflag.const"; +import { REMOTE_MINIO } from "@lib/common/models/setting.const"; +import { + createFetchAllFlagHandler, + createRebuildFlagHandler, + createSuspendFlagHandler, + isFlagFileExist, + deleteFlagFile, + adjustSettingToRemote, + adjustSettingToRemoteIfNeeded, + processVaultInitialisation, + verifyAndUnlockSuspension, + flagHandlerToEventHandler, +} from "./redFlag"; +import { + TweakValuesRecommendedTemplate, + TweakValuesShouldMatchedTemplate, + TweakValuesTemplate, +} from "@/lib/src/common/types"; + +// Mock types and functions +const createLoggerMock = (): LogFunction => { + return vi.fn(); +}; + +const createStorageAccessMock = () => { + const files: Set = new Set(); + return { + files, + isExists: vi.fn((path: string) => Promise.resolve(files.has(path))), + normalisePath: vi.fn((path: string) => path), + delete: vi.fn((path: string, _recursive?: boolean) => { + files.delete(path); + return Promise.resolve(); + }), + getFileNames: vi.fn(() => Array.from(files)), + }; +}; + +const createSettingServiceMock = () => { + const settings: any = { + batchSave: true, + suspendFileWatching: false, + writeLogToTheFile: false, + remoteType: "CouchDB", + }; + return { + settings, + currentSettings: vi.fn(() => settings), + applyPartial: vi.fn((partial: any, _feedback?: boolean) => { + Object.assign(settings, partial); + return Promise.resolve(); + }), + suspendAllSync: vi.fn(() => Promise.resolve()), + suspendExtraSync: vi.fn(() => Promise.resolve()), + }; +}; + +const createAppLifecycleMock = () => { + return { + performRestart: vi.fn(), + onLayoutReady: { + addHandler: vi.fn(), + }, + }; +}; + +const createUIServiceMock = () => { + return { + dialogManager: { + openWithExplicitCancel: vi.fn(), + }, + confirm: { + askSelectStringDialogue: vi.fn(), + askYesNoDialog: vi.fn(), + }, + }; +}; + +const createRebuilderMock = () => { + return { + $fetchLocal: vi.fn(async () => {}), + $rebuildEverything: vi.fn(async () => {}), + }; +}; + +const createTweakValueMock = () => { + return { + fetchRemotePreferred: vi.fn(() => Promise.resolve(null)), + }; +}; + +const createHostMock = () => { + const storageAccessMock = createStorageAccessMock(); + const settingMock = createSettingServiceMock(); + const appLifecycleMock = createAppLifecycleMock(); + const uiMock = createUIServiceMock(); + const rebuilderMock = createRebuilderMock(); + const tweakValueMock = createTweakValueMock(); + + return { + services: { + setting: settingMock, + appLifecycle: appLifecycleMock, + UI: uiMock, + tweakValue: tweakValueMock, + }, + serviceModules: { + storageAccess: storageAccessMock, + rebuilder: rebuilderMock, + }, + mocks: { + storageAccess: storageAccessMock, + setting: settingMock, + appLifecycle: appLifecycleMock, + ui: uiMock, + rebuilder: rebuilderMock, + tweakValue: tweakValueMock, + }, + }; +}; + +describe("Red Flag Feature", () => { + describe("isFlagFileExist", () => { + it("should return true if flag file exists", async () => { + const host = createHostMock(); + + host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL); + + const result = await isFlagFileExist(host as any, FlagFilesOriginal.FETCH_ALL); + expect(result).toBe(true); + }); + + it("should return false if flag file does not exist", async () => { + const host = createHostMock(); + + const result = await isFlagFileExist(host as any, FlagFilesOriginal.FETCH_ALL); + expect(result).toBe(false); + }); + }); + + describe("deleteFlagFile", () => { + it("should delete flag file if it exists", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL); + + await deleteFlagFile(host as any, log, FlagFilesOriginal.FETCH_ALL); + + const exists = await host.mocks.storageAccess.isExists( + host.mocks.storageAccess.normalisePath(FlagFilesOriginal.FETCH_ALL) + ); + expect(exists).toBe(false); + }); + + it("should not throw error if file does not exist", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + await expect(deleteFlagFile(host as any, log, FlagFilesOriginal.FETCH_ALL)).resolves.not.toThrow(); + }); + + it("should log error if deletion fails", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + host.mocks.storageAccess.delete.mockRejectedValueOnce(new Error("Delete failed")); + host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL); + + await deleteFlagFile(host as any, log, FlagFilesOriginal.FETCH_ALL); + + expect(log).toHaveBeenCalled(); + }); + }); + + describe("FlagFile Handler Priority", () => { + it("should handle suspend flag with priority 5", () => { + const host = createHostMock(); + const log = createLoggerMock(); + + const handler = createSuspendFlagHandler(host as any, log); + expect(handler.priority).toBe(5); + expect(typeof handler.check).toBe("function"); + expect(typeof handler.handle).toBe("function"); + }); + + it("should handle fetch all flag with priority 10", () => { + const host = createHostMock(); + const log = createLoggerMock(); + + const handler = createFetchAllFlagHandler(host as any, log); + expect(handler.priority).toBe(10); + expect(typeof handler.check).toBe("function"); + expect(typeof handler.handle).toBe("function"); + }); + + it("should handle rebuild all flag with priority 20", () => { + const host = createHostMock(); + const log = createLoggerMock(); + + const handler = createRebuildFlagHandler(host as any, log); + expect(handler.priority).toBe(20); + expect(typeof handler.check).toBe("function"); + expect(typeof handler.handle).toBe("function"); + }); + }); + + describe("Setting adjustment during vault initialisation", () => { + it("should suspend file watching during initialisation", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + expect(host.mocks.setting.currentSettings().suspendFileWatching).toBe(false); + + const result = await processVaultInitialisation(host as any, log, () => { + expect(host.mocks.setting.currentSettings().suspendFileWatching).toBe(true); + return Promise.resolve(true); + }); + + expect(result).toBe(true); + }); + + it("should disable batch save during initialisation", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + expect(host.mocks.setting.currentSettings().batchSave).toBe(true); + + const result = await processVaultInitialisation(host as any, log, () => { + expect(host.mocks.setting.currentSettings().batchSave).toBe(false); + return Promise.resolve(true); + }); + + expect(result).toBe(true); + }); + + it("should suspend all sync operations", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + await processVaultInitialisation(host as any, log, () => { + return Promise.resolve(true); + }); + + expect(host.mocks.setting.suspendAllSync).toHaveBeenCalled(); + expect(host.mocks.setting.suspendExtraSync).toHaveBeenCalled(); + }); + + it("should resume file watching after initialisation completes", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + await processVaultInitialisation( + host as any, + log, + () => { + return Promise.resolve(true); + }, + false + ); + + expect(host.mocks.setting.currentSettings().suspendFileWatching).toBe(false); + }); + + it("should keep suspending when keepSuspending is true", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + await processVaultInitialisation( + host as any, + log, + () => { + return Promise.resolve(true); + }, + true + ); + + expect(host.mocks.setting.currentSettings().suspendFileWatching).toBe(true); + }); + + it("should return false when process fails", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + const result = await processVaultInitialisation( + host as any, + log, + () => { + throw new Error("Process failed"); + }, + false + ); + + expect(result).toBe(false); + expect(log).toHaveBeenCalled(); + }); + }); + + describe("Suspend Flag Handler", () => { + it("should write logs to file when suspend flag is detected", () => { + const host = createHostMock(); + const log = createLoggerMock(); + + const handler = createSuspendFlagHandler(host as any, log); + + expect(handler.priority).toBe(5); + expect(typeof handler.check).toBe("function"); + expect(typeof handler.handle).toBe("function"); + }); + + it("should keep suspending after initialisation when suspend flag is active", async () => { + const host = createHostMock(); + + const handler = createSuspendFlagHandler(host as any, createLoggerMock()); + + const checkResult = await handler.check(); + expect(typeof checkResult).toBe("boolean"); + }); + + it("should apply writeLogToTheFile setting during suspension", async () => { + const host = createHostMock(); + + host.mocks.storageAccess.files.add(FlagFilesOriginal.SUSPEND_ALL); + + const handler = createSuspendFlagHandler(host as any, createLoggerMock()); + const checkResult = await handler.check(); + + expect(checkResult).toBe(true); + }); + }); + + describe("Fetch All Flag Handler", () => { + it("should detect fetch all flag using original filename", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL); + + const handler = createFetchAllFlagHandler(host as any, log); + const exists = await handler.check(); + + expect(exists).toBe(true); + }); + + it("should detect fetch all flag using human-readable filename", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + host.mocks.storageAccess.files.add(FlagFilesHumanReadable.FETCH_ALL); + + const handler = createFetchAllFlagHandler(host as any, log); + const exists = await handler.check(); + + expect(exists).toBe(true); + }); + + it("should clean up both original and human-readable fetch all flag files", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL); + host.mocks.storageAccess.files.add(FlagFilesHumanReadable.FETCH_ALL); + + await deleteFlagFile(host as any, log, FlagFilesOriginal.FETCH_ALL); + await deleteFlagFile(host as any, log, FlagFilesHumanReadable.FETCH_ALL); + + const originalExists = await host.mocks.storageAccess.isExists( + host.mocks.storageAccess.normalisePath(FlagFilesOriginal.FETCH_ALL) + ); + const humanExists = await host.mocks.storageAccess.isExists( + host.mocks.storageAccess.normalisePath(FlagFilesHumanReadable.FETCH_ALL) + ); + + expect(originalExists).toBe(false); + expect(humanExists).toBe(false); + }); + + it("should have priority 10", () => { + const host = createHostMock(); + const log = createLoggerMock(); + + const handler = createFetchAllFlagHandler(host as any, log); + expect(handler.priority).toBe(10); + }); + }); + + describe("Rebuild All Flag Handler", () => { + it("should detect rebuild all flag using original filename", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + host.mocks.storageAccess.files.add(FlagFilesOriginal.REBUILD_ALL); + + const handler = createRebuildFlagHandler(host as any, log); + const exists = await handler.check(); + + expect(exists).toBe(true); + }); + + it("should detect rebuild all flag using human-readable filename", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + host.mocks.storageAccess.files.add(FlagFilesHumanReadable.REBUILD_ALL); + + const handler = createRebuildFlagHandler(host as any, log); + const exists = await handler.check(); + + expect(exists).toBe(true); + }); + + it("should clean up both original and human-readable rebuild all flag files", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + host.mocks.storageAccess.files.add(FlagFilesOriginal.REBUILD_ALL); + host.mocks.storageAccess.files.add(FlagFilesHumanReadable.REBUILD_ALL); + + await deleteFlagFile(host as any, log, FlagFilesOriginal.REBUILD_ALL); + await deleteFlagFile(host as any, log, FlagFilesHumanReadable.REBUILD_ALL); + + const originalExists = await host.mocks.storageAccess.isExists( + host.mocks.storageAccess.normalisePath(FlagFilesOriginal.REBUILD_ALL) + ); + const humanExists = await host.mocks.storageAccess.isExists( + host.mocks.storageAccess.normalisePath(FlagFilesHumanReadable.REBUILD_ALL) + ); + + expect(originalExists).toBe(false); + expect(humanExists).toBe(false); + }); + + it("should have priority 20", () => { + const host = createHostMock(); + const log = createLoggerMock(); + + const handler = createRebuildFlagHandler(host as any, log); + expect(handler.priority).toBe(20); + }); + }); + + describe("Flag file cleanup on error", () => { + it("should handle errors during flag file deletion gracefully", async () => { + const host = createHostMock(); + + // Simulate error in delete operation + host.mocks.storageAccess.delete.mockRejectedValueOnce(new Error("Delete failed")); + + try { + await host.mocks.storageAccess.delete(FlagFilesOriginal.FETCH_ALL, true); + } catch { + // Error handled + } + + expect(host.mocks.storageAccess.delete).toHaveBeenCalled(); + }); + }); + + describe("Integration: Handler registration on layout ready", () => { + it("should register handlers with correct priorities", () => { + const host = createHostMock(); + + expect(host.services.appLifecycle.onLayoutReady.addHandler).toBeDefined(); + expect(typeof host.services.appLifecycle.onLayoutReady.addHandler).toBe("function"); + }); + }); + + describe("Dialog interaction scenarios", () => { + it("should handle fetch all dialog cancellation", async () => { + const host = createHostMock(); + + // Simulate user clicking cancel + host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce("cancelled"); + + // The dialog manager would return cancelled + const result = await host.mocks.ui.dialogManager.openWithExplicitCancel(); + expect(result).toBe("cancelled"); + }); + + it("should handle rebuild dialog cancellation", async () => { + const host = createHostMock(); + + // Simulate user clicking cancel + host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce("cancelled"); + + const result = await host.mocks.ui.dialogManager.openWithExplicitCancel(); + expect(result).toBe("cancelled"); + }); + + it("should handle confirm dialog for remote configuration mismatch", async () => { + const host = createHostMock(); + + await host.mocks.ui.confirm.askSelectStringDialogue("Your settings differed slightly.", ["OK"]); + expect(host.mocks.ui.confirm.askSelectStringDialogue).toHaveBeenCalled(); + }); + }); + + describe("Remote configuration adjustment", () => { + it("should skip remote configuration fetch when preventFetchingConfig is true", async () => { + const host = createHostMock(); + const config = { preventFetchingConfig: true } as any; + + await adjustSettingToRemoteIfNeeded( + host as any, + createLoggerMock(), + { preventFetchingConfig: true }, + config + ); + + expect(host.mocks.tweakValue.fetchRemotePreferred).not.toHaveBeenCalled(); + }); + + it("should fetch remote configuration when preventFetchingConfig is false", async () => { + const host = createHostMock(); + const config = { batchSave: true } as any; + + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ + batchSave: false, + } as any); + + await adjustSettingToRemoteIfNeeded( + host as any, + createLoggerMock(), + { preventFetchingConfig: false }, + config + ); + + expect(host.mocks.tweakValue.fetchRemotePreferred).toHaveBeenCalled(); + }); + + const mismatchDetectionKeys = Object.keys(TweakValuesShouldMatchedTemplate); + it.each(mismatchDetectionKeys)( + "should apply remote configuration when available and different:%s", + async (key) => { + const host = createHostMock(); + + const config = { [key]: TweakValuesTemplate[key as keyof typeof TweakValuesTemplate] } as any; + const differentValue = + typeof config[key as keyof typeof config] === "boolean" + ? !config[key as keyof typeof config] + : typeof config[key as keyof typeof config] === "number" + ? (config[key as keyof typeof config] as number) + 1 + : "different"; + const differentConfig = { + [key]: differentValue, + }; + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce(differentConfig as any); + host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("OK"); + + await adjustSettingToRemote(host as any, createLoggerMock(), config); + expect(host.mocks.ui.confirm.askSelectStringDialogue).toHaveBeenCalled(); + expect(host.mocks.setting.applyPartial).toHaveBeenCalled(); + } + ); + const mismatchAcceptedKeys = Object.keys(TweakValuesRecommendedTemplate).filter( + (key) => !mismatchDetectionKeys.includes(key) + ); + + it.each(mismatchAcceptedKeys)( + "should apply remote configuration when available and different but acceptable: %s", + async (key) => { + const host = createHostMock(); + + const config = { [key]: TweakValuesTemplate[key as keyof typeof TweakValuesTemplate] } as any; + const differentValue = + typeof config[key as keyof typeof config] === "boolean" + ? !config[key as keyof typeof config] + : typeof config[key as keyof typeof config] === "number" + ? (config[key as keyof typeof config] as number) + 1 + : "different"; + const differentConfig = { + [key]: differentValue, + }; + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce(differentConfig as any); + host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("OK"); + + await adjustSettingToRemote(host as any, createLoggerMock(), config); + + expect(host.mocks.setting.applyPartial).toHaveBeenCalled(); + expect(host.mocks.ui.confirm.askSelectStringDialogue).not.toHaveBeenCalled(); + } + ); + + it("should show dialog when remote fetch fails", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + const config = { batchSave: true } as any; + + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce(null); + host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Skip and proceed"); + + await adjustSettingToRemote(host as any, log, config); + + expect(host.mocks.ui.confirm.askSelectStringDialogue).toHaveBeenCalled(); + }); + + it("should retry when user selects retry option", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + const config = { batchSave: true } as any; + + host.mocks.tweakValue.fetchRemotePreferred + .mockResolvedValueOnce(null) + .mockResolvedValueOnce({ batchSave: false } as any); + host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Retry (recommended)"); + + await adjustSettingToRemote(host as any, log, config); + + expect(host.mocks.tweakValue.fetchRemotePreferred).toHaveBeenCalledTimes(2); + }); + + it("should log when no changes needed", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + const config = { batchSave: false } as any; + + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ + batchSave: false, + } as any); + + await adjustSettingToRemote(host as any, log, config); + + expect(log).toHaveBeenCalled(); + }); + + it("should handle null extra parameter in adjustSettingToRemoteIfNeeded", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + const config = { batchSave: true } as any; + + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce(null); + host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Skip and proceed"); + + await adjustSettingToRemoteIfNeeded(host as any, log, null as any, config); + + expect(host.mocks.tweakValue.fetchRemotePreferred).toHaveBeenCalled(); + }); + }); + + describe("MinIO configuration handling", () => { + it("should not enable makeLocalChunkBeforeSync when remote is MinIO", () => { + const host = createHostMock(); + host.mocks.setting.settings.remoteType = REMOTE_MINIO; + + const settings = host.mocks.setting.currentSettings(); + const isMinIO = settings.remoteType === REMOTE_MINIO; + + expect(isMinIO).toBe(true); + }); + + it("should enable makeLocalChunkBeforeSync for non-MinIO remotes", () => { + const host = createHostMock(); + host.mocks.setting.settings.remoteType = "CouchDB"; + + const settings = host.mocks.setting.currentSettings(); + const isMinIO = settings.remoteType === REMOTE_MINIO; + + expect(isMinIO).toBe(false); + }); + }); + + describe("Suspension unlock verification", () => { + it("should return true when suspension is not active", async () => { + const host = createHostMock(); + + const result = await verifyAndUnlockSuspension(host as any, createLoggerMock()); + expect(result).toBe(true); + }); + + it("should ask for confirmation when suspension is active", async () => { + const host = createHostMock(); + + await host.mocks.setting.applyPartial({ suspendFileWatching: true }); + + host.mocks.ui.confirm.askYesNoDialog.mockResolvedValueOnce("yes"); + + await verifyAndUnlockSuspension(host as any, createLoggerMock()); + + expect(host.mocks.ui.confirm.askYesNoDialog).toHaveBeenCalled(); + }); + + it("should return true when user declines suspension unlock", async () => { + const host = createHostMock(); + + await host.mocks.setting.applyPartial({ suspendFileWatching: true }); + host.mocks.ui.confirm.askYesNoDialog.mockResolvedValueOnce("no"); + + const result = await verifyAndUnlockSuspension(host as any, createLoggerMock()); + + expect(result).toBe(true); + }); + + it("should resume file watching and restart when user accepts", async () => { + const host = createHostMock(); + + await host.mocks.setting.applyPartial({ suspendFileWatching: true }, true); + host.mocks.ui.confirm.askYesNoDialog.mockResolvedValueOnce("yes"); + + await verifyAndUnlockSuspension(host as any, createLoggerMock()); + + expect(host.mocks.appLifecycle.performRestart).toHaveBeenCalled(); + }); + }); + + describe("Error handling in vault initialization", () => { + it("should handle errors during initialisation gracefully", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + const result = await processVaultInitialisation(host as any, log, () => { + throw new Error("Initialization failed"); + }); + + expect(result).toBe(false); + expect(log).toHaveBeenCalled(); + }); + + it("should track log calls during error conditions", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + await processVaultInitialisation(host as any, log, () => { + throw new Error("Test error"); + }); + + expect(log).toHaveBeenCalled(); + }); + + it("should keep suspension state when error occurs during initialization", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + await processVaultInitialisation( + host as any, + log, + () => { + return Promise.resolve(false); + }, + true + ); + + expect(host.mocks.setting.currentSettings().suspendFileWatching).toBe(true); + }); + + it("should handle applySetting error in processVaultInitialisation", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + host.mocks.setting.applyPartial.mockRejectedValueOnce(new Error("Apply partial failed")); + + const result = await processVaultInitialisation(host as any, log, () => { + return Promise.resolve(true); + }); + + expect(result).toBe(false); + }); + }); + + describe("Flag file detection with both formats", () => { + it("should detect either original or human-readable fetch all flag", async () => { + const host = createHostMock(); + + // Add only human-readable flag + host.mocks.storageAccess.files.add(FlagFilesHumanReadable.FETCH_ALL); + + const humanExists = await host.mocks.storageAccess.isExists( + host.mocks.storageAccess.normalisePath(FlagFilesHumanReadable.FETCH_ALL) + ); + + expect(humanExists).toBe(true); + }); + + it("should detect either original or human-readable rebuild flag", async () => { + const host = createHostMock(); + + // Add only original flag + host.mocks.storageAccess.files.add(FlagFilesOriginal.REBUILD_ALL); + + const originalExists = await host.mocks.storageAccess.isExists( + host.mocks.storageAccess.normalisePath(FlagFilesOriginal.REBUILD_ALL) + ); + + expect(originalExists).toBe(true); + }); + }); + + describe("Handler execution", () => { + it("should execute fetch all handler check method", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL); + + const handler = createFetchAllFlagHandler(host as any, log); + const result = await handler.check(); + + expect(result).toBe(true); + }); + + it("should execute rebuild handler check method", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + host.mocks.storageAccess.files.add(FlagFilesOriginal.REBUILD_ALL); + + const handler = createRebuildFlagHandler(host as any, log); + const result = await handler.check(); + + expect(result).toBe(true); + }); + + it("should execute suspend handler check method", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + host.mocks.storageAccess.files.add(FlagFilesOriginal.SUSPEND_ALL); + + const handler = createSuspendFlagHandler(host as any, log); + const result = await handler.check(); + + expect(result).toBe(true); + }); + + it("should return false when flag does not exist", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + const handler = createFetchAllFlagHandler(host as any, log); + const result = await handler.check(); + + expect(result).toBe(false); + }); + + it("should return correct priority for each handler", () => { + const host = createHostMock(); + const log = createLoggerMock(); + + const suspendHandler = createSuspendFlagHandler(host as any, log); + const fetchHandler = createFetchAllFlagHandler(host as any, log); + const rebuildHandler = createRebuildFlagHandler(host as any, log); + + expect(suspendHandler.priority).toBe(5); + expect(fetchHandler.priority).toBe(10); + expect(rebuildHandler.priority).toBe(20); + }); + + it("should handle suspend flag and execute handler", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + host.mocks.storageAccess.files.add(FlagFilesOriginal.SUSPEND_ALL); + + const handler = createSuspendFlagHandler(host as any, log); + const checkResult = await handler.check(); + + expect(checkResult).toBe(true); + expect(typeof handler.handle).toBe("function"); + }); + + it("should have handle method for all handlers", () => { + const host = createHostMock(); + const log = createLoggerMock(); + + const suspendHandler = createSuspendFlagHandler(host as any, log); + const fetchHandler = createFetchAllFlagHandler(host as any, log); + const rebuildHandler = createRebuildFlagHandler(host as any, log); + + expect(typeof suspendHandler.handle).toBe("function"); + expect(typeof fetchHandler.handle).toBe("function"); + expect(typeof rebuildHandler.handle).toBe("function"); + }); + }); + + describe("Multiple concurrent operations", () => { + it("should handle multiple flag files existing simultaneously", async () => { + const host = createHostMock(); + + host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL); + host.mocks.storageAccess.files.add(FlagFilesOriginal.REBUILD_ALL); + host.mocks.storageAccess.files.add(FlagFilesOriginal.SUSPEND_ALL); + + const fetchExists = await host.mocks.storageAccess.isExists( + host.mocks.storageAccess.normalisePath(FlagFilesOriginal.FETCH_ALL) + ); + const rebuildExists = await host.mocks.storageAccess.isExists( + host.mocks.storageAccess.normalisePath(FlagFilesOriginal.REBUILD_ALL) + ); + const suspendExists = await host.mocks.storageAccess.isExists( + host.mocks.storageAccess.normalisePath(FlagFilesOriginal.SUSPEND_ALL) + ); + + expect(fetchExists).toBe(true); + expect(rebuildExists).toBe(true); + expect(suspendExists).toBe(true); + }); + + it("should cleanup all flags when processing completes", async () => { + const host = createHostMock(); + + host.mocks.storageAccess.files.add(FlagFilesHumanReadable.FETCH_ALL); + host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL); + + await host.mocks.storageAccess.delete(FlagFilesHumanReadable.FETCH_ALL); + await host.mocks.storageAccess.delete(FlagFilesOriginal.FETCH_ALL); + + expect(host.mocks.storageAccess.files.size).toBe(0); + }); + }); + + describe("Setting state transitions", () => { + it("should apply complete state transition for initialization", async () => { + const host = createHostMock(); + + // Initial state + const initialState = { + batchSave: host.mocks.setting.currentSettings().batchSave, + suspendFileWatching: host.mocks.setting.currentSettings().suspendFileWatching, + }; + + // Initialization state + await host.mocks.setting.applyPartial( + { + batchSave: false, + suspendFileWatching: true, + }, + true + ); + + const initState = { + batchSave: host.mocks.setting.currentSettings().batchSave, + suspendFileWatching: host.mocks.setting.currentSettings().suspendFileWatching, + }; + + // Post-initialization state + await host.mocks.setting.applyPartial( + { + batchSave: true, + suspendFileWatching: false, + }, + true + ); + + const finalState = { + batchSave: host.mocks.setting.currentSettings().batchSave, + suspendFileWatching: host.mocks.setting.currentSettings().suspendFileWatching, + }; + + expect(initialState.batchSave).toBe(true); + expect(initialState.suspendFileWatching).toBe(false); + + expect(initState.batchSave).toBe(false); + expect(initState.suspendFileWatching).toBe(true); + + expect(finalState.batchSave).toBe(true); + expect(finalState.suspendFileWatching).toBe(false); + }); + }); + + describe("flagHandlerToEventHandler integration", () => { + it("should return true when flag does not exist", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + const handler = createFetchAllFlagHandler(host as any, log); + const eventHandler = flagHandlerToEventHandler(handler); + + const result = await eventHandler(); + expect(result).toBe(true); + }); + + it("should execute handle when flag exists and check returns true", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL); + host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce("cancelled"); + + const handler = createFetchAllFlagHandler(host as any, log); + const eventHandler = flagHandlerToEventHandler(handler); + + await eventHandler(); + + // When dialog is cancelled, handle returns false + expect(host.mocks.ui.dialogManager.openWithExplicitCancel).toHaveBeenCalled(); + }); + + it("should return handle result when flag exists", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + host.mocks.storageAccess.files.add(FlagFilesOriginal.SUSPEND_ALL); + + const handler = createSuspendFlagHandler(host as any, log); + const eventHandler = flagHandlerToEventHandler(handler); + + const result = await eventHandler(); + + // Suspend handler execution results in false from processVaultInitialisation + expect(typeof result).toBe("boolean"); + }); + + it("should not call handle when check returns false", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + const handler = createRebuildFlagHandler(host as any, log); + const handleSpy = vi.spyOn(handler, "handle"); + const eventHandler = flagHandlerToEventHandler(handler); + + const result = await eventHandler(); + + // Check returns false because no rebuild flag exists + expect(handleSpy).not.toHaveBeenCalled(); + expect(result).toBe(true); + }); + + it("should handle rebuild flag with flagHandlerToEventHandler", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + host.mocks.storageAccess.files.add(FlagFilesOriginal.REBUILD_ALL); + host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce("cancelled"); + + const handler = createRebuildFlagHandler(host as any, log); + const eventHandler = flagHandlerToEventHandler(handler); + + await eventHandler(); + + expect(host.mocks.ui.dialogManager.openWithExplicitCancel).toHaveBeenCalled(); + }); + + it("should propagate errors from handle method", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL); + host.mocks.ui.dialogManager.openWithExplicitCancel.mockRejectedValueOnce(new Error("Dialog failed")); + + const handler = createFetchAllFlagHandler(host as any, log); + const eventHandler = flagHandlerToEventHandler(handler); + + try { + await eventHandler(); + } catch (error) { + expect((error as Error).message).toBe("Dialog failed"); + } + }); + + it("should handle fetchAll flag with flagHandlerToEventHandler identical", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ + customChunkSize: 1, + } as any); + + host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL); + host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce({ vault: "identical", extra: {} }); + host.mocks.rebuilder.$fetchLocal.mockResolvedValueOnce(); + const handler = createFetchAllFlagHandler(host as any, log); + const eventHandler = flagHandlerToEventHandler(handler); + + await Promise.resolve(eventHandler()); + await new Promise((resolve) => setTimeout(resolve, 10)); + expect(host.mocks.rebuilder.$fetchLocal).toHaveBeenCalled(); + + expect(host.mocks.ui.dialogManager.openWithExplicitCancel).toHaveBeenCalled(); + }); + it("should handle rebuildAll flag with flagHandlerToEventHandler", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + host.mocks.tweakValue.fetchRemotePreferred.mockResolvedValueOnce({ + customChunkSize: 1, + } as any); + + host.mocks.storageAccess.files.add(FlagFilesOriginal.REBUILD_ALL); + host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce({ extra: {} }); + host.mocks.rebuilder.$rebuildEverything.mockResolvedValueOnce(); + const handler = createRebuildFlagHandler(host as any, log); + const eventHandler = flagHandlerToEventHandler(handler); + + await Promise.resolve(eventHandler()); + await new Promise((resolve) => setTimeout(resolve, 10)); + expect(host.mocks.rebuilder.$rebuildEverything).toHaveBeenCalled(); + + expect(host.mocks.ui.dialogManager.openWithExplicitCancel).toHaveBeenCalled(); + }); + + it("should execute all handlers in sequence", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + const suspendHandler = createSuspendFlagHandler(host as any, log); + const fetchHandler = createFetchAllFlagHandler(host as any, log); + const rebuildHandler = createRebuildFlagHandler(host as any, log); + + const suspendEvent = flagHandlerToEventHandler(suspendHandler); + const fetchEvent = flagHandlerToEventHandler(fetchHandler); + const rebuildEvent = flagHandlerToEventHandler(rebuildHandler); + + // All should return true when flags don't exist + expect(await suspendEvent()).toBe(true); + expect(await fetchEvent()).toBe(true); + expect(await rebuildEvent()).toBe(true); + }); + + it("should return false from handle when suspending", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + host.mocks.storageAccess.files.add(FlagFilesOriginal.SUSPEND_ALL); + + const handler = createSuspendFlagHandler(host as any, log); + const eventHandler = flagHandlerToEventHandler(handler); + + const result = await eventHandler(); + + // Suspend handler returns false from its handle method + expect(result).toBe(false); + }); + + it("should handle check error gracefully", async () => { + const host = createHostMock(); + const log = createLoggerMock(); + + host.mocks.storageAccess.isExists.mockRejectedValueOnce(new Error("Check failed")); + + const handler = createFetchAllFlagHandler(host as any, log); + const eventHandler = flagHandlerToEventHandler(handler); + + try { + await eventHandler(); + } catch (error) { + expect((error as Error).message).toBe("Check failed"); + } + }); + }); +}); diff --git a/styles.css b/styles.css index 72e7ade..0125ecc 100644 --- a/styles.css +++ b/styles.css @@ -1,13 +1,13 @@ -.op-scrollable .added { +.ls-dialog .added { color: var(--text-on-accent); background-color: var(--text-accent); } -.op-scrollable .normal { +.ls-dialog .normal { color: var(--text-normal); } -.op-scrollable .deleted { +.ls-dialog .deleted { color: var(--text-on-accent); background-color: var(--text-muted); } From 62f78b4028dd13862559cf0d94959f50efe4c51c Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Tue, 3 Mar 2026 13:22:17 +0000 Subject: [PATCH 047/339] Modify unit-ci to upload coverage --- .github/workflows/unit-ci.yml | 16 ++++++++++++---- vitest.config.unit.ts | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/unit-ci.yml b/.github/workflows/unit-ci.yml index 506ec9a..a1e6455 100644 --- a/.github/workflows/unit-ci.yml +++ b/.github/workflows/unit-ci.yml @@ -30,8 +30,16 @@ jobs: - name: Install dependencies run: npm ci - - name: Install test dependencies (Playwright Chromium) - run: npm run test:install-dependencies + # unit tests do not require Playwright, so we can skip installing its dependencies to save time + # - name: Install test dependencies (Playwright Chromium) + # run: npm run test:install-dependencies - - name: Run unit tests suite - run: npm run test:unit \ No newline at end of file + - name: Run unit tests suite with coverage + run: npm run test:unit:coverage + + - name: Upload coverage report + if: always() + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: coverage/** \ No newline at end of file diff --git a/vitest.config.unit.ts b/vitest.config.unit.ts index 6fbbaa5..90d44dd 100644 --- a/vitest.config.unit.ts +++ b/vitest.config.unit.ts @@ -26,7 +26,7 @@ export default mergeConfig( ...importOnlyFiles, ], provider: "v8", - reporter: ["text", "json", "html"], + reporter: ["text", "json", "html", ["text", { file: "coverage-text.txt" }]], }, }, }) From d8bc2806e02e4bb6c84142467c5f8deec56f32d6 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Tue, 3 Mar 2026 13:27:23 +0000 Subject: [PATCH 048/339] bump --- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- updates.md | 16 ++++++++++++++++ 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/manifest.json b/manifest.json index b5ac92c..88497be 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.48", + "version": "0.25.49", "minAppVersion": "0.9.12", "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 39e713b..07b6051 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.48", + "version": "0.25.49", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.48", + "version": "0.25.49", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 6a66e60..37a65d9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.48", + "version": "0.25.49", "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 366863b..228c651 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,22 @@ 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.49 + +3rd March, 2026 + +### Fixed + +- No longer deleted files are not clickable in the Global History pane. +- Diff view now uses more specific classes (#803). +- A message of configuration mismatching slightly added for better understanding. + - Now it says `When replication is initiated manually via the command palette or ribbon, a dialogue box will open to address this.` to make it clear that the user can fix the issue by themselves. + +### Refactored + +- `ModuleRedFlag` has been refactored to `serviceFeatures/redFlag` and also tested. +- `ModuleInitializerFile` has been refactored to `lib/serviceFeatures/offlineScanner` and also tested. + ## 0.25.48 2nd March, 2026 From 7ff9c666cedeac08fd673b42b878ef5bcba74037 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Tue, 3 Mar 2026 13:34:18 +0000 Subject: [PATCH 049/339] Fix: No more credentials logged --- src/serviceFeatures/redFlag.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serviceFeatures/redFlag.ts b/src/serviceFeatures/redFlag.ts index cab7cc3..e4f2b0e 100644 --- a/src/serviceFeatures/redFlag.ts +++ b/src/serviceFeatures/redFlag.ts @@ -206,7 +206,7 @@ export async function adjustSettingToRemoteIfNeeded( } else { log("Remote configuration not applied.", LOG_LEVEL_NOTICE); } - log(JSON.stringify(config), LOG_LEVEL_VERBOSE); + // log(JSON.stringify(config), LOG_LEVEL_VERBOSE); } /** From 8faa19629ba304038e956c552ef509e78f430a17 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Tue, 3 Mar 2026 13:39:13 +0000 Subject: [PATCH 050/339] bump --- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- updates.md | 4 +++- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/manifest.json b/manifest.json index 88497be..47bbe3f 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.49", + "version": "0.25.50", "minAppVersion": "0.9.12", "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 07b6051..60712a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.49", + "version": "0.25.50", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.49", + "version": "0.25.50", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 37a65d9..2be732f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.49", + "version": "0.25.50", "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 228c651..ae56fc8 100644 --- a/updates.md +++ b/updates.md @@ -3,10 +3,12 @@ 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.49 +## 0.25.50 3rd March, 2026 +Note: 0.25.49 has been skipped because of too verbose logging (credentials are logged in verbose level, but I realised that could lead to unexpected exposure on issue reporting). Please bump to 0.25.50 to get the fix if you are on 0.25.49. (No expected behaviour changes except the logging). + ### Fixed - No longer deleted files are not clickable in the Global History pane. From 3403712e249e7583fce367b479bc91f31d228a26 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 7 Mar 2026 13:13:37 +0900 Subject: [PATCH 051/339] Downgrade version from 0.25.50 to 0.25.48 for #813 --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 47bbe3f..b5ac92c 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.50", + "version": "0.25.48", "minAppVersion": "0.9.12", "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", From 79bb5e1c776755bba3d7239ae6b1dd5d5cb92ded Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 7 Mar 2026 18:36:10 +0900 Subject: [PATCH 052/339] ### Reverted - Reverted to ModuleRedFlag and ModuleInitializerFile to the previous version because of some unexpected issues. (#813) --- src/main.ts | 18 ++++++++++-------- ...le_obsolete.ts => ModuleInitializerFile.ts} | 0 2 files changed, 10 insertions(+), 8 deletions(-) rename src/modules/essential/{ModuleInitializerFile_obsolete.ts => ModuleInitializerFile.ts} (100%) diff --git a/src/main.ts b/src/main.ts index c285d72..3266045 100644 --- a/src/main.ts +++ b/src/main.ts @@ -22,7 +22,7 @@ import { ModuleMigration } from "./modules/essential/ModuleMigration.ts"; import { ModuleConflictResolver } from "./modules/coreFeatures/ModuleConflictResolver.ts"; import { ModuleInteractiveConflictResolver } from "./modules/features/ModuleInteractiveConflictResolver.ts"; import { ModuleLog } from "./modules/features/ModuleLog.ts"; -// import { ModuleRedFlag } from "./modules/coreFeatures/ModuleRedFlag.ts"; +import { ModuleRedFlag } from "./modules/coreFeatures/ModuleRedFlag.ts"; import { ModuleObsidianMenu } from "./modules/essentialObsidian/ModuleObsidianMenu.ts"; import { ModuleSetupObsidian } from "./modules/features/ModuleSetupObsidian.ts"; import { SetupManager } from "./modules/features/SetupManager.ts"; @@ -36,7 +36,7 @@ import { ModuleObsidianSettingDialogue } from "./modules/features/ModuleObsidian import { ModuleObsidianDocumentHistory } from "./modules/features/ModuleObsidianDocumentHistory.ts"; import { ModuleObsidianGlobalHistory } from "./modules/features/ModuleGlobalHistory.ts"; import { ModuleObsidianSettingsAsMarkdown } from "./modules/features/ModuleObsidianSettingAsMarkdown.ts"; -// import { ModuleInitializerFile } from "./modules/essential/ModuleInitializerFile.ts"; +import { ModuleInitializerFile } from "./modules/essential/ModuleInitializerFile.ts"; import { ModuleReplicator } from "./modules/core/ModuleReplicator.ts"; import { ModuleReplicatorCouchDB } from "./modules/core/ModuleReplicatorCouchDB.ts"; import { ModuleReplicatorMinIO } from "./modules/core/ModuleReplicatorMinIO.ts"; @@ -65,8 +65,8 @@ import type { ServiceModules } from "./types.ts"; import { useTargetFilters } from "@lib/serviceFeatures/targetFilter.ts"; import { setNoticeClass } from "@lib/mock_and_interop/wrapper.ts"; import { useCheckRemoteSize } from "./lib/src/serviceFeatures/checkRemoteSize.ts"; -import { useRedFlagFeatures } from "./serviceFeatures/redFlag.ts"; -import { useOfflineScanner } from "./lib/src/serviceFeatures/offlineScanner.ts"; +// import { useRedFlagFeatures } from "./serviceFeatures/redFlag.ts"; +// import { useOfflineScanner } from "./lib/src/serviceFeatures/offlineScanner.ts"; export default class ObsidianLiveSyncPlugin extends Plugin @@ -167,7 +167,7 @@ export default class ObsidianLiveSyncPlugin this._registerModule(new ModuleReplicator(this)); this._registerModule(new ModuleConflictResolver(this)); this._registerModule(new ModulePeriodicProcess(this)); - // this._registerModule(new ModuleInitializerFile(this)); + this._registerModule(new ModuleInitializerFile(this)); this._registerModule(new ModuleObsidianEvents(this, this)); this._registerModule(new ModuleResolvingMismatchedTweaks(this)); this._registerModule(new ModuleObsidianSettingsAsMarkdown(this)); @@ -177,7 +177,7 @@ export default class ObsidianLiveSyncPlugin this._registerModule(new ModuleSetupObsidian(this)); this._registerModule(new ModuleObsidianDocumentHistory(this, this)); this._registerModule(new ModuleMigration(this)); - // this._registerModule(new ModuleRedFlag(this)); + this._registerModule(new ModuleRedFlag(this)); this._registerModule(new ModuleInteractiveConflictResolver(this, this)); this._registerModule(new ModuleObsidianGlobalHistory(this, this)); // this._registerModule(new ModuleCheckRemoteSize(this)); @@ -416,8 +416,10 @@ export default class ObsidianLiveSyncPlugin const curriedFeature = () => feature(this); this.services.appLifecycle.onLayoutReady.addHandler(curriedFeature); } - useRedFlagFeatures(this); - useOfflineScanner(this); + // redFlag and offline scanner features disabled v0.25.50 due to some unexpected issues. Will re-enable after further testing. + // useRedFlagFeatures(this); + // useOfflineScanner(this); + // enable target filter feature. useTargetFilters(this); useCheckRemoteSize(this); diff --git a/src/modules/essential/ModuleInitializerFile_obsolete.ts b/src/modules/essential/ModuleInitializerFile.ts similarity index 100% rename from src/modules/essential/ModuleInitializerFile_obsolete.ts rename to src/modules/essential/ModuleInitializerFile.ts From d0244bd6d04ca9f0f735caf948b3c48d999fc5cb Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 7 Mar 2026 18:36:54 +0900 Subject: [PATCH 053/339] bump --- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- updates.md | 9 +++++++++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/manifest.json b/manifest.json index b5ac92c..07a53b0 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.48", + "version": "0.25.51", "minAppVersion": "0.9.12", "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 60712a6..fb33693 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.50", + "version": "0.25.51", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.50", + "version": "0.25.51", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 2be732f..601c8f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.50", + "version": "0.25.51", "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 ae56fc8..6b0ef06 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +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. +## 0.25.51 + +7th March, 2026 + +### Reverted + +- Reverted to ModuleRedFlag and ModuleInitializerFile to the previous version because of some unexpected issues. (#813) + - I will re-implement them in the future with better design and tests. + ## 0.25.50 3rd March, 2026 From 1d83e0ee31037d71b393a1572ed824a921b2e7fa Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sun, 8 Mar 2026 17:24:54 +0900 Subject: [PATCH 054/339] =?UTF-8?q?manifest.json=20=E3=82=92=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 07a53b0..4649f12 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.51", + "version": "0.25.47", "minAppVersion": "0.9.12", "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", From f7dba6854ffdee4e5d633ec459d6700a87fc5f01 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 9 Mar 2026 10:24:49 +0900 Subject: [PATCH 055/339] ### Fixed - No longer unexpected deletion-propagation occurs when the parent directory is not empty (#813). ### Revert reversions - Reverted the reversion of ModuleCheckRemoteSize. Now it is back to the service feature. --- src/lib | 2 +- src/main.ts | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/lib b/src/lib index 258d9ac..27d1d4a 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 258d9aca1139e718efb0431449556c0543d72c7e +Subproject commit 27d1d4a6e727a42307fe10c35d618864302fbbf7 diff --git a/src/main.ts b/src/main.ts index 3266045..3d63841 100644 --- a/src/main.ts +++ b/src/main.ts @@ -22,7 +22,7 @@ import { ModuleMigration } from "./modules/essential/ModuleMigration.ts"; import { ModuleConflictResolver } from "./modules/coreFeatures/ModuleConflictResolver.ts"; import { ModuleInteractiveConflictResolver } from "./modules/features/ModuleInteractiveConflictResolver.ts"; import { ModuleLog } from "./modules/features/ModuleLog.ts"; -import { ModuleRedFlag } from "./modules/coreFeatures/ModuleRedFlag.ts"; +// import { ModuleRedFlag } from "./modules/coreFeatures/ModuleRedFlag.ts"; import { ModuleObsidianMenu } from "./modules/essentialObsidian/ModuleObsidianMenu.ts"; import { ModuleSetupObsidian } from "./modules/features/ModuleSetupObsidian.ts"; import { SetupManager } from "./modules/features/SetupManager.ts"; @@ -36,7 +36,7 @@ import { ModuleObsidianSettingDialogue } from "./modules/features/ModuleObsidian import { ModuleObsidianDocumentHistory } from "./modules/features/ModuleObsidianDocumentHistory.ts"; import { ModuleObsidianGlobalHistory } from "./modules/features/ModuleGlobalHistory.ts"; import { ModuleObsidianSettingsAsMarkdown } from "./modules/features/ModuleObsidianSettingAsMarkdown.ts"; -import { ModuleInitializerFile } from "./modules/essential/ModuleInitializerFile.ts"; +// import { ModuleInitializerFile } from "./modules/essential/ModuleInitializerFile.ts"; import { ModuleReplicator } from "./modules/core/ModuleReplicator.ts"; import { ModuleReplicatorCouchDB } from "./modules/core/ModuleReplicatorCouchDB.ts"; import { ModuleReplicatorMinIO } from "./modules/core/ModuleReplicatorMinIO.ts"; @@ -65,8 +65,8 @@ import type { ServiceModules } from "./types.ts"; import { useTargetFilters } from "@lib/serviceFeatures/targetFilter.ts"; import { setNoticeClass } from "@lib/mock_and_interop/wrapper.ts"; import { useCheckRemoteSize } from "./lib/src/serviceFeatures/checkRemoteSize.ts"; -// import { useRedFlagFeatures } from "./serviceFeatures/redFlag.ts"; -// import { useOfflineScanner } from "./lib/src/serviceFeatures/offlineScanner.ts"; +import { useRedFlagFeatures } from "./serviceFeatures/redFlag.ts"; +import { useOfflineScanner } from "./lib/src/serviceFeatures/offlineScanner.ts"; export default class ObsidianLiveSyncPlugin extends Plugin @@ -167,7 +167,7 @@ export default class ObsidianLiveSyncPlugin this._registerModule(new ModuleReplicator(this)); this._registerModule(new ModuleConflictResolver(this)); this._registerModule(new ModulePeriodicProcess(this)); - this._registerModule(new ModuleInitializerFile(this)); + // this._registerModule(new ModuleInitializerFile(this)); this._registerModule(new ModuleObsidianEvents(this, this)); this._registerModule(new ModuleResolvingMismatchedTweaks(this)); this._registerModule(new ModuleObsidianSettingsAsMarkdown(this)); @@ -177,7 +177,7 @@ export default class ObsidianLiveSyncPlugin this._registerModule(new ModuleSetupObsidian(this)); this._registerModule(new ModuleObsidianDocumentHistory(this, this)); this._registerModule(new ModuleMigration(this)); - this._registerModule(new ModuleRedFlag(this)); + // this._registerModule(new ModuleRedFlag(this)); this._registerModule(new ModuleInteractiveConflictResolver(this, this)); this._registerModule(new ModuleObsidianGlobalHistory(this, this)); // this._registerModule(new ModuleCheckRemoteSize(this)); @@ -416,9 +416,8 @@ export default class ObsidianLiveSyncPlugin const curriedFeature = () => feature(this); this.services.appLifecycle.onLayoutReady.addHandler(curriedFeature); } - // redFlag and offline scanner features disabled v0.25.50 due to some unexpected issues. Will re-enable after further testing. - // useRedFlagFeatures(this); - // useOfflineScanner(this); + useRedFlagFeatures(this); + useOfflineScanner(this); // enable target filter feature. useTargetFilters(this); From 584adc929654caec8af93881872860e9aae47a8b Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 9 Mar 2026 10:25:19 +0900 Subject: [PATCH 056/339] bump --- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- updates.md | 17 +++++++++++++++++ 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/manifest.json b/manifest.json index 07a53b0..cdbae18 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.51", + "version": "0.25.52", "minAppVersion": "0.9.12", "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 fb33693..f2d5fff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.51", + "version": "0.25.52", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.51", + "version": "0.25.52", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 601c8f1..ee4ea56 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.51", + "version": "0.25.52", "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 6b0ef06..53a4a9f 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,23 @@ 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.52 + +9th March, 2026 + +Excuses: Too much `I`. +Whilst I had a fever, I could not figure it out at all, but once I felt better, I spotted the problem in about thirty seconds. I apologise for causing you concern. I am grateful for your patience. +I would like to devise a mechanism for running simple test scenarios. Now that we have got the Obsidian CLI up and running, it seems the perfect opportunity. + +To improve the bus factor, we really need to organise the source code more thoroughly. Your cooperation and contributions would be greatly appreciated. + +### Fixed +- No longer unexpected deletion-propagation occurs when the parent directory is not empty (#813). + +### Revert reversions +- Reverted the reversion of ModuleCheckRemoteSize. Now it is back to the service feature. + + ## 0.25.51 7th March, 2026 From 0dfd42259deccd283def3b44344c1f7c809b6bf1 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 11 Mar 2026 05:47:00 +0100 Subject: [PATCH 057/339] 11th March, 2026 Now, Self-hosted LiveSync has finally begun to be split into the Self-hosted LiveSync plugin for Obsidian, and a properly abstracted version of it. This may not offer much benefit to Obsidian plugin users, or might even cause a slight inconvenience, but I believe it will certainly help improve testability and make the ecosystem better. However, I do not see the point in putting something with little benefit into beta, so I am handling this on the alpha branch. I would actually preferred to create an R&D branch, but I was not keen on the ampersand, and I feel it will eventually become a proper beta anyway. ### Refactored - Separated `ObsidianLiveSyncPlugin` into `ObsidianLiveSyncPlugin` and `LiveSyncBaseCore`. - Now `LiveSyncCore` indicates the type specified version of `LiveSyncBaseCore`. - Referencing `plugin.xxx` has been rewritten to referencing the corresponding service or `core.xxx`. ### Internal API changes - Storage Access APIs are now yielding Promises. This is to allow more limited storage platforms to be supported. ### R&D - Browser-version of Self-hosted LiveSync is now in development. This is not intended for public use now, but I will eventually make it available for testing. - We can see the code in `src/apps/webapp` for the browser version. --- .gitignore | 3 +- src/LiveSyncBaseCore.ts | 287 +++++++++++ src/apps/webapp/.gitignore | 4 + .../webapp/adapters/FSAPIConversionAdapter.ts | 34 ++ .../webapp/adapters/FSAPIFileSystemAdapter.ts | 214 ++++++++ src/apps/webapp/adapters/FSAPIPathAdapter.ts | 18 + .../webapp/adapters/FSAPIStorageAdapter.ts | 210 ++++++++ .../webapp/adapters/FSAPITypeGuardAdapter.ts | 17 + src/apps/webapp/adapters/FSAPITypes.ts | 24 + src/apps/webapp/adapters/FSAPIVaultAdapter.ts | 123 +++++ src/apps/webapp/index.html | 209 ++++++++ src/apps/webapp/main.ts | 345 +++++++++++++ .../FSAPIStorageEventManagerAdapter.ts | 281 +++++++++++ .../managers/StorageEventManagerFSAPI.ts | 39 ++ src/apps/webapp/package.json | 21 + .../serviceModules/DatabaseFileAccess.ts | 15 + .../serviceModules/FSAPIServiceModules.ts | 104 ++++ .../webapp/serviceModules/FileAccessFSAPI.ts | 20 + .../serviceModules/ServiceFileAccessImpl.ts | 15 + src/apps/webapp/svelte.config.js | 7 + src/apps/webapp/tsconfig.json | 32 ++ src/apps/webapp/vite.config.ts | 34 ++ src/apps/webpeer/src/P2PReplicatorShim.ts | 76 ++- src/apps/webpeer/src/SyncMain.svelte | 2 +- src/common/PeriodicProcessor.ts | 45 ++ src/common/events.ts | 4 +- src/common/utils.ts | 51 +- src/features/ConfigSync/CmdConfigSync.ts | 109 ++-- src/features/ConfigSync/PluginCombo.svelte | 7 +- src/features/ConfigSync/PluginDialogModal.ts | 2 +- src/features/ConfigSync/PluginPane.svelte | 33 +- .../HiddenFileSync/CmdHiddenFileSync.ts | 108 ++-- src/features/LiveSyncCommands.ts | 17 +- .../CmdLocalDatabaseMainte.ts | 22 +- src/features/P2PSync/CmdP2PReplicator.ts | 29 +- .../P2PReplicator/P2PReplicatorPane.svelte | 56 ++- .../P2PReplicator/P2PReplicatorPaneView.ts | 43 +- src/lib | 2 +- src/main.ts | 467 ++++-------------- src/managers/StorageEventManagerObsidian.ts | 10 +- src/modules/AbstractModule.ts | 16 +- src/modules/core/ModulePeriodicProcess.ts | 2 +- src/modules/core/ModuleReplicator.ts | 3 +- src/modules/core/ReplicateResultProcessor.ts | 8 +- .../coreFeatures/ModuleConflictResolver.ts | 12 +- src/modules/essential/ModuleBasicMenu.ts | 86 ++++ .../essential/ModuleInitializerFile.ts | 4 +- src/modules/essential/ModuleMigration.ts | 6 +- .../ModuleCheckRemoteSize_obsolete.ts | 9 +- .../essentialObsidian/ModuleObsidianMenu.ts | 83 +--- src/modules/extras/ModuleReplicateTest.ts | 7 +- src/modules/extras/devUtil/TestPane.svelte | 11 +- src/modules/extras/devUtil/testUtils.ts | 4 +- src/modules/extras/devUtil/tests.ts | 8 +- .../DocumentHistory/DocumentHistoryModal.ts | 12 +- .../GlobalHistory/GlobalHistory.svelte | 12 +- .../GlobalHistory/GlobalHistoryView.ts | 1 + src/modules/features/ModuleLog.ts | 7 +- .../features/ModuleObsidianDocumentHistory.ts | 2 +- .../features/ModuleObsidianSettingTab.ts | 3 +- .../ObsidianLiveSyncSettingTab.ts | 45 +- .../features/SettingDialogue/PaneHatch.ts | 56 +-- .../SettingDialogue/PaneMaintenance.ts | 16 +- .../features/SettingDialogue/PanePatches.ts | 22 +- .../SettingDialogue/PaneRemoteConfig.ts | 12 +- .../features/SettingDialogue/PaneSetup.ts | 6 +- .../SettingDialogue/PaneSyncSettings.ts | 2 +- src/modules/main/ModuleLiveSyncMain.ts | 2 +- src/modules/services/ObsidianAPIService.ts | 14 + .../ObsidianFileSystemAdapter.ts | 12 +- test/harness/harness.ts | 14 +- test/suite/db_common.ts | 8 +- test/suite/onlylocaldb.test.ts | 18 +- test/suite/sync.senario.basic.ts | 30 +- test/suite/sync_common.ts | 36 +- test/unit/dialog.test.ts | 2 +- updates.md | 28 +- 77 files changed, 2849 insertions(+), 909 deletions(-) create mode 100644 src/LiveSyncBaseCore.ts create mode 100644 src/apps/webapp/.gitignore create mode 100644 src/apps/webapp/adapters/FSAPIConversionAdapter.ts create mode 100644 src/apps/webapp/adapters/FSAPIFileSystemAdapter.ts create mode 100644 src/apps/webapp/adapters/FSAPIPathAdapter.ts create mode 100644 src/apps/webapp/adapters/FSAPIStorageAdapter.ts create mode 100644 src/apps/webapp/adapters/FSAPITypeGuardAdapter.ts create mode 100644 src/apps/webapp/adapters/FSAPITypes.ts create mode 100644 src/apps/webapp/adapters/FSAPIVaultAdapter.ts create mode 100644 src/apps/webapp/index.html create mode 100644 src/apps/webapp/main.ts create mode 100644 src/apps/webapp/managers/FSAPIStorageEventManagerAdapter.ts create mode 100644 src/apps/webapp/managers/StorageEventManagerFSAPI.ts create mode 100644 src/apps/webapp/package.json create mode 100644 src/apps/webapp/serviceModules/DatabaseFileAccess.ts create mode 100644 src/apps/webapp/serviceModules/FSAPIServiceModules.ts create mode 100644 src/apps/webapp/serviceModules/FileAccessFSAPI.ts create mode 100644 src/apps/webapp/serviceModules/ServiceFileAccessImpl.ts create mode 100644 src/apps/webapp/svelte.config.js create mode 100644 src/apps/webapp/tsconfig.json create mode 100644 src/apps/webapp/vite.config.ts create mode 100644 src/common/PeriodicProcessor.ts create mode 100644 src/modules/essential/ModuleBasicMenu.ts diff --git a/.gitignore b/.gitignore index 51ae124..43e267e 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,5 @@ data.json cov_profile/** -coverage \ No newline at end of file +coverage +src/apps/cli/dist/* \ No newline at end of file diff --git a/src/LiveSyncBaseCore.ts b/src/LiveSyncBaseCore.ts new file mode 100644 index 0000000..1f822bd --- /dev/null +++ b/src/LiveSyncBaseCore.ts @@ -0,0 +1,287 @@ +import { LOG_LEVEL_INFO } from "octagonal-wheels/common/logger"; +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"; +import type { Confirm } from "./lib/src/interfaces/Confirm"; +import type { DatabaseFileAccess } from "./lib/src/interfaces/DatabaseFileAccess"; +import type { Rebuilder } from "./lib/src/interfaces/DatabaseRebuilder"; +import type { IFileHandler } from "./lib/src/interfaces/FileHandler"; +import type { StorageAccess } from "./lib/src/interfaces/StorageAccess"; +import type { LiveSyncLocalDBEnv } from "./lib/src/pouchdb/LiveSyncLocalDB"; +import type { LiveSyncCouchDBReplicatorEnv } from "./lib/src/replication/couchdb/LiveSyncReplicator"; +import type { CheckPointInfo } from "./lib/src/replication/journal/JournalSyncTypes"; +import type { LiveSyncJournalReplicatorEnv } from "./lib/src/replication/journal/LiveSyncJournalReplicatorEnv"; +import type { LiveSyncReplicatorEnv } from "./lib/src/replication/LiveSyncAbstractReplicator"; +import { useCheckRemoteSize } from "./lib/src/serviceFeatures/checkRemoteSize"; +import { useOfflineScanner } from "./lib/src/serviceFeatures/offlineScanner"; +import { useTargetFilters } from "./lib/src/serviceFeatures/targetFilter"; +import type { ServiceContext } from "./lib/src/services/base/ServiceBase"; +import type { InjectableServiceHub } from "./lib/src/services/InjectableServices"; +import { AbstractModule } from "./modules/AbstractModule"; +import { ModulePeriodicProcess } from "./modules/core/ModulePeriodicProcess"; +import { ModuleReplicator } from "./modules/core/ModuleReplicator"; +import { ModuleReplicatorCouchDB } from "./modules/core/ModuleReplicatorCouchDB"; +import { ModuleReplicatorMinIO } from "./modules/core/ModuleReplicatorMinIO"; +import { ModuleConflictChecker } from "./modules/coreFeatures/ModuleConflictChecker"; +import { ModuleConflictResolver } from "./modules/coreFeatures/ModuleConflictResolver"; +import { ModuleResolvingMismatchedTweaks } from "./modules/coreFeatures/ModuleResolveMismatchedTweaks"; +import { ModuleLiveSyncMain } from "./modules/main/ModuleLiveSyncMain"; +import type { ServiceModules } from "./lib/src/interfaces/ServiceModule"; +import { useRedFlagFeatures } from "./serviceFeatures/redFlag"; +import { ModuleBasicMenu } from "./modules/essential/ModuleBasicMenu"; + +export class LiveSyncBaseCore< + T extends ServiceContext = ServiceContext, + TCommands extends IMinimumLiveSyncCommands = IMinimumLiveSyncCommands, +> + implements + LiveSyncLocalDBEnv, + LiveSyncReplicatorEnv, + LiveSyncJournalReplicatorEnv, + LiveSyncCouchDBReplicatorEnv, + HasSettings +{ + addOns = [] as TCommands[]; + + /** + * register an add-onn to the plug-in. + * Add-ons are features that are not essential to the core functionality of the plugin, + * @param addOn + */ + private _registerAddOn(addOn: TCommands) { + this.addOns.push(addOn); + this.services.appLifecycle.onUnload.addHandler(() => Promise.resolve(addOn.onunload()).then(() => true)); + } + + /** + * Get an add-on by its class name. Returns undefined if not found. + * @param cls + * @returns + */ + getAddOn(cls: string) { + for (const addon of this.addOns) { + if (addon.constructor.name == cls) return addon as T; + } + return undefined; + } + + constructor( + serviceHub: InjectableServiceHub, + serviceModuleInitialiser: ( + core: LiveSyncBaseCore, + serviceHub: InjectableServiceHub + ) => ServiceModules, + extraModuleInitialiser: (core: LiveSyncBaseCore) => AbstractModule[], + addOnsInitialiser: (core: LiveSyncBaseCore) => TCommands[], + featuresInitialiser: (core: LiveSyncBaseCore) => void + ) { + this._services = serviceHub; + this._serviceModules = serviceModuleInitialiser(this, serviceHub); + const extraModules = extraModuleInitialiser(this); + this.registerModules(extraModules); + this.initialiseServiceFeatures(); + featuresInitialiser(this); + const addOns = addOnsInitialiser(this); + for (const addOn of addOns) { + this._registerAddOn(addOn); + } + this.bindModuleFunctions(); + } + /** + * The service hub for managing all services. + */ + _services: InjectableServiceHub | undefined = undefined; + + get services() { + if (!this._services) { + throw new Error("Services not initialised yet"); + } + return this._services; + } + /** + * Service Modules + */ + protected _serviceModules: ServiceModules; + + get serviceModules() { + return this._serviceModules; + } + + /** + * The modules of the plug-in. Modules are responsible for specific features or functionalities of the plug-in, such as file handling, conflict resolution, replication, etc. + */ + private modules = [ + // Move to registerModules + ] as AbstractModule[]; + + /** + * Get a module by its class. Throws an error if not found. + * Mostly used for getting SetupManager. + * @param constructor + * @returns + */ + getModule(constructor: new (...args: any[]) => T): T { + for (const module of this.modules) { + if (module.constructor === constructor) return module as T; + } + throw new Error(`Module ${constructor} not found or not loaded.`); + } + + /** + * Register a module to the plug-in. + * @param module The module to register. + */ + private _registerModule(module: AbstractModule) { + this.modules.push(module); + } + + public registerModules(extraModules: AbstractModule[] = []) { + this._registerModule(new ModuleLiveSyncMain(this)); + this._registerModule(new ModuleConflictChecker(this)); + this._registerModule(new ModuleReplicatorMinIO(this)); + this._registerModule(new ModuleReplicatorCouchDB(this)); + this._registerModule(new ModuleReplicator(this)); + this._registerModule(new ModuleConflictResolver(this)); + this._registerModule(new ModulePeriodicProcess(this)); + this._registerModule(new ModuleResolvingMismatchedTweaks(this)); + this._registerModule(new ModuleBasicMenu(this)); + + for (const module of extraModules) { + this._registerModule(module); + } + // Test and Dev Modules + } + + /** + * Bind module functions to services. + */ + public bindModuleFunctions() { + for (const module of this.modules) { + if (module instanceof AbstractModule) { + module.onBindFunction(this, this.services); + __$checkInstanceBinding(module); // Check if all functions are properly bound, and log warnings if not. + } else { + this.services.API.addLog( + `Module ${(module as any)?.constructor?.name ?? "unknown"} does not have onBindFunction, skipping binding.`, + LOG_LEVEL_INFO + ); + } + } + } + /** + * @obsolete Use services.UI.confirm instead. The confirm function to show a confirmation dialog to the user. + */ + get confirm(): Confirm { + return this.services.UI.confirm; + } + + /** + * @obsolete Use services.setting.currentSettings instead. The current settings of the plug-in. + */ + get settings() { + return this.services.setting.settings; + } + + /** + * @obsolete Use services.setting.settings instead. Set the settings of the plug-in. + */ + set settings(value: ObsidianLiveSyncSettings) { + this.services.setting.settings = value; + } + + /** + * @obsolete Use services.setting.currentSettings instead. Get the settings of the plug-in. + * @returns The current settings of the plug-in. + */ + getSettings(): ObsidianLiveSyncSettings { + return this.settings; + } + + /** + * @obsolete Use services.database.localDatabase instead. The local database instance. + */ + get localDatabase() { + return this.services.database.localDatabase; + } + + /** + * @obsolete Use services.database.localDatabase instead. Get the PouchDB database instance. Note that this is not the same as the local database instance, which is a wrapper around the PouchDB database. + * @returns The PouchDB database instance. + */ + getDatabase(): PouchDB.Database { + return this.localDatabase.localDatabase; + } + + /** + * @obsolete Use services.keyValueDB.simpleStore instead. A simple key-value store for storing non-file data, such as checkpoints, sync status, etc. + */ + get simpleStore() { + return this.services.keyValueDB.simpleStore as SimpleStore; + } + + /** + * @obsolete Use services.replication.getActiveReplicator instead. Get the active replicator instance. Note that there can be multiple replicators, but only one can be active at a time. + */ + get replicator() { + return this.services.replicator.getActiveReplicator()!; + } + + /** + * @obsolete Use services.keyValueDB.kvDB instead. Get the key-value database instance. This is used for storing large data that cannot be stored in the simple store, such as file metadata, etc. + */ + get kvDB() { + return this.services.keyValueDB.kvDB; + } + + /// Modules which were relied on services + /** + * Storage Accessor for handling file operations. + * @obsolete Use serviceModules.storageAccess instead. + */ + get storageAccess(): StorageAccess { + return this.serviceModules.storageAccess; + } + /** + * Database File Accessor for handling file operations related to the database, such as exporting the database, importing from a file, etc. + * @obsolete Use serviceModules.databaseFileAccess instead. + */ + get databaseFileAccess(): DatabaseFileAccess { + return this.serviceModules.databaseFileAccess; + } + /** + * File Handler for handling file operations related to replication, such as resolving conflicts, applying changes from replication, etc. + * @obsolete Use serviceModules.fileHandler instead. + */ + get fileHandler(): IFileHandler { + return this.serviceModules.fileHandler; + } + /** + * Rebuilder for handling database rebuilding operations. + * @obsolete Use serviceModules.rebuilder instead. + */ + get rebuilder(): Rebuilder { + return this.serviceModules.rebuilder; + } + + // private initialiseServices(serviceHub: InjectableServiceHub) { + // this._services = serviceHub; + // } + /** + * Initialise ServiceFeatures. + * (Please refer `serviceFeatures` for more details) + */ + initialiseServiceFeatures() { + useRedFlagFeatures(this); + useOfflineScanner(this); + + // enable target filter feature. + useTargetFilters(this); + useCheckRemoteSize(this); + } +} + +export interface IMinimumLiveSyncCommands { + onunload(): void; + onload(): void | Promise; + constructor: { name: string }; +} diff --git a/src/apps/webapp/.gitignore b/src/apps/webapp/.gitignore new file mode 100644 index 0000000..7ac8ea1 --- /dev/null +++ b/src/apps/webapp/.gitignore @@ -0,0 +1,4 @@ +node_modules +dist +.DS_Store +*.log diff --git a/src/apps/webapp/adapters/FSAPIConversionAdapter.ts b/src/apps/webapp/adapters/FSAPIConversionAdapter.ts new file mode 100644 index 0000000..7acd106 --- /dev/null +++ b/src/apps/webapp/adapters/FSAPIConversionAdapter.ts @@ -0,0 +1,34 @@ +import type { UXFileInfoStub, UXFolderInfo } from "../../../lib/src/common/types"; +import type { IConversionAdapter } from "../../../lib/src/serviceModules/adapters"; +import type { FSAPIFile, FSAPIFolder } from "./FSAPITypes"; + +/** + * Conversion adapter implementation for FileSystem API + */ +export class FSAPIConversionAdapter implements IConversionAdapter { + nativeFileToUXFileInfoStub(file: FSAPIFile): UXFileInfoStub { + const pathParts = file.path.split("/"); + const name = pathParts[pathParts.length - 1] || file.handle.name; + + return { + name: name, + path: file.path, + stat: file.stat, + isFolder: false, + }; + } + + nativeFolderToUXFolder(folder: FSAPIFolder): UXFolderInfo { + const pathParts = folder.path.split("/"); + const name = pathParts[pathParts.length - 1] || folder.handle.name; + const parentPath = pathParts.slice(0, -1).join("/"); + + return { + name: name, + path: folder.path, + isFolder: true, + children: [], + parent: parentPath as any, + }; + } +} diff --git a/src/apps/webapp/adapters/FSAPIFileSystemAdapter.ts b/src/apps/webapp/adapters/FSAPIFileSystemAdapter.ts new file mode 100644 index 0000000..1c87b46 --- /dev/null +++ b/src/apps/webapp/adapters/FSAPIFileSystemAdapter.ts @@ -0,0 +1,214 @@ +import type { FilePath, UXStat } from "../../../lib/src/common/types"; +import type { IFileSystemAdapter } from "../../../lib/src/serviceModules/adapters"; +import { FSAPIPathAdapter } from "./FSAPIPathAdapter"; +import { FSAPITypeGuardAdapter } from "./FSAPITypeGuardAdapter"; +import { FSAPIConversionAdapter } from "./FSAPIConversionAdapter"; +import { FSAPIStorageAdapter } from "./FSAPIStorageAdapter"; +import { FSAPIVaultAdapter } from "./FSAPIVaultAdapter"; +import type { FSAPIFile, FSAPIFolder, FSAPIStat } from "./FSAPITypes"; +import { shareRunningResult } from "octagonal-wheels/concurrency/lock_v2"; + +/** + * Complete file system adapter implementation for FileSystem API + */ +export class FSAPIFileSystemAdapter implements IFileSystemAdapter { + readonly path: FSAPIPathAdapter; + readonly typeGuard: FSAPITypeGuardAdapter; + readonly conversion: FSAPIConversionAdapter; + readonly storage: FSAPIStorageAdapter; + readonly vault: FSAPIVaultAdapter; + + private fileCache = new Map(); + private handleCache = new Map(); + + constructor(private rootHandle: FileSystemDirectoryHandle) { + this.path = new FSAPIPathAdapter(); + this.typeGuard = new FSAPITypeGuardAdapter(); + this.conversion = new FSAPIConversionAdapter(); + this.storage = new FSAPIStorageAdapter(rootHandle); + this.vault = new FSAPIVaultAdapter(rootHandle); + } + + private normalisePath(path: FilePath | string): string { + return this.path.normalisePath(path as string); + } + + /** + * Get file handle for a given path + */ + private async getFileHandleByPath(p: FilePath | string): Promise { + const pathStr = p as string; + + // Check cache first + const cached = this.handleCache.get(pathStr); + if (cached) return cached; + + try { + const parts = pathStr.split("/").filter((part) => part !== ""); + if (parts.length === 0) return null; + + let currentHandle: FileSystemDirectoryHandle = this.rootHandle; + const fileName = parts[parts.length - 1]; + + // Navigate to the parent directory + for (let i = 0; i < parts.length - 1; i++) { + currentHandle = await currentHandle.getDirectoryHandle(parts[i]); + } + + const fileHandle = await currentHandle.getFileHandle(fileName); + this.handleCache.set(pathStr, fileHandle); + return fileHandle; + } catch { + return null; + } + } + + async getAbstractFileByPath(p: FilePath | string): Promise { + const pathStr = this.normalisePath(p); + + const cached = this.fileCache.get(pathStr); + if (cached) { + return cached; + } + + return await this.refreshFile(pathStr); + } + + /** + * + */ + async getAbstractFileByPathInsensitive(p: FilePath | string): Promise { + const pathStr = this.normalisePath(p); + const exact = await this.getAbstractFileByPath(pathStr); + if (exact) { + return exact; + } + // TODO: Refactor: Very, Very heavy. + + const lowerPath = pathStr.toLowerCase(); + for (const [cachedPath, cachedFile] of this.fileCache.entries()) { + if (cachedPath.toLowerCase() === lowerPath) { + return cachedFile; + } + } + + await this.scanDirectory(); + + for (const [cachedPath, cachedFile] of this.fileCache.entries()) { + if (cachedPath.toLowerCase() === lowerPath) { + return cachedFile; + } + } + + return null; + } + + async getFiles(): Promise { + if (this.fileCache.size === 0) { + await this.scanDirectory(); + } + return Array.from(this.fileCache.values()); + } + + async statFromNative(file: FSAPIFile): Promise { + // Refresh stat from the file handle + try { + const fileObject = await file.handle.getFile(); + return { + size: fileObject.size, + mtime: fileObject.lastModified, + ctime: fileObject.lastModified, + type: "file", + }; + } catch { + return file.stat; + } + } + + async reconcileInternalFile(p: string): Promise { + // No-op in webapp version + // This is used by Obsidian to sync internal file metadata + } + + /** + * Refresh file cache for a specific path + */ + async refreshFile(p: string): Promise { + const pathStr = this.normalisePath(p); + const handle = await this.getFileHandleByPath(pathStr); + if (!handle) { + this.fileCache.delete(pathStr); + this.handleCache.delete(pathStr); + return null; + } + + const fileObject = await handle.getFile(); + const file: FSAPIFile = { + path: pathStr as FilePath, + stat: { + size: fileObject.size, + mtime: fileObject.lastModified, + ctime: fileObject.lastModified, + type: "file", + }, + handle: handle, + }; + + this.fileCache.set(pathStr, file); + this.handleCache.set(pathStr, handle); + return file; + } + + /** + * Helper method to recursively scan directory and populate file cache + */ + async scanDirectory(relativePath: string = ""): Promise { + return shareRunningResult("scanDirectory:" + relativePath, async () => { + try { + const parts = relativePath.split("/").filter((part) => part !== ""); + let currentHandle = this.rootHandle; + + for (const part of parts) { + currentHandle = await currentHandle.getDirectoryHandle(part); + } + + // Use AsyncIterator instead of .values() for better compatibility + for await (const [name, entry] of (currentHandle as any).entries()) { + const entryPath = relativePath ? `${relativePath}/${name}` : name; + + if (entry.kind === "directory") { + // Recursively scan subdirectories + await this.scanDirectory(entryPath); + } else if (entry.kind === "file") { + const fileHandle = entry as FileSystemFileHandle; + const fileObject = await fileHandle.getFile(); + + const file: FSAPIFile = { + path: entryPath as FilePath, + stat: { + size: fileObject.size, + mtime: fileObject.lastModified, + ctime: fileObject.lastModified, + type: "file", + }, + handle: fileHandle, + }; + + this.fileCache.set(entryPath, file); + this.handleCache.set(entryPath, fileHandle); + } + } + } catch (error) { + console.error(`Error scanning directory ${relativePath}:`, error); + } + }); + } + + /** + * Clear all caches + */ + clearCache(): void { + this.fileCache.clear(); + this.handleCache.clear(); + } +} diff --git a/src/apps/webapp/adapters/FSAPIPathAdapter.ts b/src/apps/webapp/adapters/FSAPIPathAdapter.ts new file mode 100644 index 0000000..640f3bf --- /dev/null +++ b/src/apps/webapp/adapters/FSAPIPathAdapter.ts @@ -0,0 +1,18 @@ +import type { FilePath } from "../../../lib/src/common/types"; +import type { IPathAdapter } from "../../../lib/src/serviceModules/adapters"; +import type { FSAPIFile } from "./FSAPITypes"; + +/** + * Path adapter implementation for FileSystem API + */ +export class FSAPIPathAdapter implements IPathAdapter { + getPath(file: string | FSAPIFile): FilePath { + return (typeof file === "string" ? file : file.path) as FilePath; + } + + normalisePath(p: string): string { + // Normalize path separators to forward slashes (like Obsidian) + // Remove leading/trailing slashes + return p.replace(/\\/g, "/").replace(/^\/+|\/+$/g, ""); + } +} diff --git a/src/apps/webapp/adapters/FSAPIStorageAdapter.ts b/src/apps/webapp/adapters/FSAPIStorageAdapter.ts new file mode 100644 index 0000000..ec4fb81 --- /dev/null +++ b/src/apps/webapp/adapters/FSAPIStorageAdapter.ts @@ -0,0 +1,210 @@ +import type { UXDataWriteOptions } from "../../../lib/src/common/types"; +import type { IStorageAdapter } from "../../../lib/src/serviceModules/adapters"; +import type { FSAPIStat } from "./FSAPITypes"; + +/** + * Storage adapter implementation for FileSystem API + */ +export class FSAPIStorageAdapter implements IStorageAdapter { + constructor(private rootHandle: FileSystemDirectoryHandle) {} + + /** + * Resolve a path to directory and file handles + */ + private async resolvePath(p: string): Promise<{ + dirHandle: FileSystemDirectoryHandle; + fileName: string; + } | null> { + try { + const parts = p.split("/").filter((part) => part !== ""); + if (parts.length === 0) { + return null; + } + + let currentHandle = this.rootHandle; + const fileName = parts[parts.length - 1]; + + // Navigate to the parent directory + for (let i = 0; i < parts.length - 1; i++) { + currentHandle = await currentHandle.getDirectoryHandle(parts[i]); + } + + return { dirHandle: currentHandle, fileName }; + } catch { + return null; + } + } + + /** + * Get file handle for a given path + */ + private async getFileHandle(p: string): Promise { + const resolved = await this.resolvePath(p); + if (!resolved) return null; + + try { + return await resolved.dirHandle.getFileHandle(resolved.fileName); + } catch { + return null; + } + } + + /** + * Get directory handle for a given path + */ + private async getDirectoryHandle(p: string): Promise { + try { + const parts = p.split("/").filter((part) => part !== ""); + if (parts.length === 0) { + return this.rootHandle; + } + + let currentHandle = this.rootHandle; + for (const part of parts) { + currentHandle = await currentHandle.getDirectoryHandle(part); + } + + return currentHandle; + } catch { + return null; + } + } + + async exists(p: string): Promise { + const fileHandle = await this.getFileHandle(p); + if (fileHandle) return true; + + const dirHandle = await this.getDirectoryHandle(p); + return dirHandle !== null; + } + + async trystat(p: string): Promise { + // Try as file first + const fileHandle = await this.getFileHandle(p); + if (fileHandle) { + const file = await fileHandle.getFile(); + return { + size: file.size, + mtime: file.lastModified, + ctime: file.lastModified, + type: "file", + }; + } + + // Try as directory + const dirHandle = await this.getDirectoryHandle(p); + if (dirHandle) { + return { + size: 0, + mtime: Date.now(), + ctime: Date.now(), + type: "folder", + }; + } + + return null; + } + + async stat(p: string): Promise { + return await this.trystat(p); + } + + async mkdir(p: string): Promise { + const parts = p.split("/").filter((part) => part !== ""); + let currentHandle = this.rootHandle; + + for (const part of parts) { + currentHandle = await currentHandle.getDirectoryHandle(part, { create: true }); + } + } + + async remove(p: string): Promise { + const resolved = await this.resolvePath(p); + if (!resolved) return; + + await resolved.dirHandle.removeEntry(resolved.fileName, { recursive: true }); + } + + async read(p: string): Promise { + const fileHandle = await this.getFileHandle(p); + if (!fileHandle) { + throw new Error(`File not found: ${p}`); + } + + const file = await fileHandle.getFile(); + return await file.text(); + } + + async readBinary(p: string): Promise { + const fileHandle = await this.getFileHandle(p); + if (!fileHandle) { + throw new Error(`File not found: ${p}`); + } + + const file = await fileHandle.getFile(); + return await file.arrayBuffer(); + } + + async write(p: string, data: string, options?: UXDataWriteOptions): Promise { + const resolved = await this.resolvePath(p); + if (!resolved) { + throw new Error(`Invalid path: ${p}`); + } + + // Ensure parent directory exists + await this.mkdir(p.split("/").slice(0, -1).join("/")); + + const fileHandle = await resolved.dirHandle.getFileHandle(resolved.fileName, { create: true }); + const writable = await fileHandle.createWritable(); + await writable.write(data); + await writable.close(); + } + + async writeBinary(p: string, data: ArrayBuffer, options?: UXDataWriteOptions): Promise { + const resolved = await this.resolvePath(p); + if (!resolved) { + throw new Error(`Invalid path: ${p}`); + } + + // Ensure parent directory exists + await this.mkdir(p.split("/").slice(0, -1).join("/")); + + const fileHandle = await resolved.dirHandle.getFileHandle(resolved.fileName, { create: true }); + const writable = await fileHandle.createWritable(); + await writable.write(data); + await writable.close(); + } + + async append(p: string, data: string, options?: UXDataWriteOptions): Promise { + const existing = await this.exists(p); + if (existing) { + const currentContent = await this.read(p); + await this.write(p, currentContent + data, options); + } else { + await this.write(p, data, options); + } + } + + async list(basePath: string): Promise<{ files: string[]; folders: string[] }> { + const dirHandle = await this.getDirectoryHandle(basePath); + if (!dirHandle) { + return { files: [], folders: [] }; + } + + const files: string[] = []; + const folders: string[] = []; + + // Use AsyncIterator instead of .values() for better compatibility + for await (const [name, entry] of (dirHandle as any).entries()) { + const entryPath = basePath ? `${basePath}/${name}` : name; + + if (entry.kind === "directory") { + folders.push(entryPath); + } else if (entry.kind === "file") { + files.push(entryPath); + } + } + + return { files, folders }; + } +} diff --git a/src/apps/webapp/adapters/FSAPITypeGuardAdapter.ts b/src/apps/webapp/adapters/FSAPITypeGuardAdapter.ts new file mode 100644 index 0000000..49e34a8 --- /dev/null +++ b/src/apps/webapp/adapters/FSAPITypeGuardAdapter.ts @@ -0,0 +1,17 @@ +import type { ITypeGuardAdapter } from "../../../lib/src/serviceModules/adapters"; +import type { FSAPIFile, FSAPIFolder } from "./FSAPITypes"; + +/** + * Type guard adapter implementation for FileSystem API + */ +export class FSAPITypeGuardAdapter implements ITypeGuardAdapter { + isFile(file: any): file is FSAPIFile { + return ( + file && typeof file === "object" && "path" in file && "stat" in file && "handle" in file && !file.isFolder + ); + } + + isFolder(item: any): item is FSAPIFolder { + return item && typeof item === "object" && "path" in item && item.isFolder === true && "handle" in item; + } +} diff --git a/src/apps/webapp/adapters/FSAPITypes.ts b/src/apps/webapp/adapters/FSAPITypes.ts new file mode 100644 index 0000000..fd696a9 --- /dev/null +++ b/src/apps/webapp/adapters/FSAPITypes.ts @@ -0,0 +1,24 @@ +import type { FilePath, UXStat } from "../../../lib/src/common/types"; + +/** + * FileSystem API file representation + */ +export type FSAPIFile = { + path: FilePath; + stat: UXStat; + handle: FileSystemFileHandle; +}; + +/** + * FileSystem API folder representation + */ +export type FSAPIFolder = { + path: FilePath; + isFolder: true; + handle: FileSystemDirectoryHandle; +}; + +/** + * FileSystem API stat type (compatible with UXStat) + */ +export type FSAPIStat = UXStat; diff --git a/src/apps/webapp/adapters/FSAPIVaultAdapter.ts b/src/apps/webapp/adapters/FSAPIVaultAdapter.ts new file mode 100644 index 0000000..c709e47 --- /dev/null +++ b/src/apps/webapp/adapters/FSAPIVaultAdapter.ts @@ -0,0 +1,123 @@ +import type { FilePath, UXDataWriteOptions } from "../../../lib/src/common/types"; +import type { IVaultAdapter } from "../../../lib/src/serviceModules/adapters"; +import type { FSAPIFile, FSAPIFolder } from "./FSAPITypes"; + +/** + * Vault adapter implementation for FileSystem API + */ +export class FSAPIVaultAdapter implements IVaultAdapter { + constructor(private rootHandle: FileSystemDirectoryHandle) {} + + async read(file: FSAPIFile): Promise { + const fileObject = await file.handle.getFile(); + return await fileObject.text(); + } + + async cachedRead(file: FSAPIFile): Promise { + // No caching in webapp version, just read directly + return await this.read(file); + } + + async readBinary(file: FSAPIFile): Promise { + const fileObject = await file.handle.getFile(); + return await fileObject.arrayBuffer(); + } + + async modify(file: FSAPIFile, data: string, options?: UXDataWriteOptions): Promise { + const writable = await file.handle.createWritable(); + await writable.write(data); + await writable.close(); + } + + async modifyBinary(file: FSAPIFile, data: ArrayBuffer, options?: UXDataWriteOptions): Promise { + const writable = await file.handle.createWritable(); + await writable.write(data); + await writable.close(); + } + + async create(p: string, data: string, options?: UXDataWriteOptions): Promise { + const parts = p.split("/").filter((part) => part !== ""); + const fileName = parts[parts.length - 1]; + + // Navigate to parent directory, creating as needed + let currentHandle = this.rootHandle; + for (let i = 0; i < parts.length - 1; i++) { + currentHandle = await currentHandle.getDirectoryHandle(parts[i], { create: true }); + } + + // Create the file + const fileHandle = await currentHandle.getFileHandle(fileName, { create: true }); + const writable = await fileHandle.createWritable(); + await writable.write(data); + await writable.close(); + + // Get file metadata + const fileObject = await fileHandle.getFile(); + + return { + path: p as FilePath, + stat: { + size: fileObject.size, + mtime: fileObject.lastModified, + ctime: fileObject.lastModified, + type: "file", + }, + handle: fileHandle, + }; + } + + async createBinary(p: string, data: ArrayBuffer, options?: UXDataWriteOptions): Promise { + const parts = p.split("/").filter((part) => part !== ""); + const fileName = parts[parts.length - 1]; + + // Navigate to parent directory, creating as needed + let currentHandle = this.rootHandle; + for (let i = 0; i < parts.length - 1; i++) { + currentHandle = await currentHandle.getDirectoryHandle(parts[i], { create: true }); + } + + // Create the file + const fileHandle = await currentHandle.getFileHandle(fileName, { create: true }); + const writable = await fileHandle.createWritable(); + await writable.write(data); + await writable.close(); + + // Get file metadata + const fileObject = await fileHandle.getFile(); + + return { + path: p as FilePath, + stat: { + size: fileObject.size, + mtime: fileObject.lastModified, + ctime: fileObject.lastModified, + type: "file", + }, + handle: fileHandle, + }; + } + + async delete(file: FSAPIFile | FSAPIFolder, force = false): Promise { + const parts = file.path.split("/").filter((part) => part !== ""); + const name = parts[parts.length - 1]; + + // Navigate to parent directory + let currentHandle = this.rootHandle; + for (let i = 0; i < parts.length - 1; i++) { + currentHandle = await currentHandle.getDirectoryHandle(parts[i]); + } + + // Remove the entry + await currentHandle.removeEntry(name, { recursive: force }); + } + + async trash(file: FSAPIFile | FSAPIFolder, force = false): Promise { + // In webapp, trash is the same as delete (no recycle bin) + await this.delete(file, force); + } + + trigger(name: string, ...data: any[]): any { + // No-op in webapp version (no event system yet) + return undefined; + } +} diff --git a/src/apps/webapp/index.html b/src/apps/webapp/index.html new file mode 100644 index 0000000..72acdc3 --- /dev/null +++ b/src/apps/webapp/index.html @@ -0,0 +1,209 @@ + + + + + + Self-hosted LiveSync WebApp + + + +
+

🔄 Self-hosted LiveSync

+

Browser-based Self-hosted LiveSync using FileSystem API

+ +
+ Initialising... +
+ +
+

About This Application

+
    +
  • Runs entirely in your browser
  • +
  • Uses FileSystem API to access your local vault
  • +
  • Syncs with CouchDB server (like Obsidian plugin)
  • +
  • Settings stored in .livesync/settings.json
  • +
  • Real-time file watching with FileSystemObserver (Chrome 124+)
  • +
+
+ +
+

How to Use

+
    +
  • Grant directory access when prompted
  • +
  • Create .livesync/settings.json in your vault folder. (Compatible with Obsidian's Self-hosted LiveSync)
  • +
  • Add your CouchDB connection details
  • +
  • Your files will be synced automatically
  • +
+
+ + + + +
+ + + + diff --git a/src/apps/webapp/main.ts b/src/apps/webapp/main.ts new file mode 100644 index 0000000..b723395 --- /dev/null +++ b/src/apps/webapp/main.ts @@ -0,0 +1,345 @@ +/** + * Self-hosted LiveSync WebApp + * Browser-based version of Self-hosted LiveSync plugin using FileSystem API + */ + +import { BrowserServiceHub } from "../../lib/src/services/BrowserServices"; +import { LiveSyncBaseCore } from "../../LiveSyncBaseCore"; +import { ServiceContext } from "../../lib/src/services/base/ServiceBase"; +import { initialiseServiceModulesFSAPI } from "./serviceModules/FSAPIServiceModules"; +import type { ObsidianLiveSyncSettings } from "../../lib/src/common/types"; +import type { BrowserAPIService } from "../../lib/src/services/implements/browser/BrowserAPIService"; +import type { InjectableSettingService } from "../../lib/src/services/implements/injectable/InjectableSettingService"; +// import { SetupManager } from "@/modules/features/SetupManager"; +// import { ModuleObsidianSettingsAsMarkdown } from "@/modules/features/ModuleObsidianSettingAsMarkdown"; +// import { ModuleSetupObsidian } from "@/modules/features/ModuleSetupObsidian"; +// import { ModuleObsidianMenu } from "@/modules/essentialObsidian/ModuleObsidianMenu"; + +const SETTINGS_DIR = ".livesync"; +const SETTINGS_FILE = "settings.json"; +const DB_NAME = "livesync-webapp"; + +/** + * Default settings for the webapp + */ +const DEFAULT_SETTINGS: Partial = { + liveSync: false, + syncOnSave: true, + syncOnStart: false, + savingDelay: 200, + lessInformationInLog: false, + gcDelay: 0, + periodicReplication: false, + periodicReplicationInterval: 60, + isConfigured: false, + // CouchDB settings - user needs to configure these + couchDB_URI: "", + couchDB_USER: "", + couchDB_PASSWORD: "", + couchDB_DBNAME: "", + // Disable features not needed in webapp + usePluginSync: false, + autoSweepPlugins: false, + autoSweepPluginsPeriodic: false, +}; + +class LiveSyncWebApp { + private rootHandle: FileSystemDirectoryHandle | null = null; + private core: LiveSyncBaseCore | null = null; + private serviceHub: BrowserServiceHub | null = null; + + async initialize() { + console.log("Self-hosted LiveSync WebApp"); + console.log("Initializing..."); + + // Request directory access + await this.requestDirectoryAccess(); + + if (!this.rootHandle) { + throw new Error("Failed to get directory access"); + } + + console.log(`Vault directory: ${this.rootHandle.name}`); + + // Create service context and hub + const context = new ServiceContext(); + this.serviceHub = new BrowserServiceHub(); + + // Setup API service + (this.serviceHub.API as BrowserAPIService).getSystemVaultName.setHandler( + () => this.rootHandle?.name || "livesync-webapp" + ); + + // Setup settings handlers - save to .livesync folder + const settingService = this.serviceHub.setting as InjectableSettingService; + + settingService.saveData.setHandler(async (data: ObsidianLiveSyncSettings) => { + try { + await this.saveSettingsToFile(data); + console.log("[Settings] Saved to .livesync/settings.json"); + } catch (error) { + console.error("[Settings] Failed to save:", error); + } + }); + + settingService.loadData.setHandler(async (): Promise => { + try { + const data = await this.loadSettingsFromFile(); + if (data) { + console.log("[Settings] Loaded from .livesync/settings.json"); + return { ...DEFAULT_SETTINGS, ...data } as ObsidianLiveSyncSettings; + } + } catch (error) { + console.log("[Settings] Failed to load, using defaults"); + } + return DEFAULT_SETTINGS as ObsidianLiveSyncSettings; + }); + + // Create LiveSync core + this.core = new LiveSyncBaseCore( + this.serviceHub, + (core, serviceHub) => { + return initialiseServiceModulesFSAPI(this.rootHandle!, core, serviceHub); + }, + (core) => [ + // new ModuleObsidianEvents(this, core), + // new ModuleObsidianSettingDialogue(this, core), + // new ModuleObsidianMenu(core), + // new ModuleSetupObsidian(core), + // new ModuleObsidianSettingsAsMarkdown(core), + // new ModuleLog(this, core), + // new ModuleObsidianDocumentHistory(this, core), + // new ModuleInteractiveConflictResolver(this, core), + // new ModuleObsidianGlobalHistory(this, core), + // new ModuleDev(this, core), + // new ModuleReplicateTest(this, core), + // new ModuleIntegratedTest(this, core), + // new SetupManager(core), + ], + () => [],// No add-ons + () => [], + ); + + // Start the core + await this.start(); + } + + private async saveSettingsToFile(data: ObsidianLiveSyncSettings): Promise { + if (!this.rootHandle) return; + + try { + // Create .livesync directory if it doesn't exist + const livesyncDir = await this.rootHandle.getDirectoryHandle(SETTINGS_DIR, { create: true }); + + // Create/overwrite settings.json + const fileHandle = await livesyncDir.getFileHandle(SETTINGS_FILE, { create: true }); + const writable = await fileHandle.createWritable(); + await writable.write(JSON.stringify(data, null, 2)); + await writable.close(); + } catch (error) { + console.error("[Settings] Error saving to file:", error); + throw error; + } + } + + private async loadSettingsFromFile(): Promise | null> { + if (!this.rootHandle) return null; + + try { + const livesyncDir = await this.rootHandle.getDirectoryHandle(SETTINGS_DIR); + const fileHandle = await livesyncDir.getFileHandle(SETTINGS_FILE); + const file = await fileHandle.getFile(); + const text = await file.text(); + return JSON.parse(text); + } catch (error) { + // File doesn't exist yet + return null; + } + } + + private async requestDirectoryAccess() { + try { + // Check if we have a cached directory handle + const cached = await this.loadCachedDirectoryHandle(); + if (cached) { + // Verify permission (cast to any for compatibility) + try { + const permission = await (cached as any).queryPermission({ mode: "readwrite" }); + if (permission === "granted") { + this.rootHandle = cached; + console.log("[Directory] Using cached directory handle"); + return; + } + } catch (e) { + // queryPermission might not be supported, try to use anyway + console.log("[Directory] Could not verify permission, requesting new access"); + } + } + + // Request new directory access + console.log("[Directory] Requesting directory access..."); + this.rootHandle = await (window as any).showDirectoryPicker({ + mode: "readwrite", + startIn: "documents", + }); + + // Save the handle for next time + await this.saveCachedDirectoryHandle(this.rootHandle); + console.log("[Directory] Directory access granted"); + } catch (error) { + console.error("[Directory] Failed to get directory access:", error); + throw error; + } + } + + private async saveCachedDirectoryHandle(handle: FileSystemDirectoryHandle) { + try { + // Use IndexedDB to store the directory handle + const db = await this.openHandleDB(); + const transaction = db.transaction(["handles"], "readwrite"); + const store = transaction.objectStore("handles"); + await new Promise((resolve, reject) => { + const request = store.put(handle, "rootHandle"); + request.onsuccess = resolve; + request.onerror = reject; + }); + db.close(); + } catch (error) { + console.error("[Directory] Failed to cache handle:", error); + } + } + + private async loadCachedDirectoryHandle(): Promise { + try { + const db = await this.openHandleDB(); + const transaction = db.transaction(["handles"], "readonly"); + const store = transaction.objectStore("handles"); + const handle = await new Promise((resolve, reject) => { + const request = store.get("rootHandle"); + request.onsuccess = () => resolve(request.result || null); + request.onerror = reject; + }); + db.close(); + return handle; + } catch (error) { + console.error("[Directory] Failed to load cached handle:", error); + return null; + } + } + + private async openHandleDB(): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open("livesync-webapp-handles", 1); + request.onerror = () => reject(request.error); + request.onsuccess = () => resolve(request.result); + request.onupgradeneeded = (event) => { + const db = (event.target as IDBOpenDBRequest).result; + if (!db.objectStoreNames.contains("handles")) { + db.createObjectStore("handles"); + } + }; + }); + } + + private async start() { + if (!this.core) { + throw new Error("Core not initialized"); + } + + try { + console.log("[Starting] Initializing LiveSync..."); + + const loadResult = await this.core.services.control.onLoad(); + if (!loadResult) { + console.error("[Error] Failed to initialize LiveSync"); + this.showError("Failed to initialize LiveSync"); + return; + } + + await this.core.services.control.onReady(); + + console.log("[Ready] LiveSync is running"); + + // Check if configured + const settings = this.core.services.setting.currentSettings(); + if (!settings.isConfigured) { + console.warn("[Warning] LiveSync is not configured yet"); + this.showWarning("Please configure CouchDB connection in settings"); + } else { + console.log("[Info] LiveSync is configured and ready"); + console.log(`[Info] Database: ${settings.couchDB_URI}/${settings.couchDB_DBNAME}`); + this.showSuccess("LiveSync is ready!"); + } + + // Scan the directory to populate file cache + const fileAccess = (this.core as any)._serviceModules?.storageAccess?.vaultAccess; + if (fileAccess?.fsapiAdapter) { + console.log("[Scanning] Scanning vault directory..."); + await fileAccess.fsapiAdapter.scanDirectory(); + const files = await fileAccess.fsapiAdapter.getFiles(); + console.log(`[Scanning] Found ${files.length} files`); + } + } catch (error) { + console.error("[Error] Failed to start:", error); + this.showError(`Failed to start: ${error}`); + } + } + + async shutdown() { + if (this.core) { + console.log("[Shutdown] Shutting down..."); + + // Stop file watching + const storageEventManager = (this.core as any)._serviceModules?.storageAccess?.storageEventManager; + if (storageEventManager?.cleanup) { + await storageEventManager.cleanup(); + } + + await this.core.services.control.onUnload(); + console.log("[Shutdown] Complete"); + } + } + + private showError(message: string) { + const statusEl = document.getElementById("status"); + if (statusEl) { + statusEl.className = "error"; + statusEl.textContent = `Error: ${message}`; + } + } + + private showWarning(message: string) { + const statusEl = document.getElementById("status"); + if (statusEl) { + statusEl.className = "warning"; + statusEl.textContent = `Warning: ${message}`; + } + } + + private showSuccess(message: string) { + const statusEl = document.getElementById("status"); + if (statusEl) { + statusEl.className = "success"; + statusEl.textContent = message; + } + } +} + +// Initialize on load +const app = new LiveSyncWebApp(); + +window.addEventListener("load", async () => { + try { + await app.initialize(); + } catch (error) { + console.error("Failed to initialize:", error); + } +}); + +// Handle page unload +window.addEventListener("beforeunload", () => { + void app.shutdown(); +}); + +// Export for debugging +(window as any).livesyncApp = app; diff --git a/src/apps/webapp/managers/FSAPIStorageEventManagerAdapter.ts b/src/apps/webapp/managers/FSAPIStorageEventManagerAdapter.ts new file mode 100644 index 0000000..5977c58 --- /dev/null +++ b/src/apps/webapp/managers/FSAPIStorageEventManagerAdapter.ts @@ -0,0 +1,281 @@ +import type { FilePath, UXFileInfoStub, UXInternalFileInfoStub } from "../../../lib/src/common/types"; +import type { FileEventItem } from "../../../lib/src/common/types"; +import type { IStorageEventManagerAdapter } from "../../../lib/src/managers/adapters"; +import type { + IStorageEventTypeGuardAdapter, + IStorageEventPersistenceAdapter, + IStorageEventWatchAdapter, + IStorageEventStatusAdapter, + IStorageEventConverterAdapter, + IStorageEventWatchHandlers, +} from "../../../lib/src/managers/adapters"; +import type { FileEventItemSentinel } from "../../../lib/src/managers/StorageEventManager"; +import type { FSAPIFile, FSAPIFolder } from "../adapters/FSAPITypes"; + +/** + * FileSystem API-specific type guard adapter + */ +class FSAPITypeGuardAdapter implements IStorageEventTypeGuardAdapter { + isFile(file: any): file is FSAPIFile { + return ( + file && typeof file === "object" && "path" in file && "stat" in file && "handle" in file && !file.isFolder + ); + } + + isFolder(item: any): item is FSAPIFolder { + return item && typeof item === "object" && "path" in item && item.isFolder === true && "handle" in item; + } +} + +/** + * FileSystem API-specific persistence adapter (IndexedDB-based snapshot) + */ +class FSAPIPersistenceAdapter implements IStorageEventPersistenceAdapter { + private dbName = "livesync-webapp-snapshot"; + private storeName = "snapshots"; + private snapshotKey = "file-events"; + + private async openDB(): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(this.dbName, 1); + + request.onerror = () => reject(request.error); + request.onsuccess = () => resolve(request.result); + + request.onupgradeneeded = (event) => { + const db = (event.target as IDBOpenDBRequest).result; + if (!db.objectStoreNames.contains(this.storeName)) { + db.createObjectStore(this.storeName); + } + }; + }); + } + + async saveSnapshot(snapshot: (FileEventItem | FileEventItemSentinel)[]): Promise { + try { + const db = await this.openDB(); + const transaction = db.transaction([this.storeName], "readwrite"); + const store = transaction.objectStore(this.storeName); + + await new Promise((resolve, reject) => { + const request = store.put(snapshot, this.snapshotKey); + request.onsuccess = () => resolve(); + request.onerror = () => reject(request.error); + }); + + db.close(); + } catch (error) { + console.error("Failed to save snapshot:", error); + } + } + + async loadSnapshot(): Promise<(FileEventItem | FileEventItemSentinel)[] | null> { + try { + const db = await this.openDB(); + const transaction = db.transaction([this.storeName], "readonly"); + const store = transaction.objectStore(this.storeName); + + const result = await new Promise<(FileEventItem | FileEventItemSentinel)[] | null>((resolve, reject) => { + const request = store.get(this.snapshotKey); + request.onsuccess = () => resolve(request.result || null); + request.onerror = () => reject(request.error); + }); + + db.close(); + return result; + } catch { + return null; + } + } +} + +/** + * FileSystem API-specific status adapter (console logging) + */ +class FSAPIStatusAdapter 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; + } + } +} + +/** + * FileSystem API-specific converter adapter + */ +class FSAPIConverterAdapter implements IStorageEventConverterAdapter { + toFileInfo(file: FSAPIFile, deleted?: boolean): UXFileInfoStub { + const pathParts = file.path.split("/"); + const name = pathParts[pathParts.length - 1] || file.handle.name; + + return { + name: name, + path: file.path, + stat: file.stat, + deleted: deleted, + isFolder: false, + }; + } + + toInternalFileInfo(p: FilePath): UXInternalFileInfoStub { + const pathParts = p.split("/"); + const name = pathParts[pathParts.length - 1] || ""; + + return { + name: name, + path: p, + isInternal: true, + stat: undefined, + }; + } +} + +/** + * FileSystem API-specific watch adapter using FileSystemObserver (Chrome only) + */ +class FSAPIWatchAdapter implements IStorageEventWatchAdapter { + private observer: any = null; // FileSystemObserver type + + constructor(private rootHandle: FileSystemDirectoryHandle) {} + + async beginWatch(handlers: IStorageEventWatchHandlers): Promise { + // Use FileSystemObserver if available (Chrome 124+) + if (typeof (window as any).FileSystemObserver === "undefined") { + console.log("[FSAPIWatchAdapter] FileSystemObserver not available, file watching disabled"); + console.log("[FSAPIWatchAdapter] Consider using Chrome 124+ for real-time file watching"); + return Promise.resolve(); + } + + try { + const FileSystemObserver = (window as any).FileSystemObserver; + + this.observer = new FileSystemObserver(async (records: any[]) => { + for (const record of records) { + const handle = record.root; + const changedHandle = record.changedHandle; + const relativePathComponents = record.relativePathComponents; + const type = record.type; // "appeared", "disappeared", "modified", "moved", "unknown", "errored" + + // Build relative path + const relativePath = relativePathComponents ? relativePathComponents.join("/") : ""; + + // Skip .livesync directory to avoid infinite loops + if (relativePath.startsWith(".livesync/") || relativePath === ".livesync") { + continue; + } + + console.log(`[FileSystemObserver] ${type}: ${relativePath}`); + + // Convert to our event handlers + try { + if (type === "appeared" || type === "modified") { + if (changedHandle && changedHandle.kind === "file") { + const file = await changedHandle.getFile(); + const fileInfo = { + path: relativePath as any, + stat: { + size: file.size, + mtime: file.lastModified, + ctime: file.lastModified, + type: "file" as const, + }, + handle: changedHandle, + }; + + if (type === "appeared") { + await handlers.onCreate(fileInfo, undefined); + } else { + await handlers.onChange(fileInfo, undefined); + } + } + } else if (type === "disappeared") { + const fileInfo = { + path: relativePath as any, + stat: { + size: 0, + mtime: Date.now(), + ctime: Date.now(), + type: "file" as const, + }, + handle: null as any, + }; + await handlers.onDelete(fileInfo, undefined); + } else if (type === "moved") { + // Handle as delete + create + // Note: FileSystemObserver provides both old and new paths in some cases + // For simplicity, we'll treat it as a modification + if (changedHandle && changedHandle.kind === "file") { + const file = await changedHandle.getFile(); + const fileInfo = { + path: relativePath as any, + stat: { + size: file.size, + mtime: file.lastModified, + ctime: file.lastModified, + type: "file" as const, + }, + handle: changedHandle, + }; + await handlers.onChange(fileInfo, undefined); + } + } + } catch (error) { + console.error( + `[FileSystemObserver] Error processing ${type} event for ${relativePath}:`, + error + ); + } + } + }); + + // Start observing + await this.observer.observe(this.rootHandle, { recursive: true }); + console.log("[FSAPIWatchAdapter] FileSystemObserver started successfully"); + } catch (error) { + console.error("[FSAPIWatchAdapter] Failed to start FileSystemObserver:", error); + console.log("[FSAPIWatchAdapter] Falling back to manual sync mode"); + } + + return Promise.resolve(); + } + + async stopWatch(): Promise { + if (this.observer) { + try { + this.observer.disconnect(); + this.observer = null; + console.log("[FSAPIWatchAdapter] FileSystemObserver stopped"); + } catch (error) { + console.error("[FSAPIWatchAdapter] Error stopping observer:", error); + } + } + } +} + +/** + * Composite adapter for FileSystem API StorageEventManager + */ +export class FSAPIStorageEventManagerAdapter implements IStorageEventManagerAdapter { + readonly typeGuard: FSAPITypeGuardAdapter; + readonly persistence: FSAPIPersistenceAdapter; + readonly watch: FSAPIWatchAdapter; + readonly status: FSAPIStatusAdapter; + readonly converter: FSAPIConverterAdapter; + + constructor(rootHandle: FileSystemDirectoryHandle) { + this.typeGuard = new FSAPITypeGuardAdapter(); + this.persistence = new FSAPIPersistenceAdapter(); + this.watch = new FSAPIWatchAdapter(rootHandle); + this.status = new FSAPIStatusAdapter(); + this.converter = new FSAPIConverterAdapter(); + } +} diff --git a/src/apps/webapp/managers/StorageEventManagerFSAPI.ts b/src/apps/webapp/managers/StorageEventManagerFSAPI.ts new file mode 100644 index 0000000..1bbc483 --- /dev/null +++ b/src/apps/webapp/managers/StorageEventManagerFSAPI.ts @@ -0,0 +1,39 @@ +import { + StorageEventManagerBase, + type StorageEventManagerBaseDependencies, +} from "../../../lib/src/managers/StorageEventManager"; +import { FSAPIStorageEventManagerAdapter } from "./FSAPIStorageEventManagerAdapter"; +import type { IMinimumLiveSyncCommands, LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; +import type { ServiceContext } from "../../../lib/src/services/base/ServiceBase"; + +export class StorageEventManagerFSAPI extends StorageEventManagerBase { + core: LiveSyncBaseCore; + private fsapiAdapter: FSAPIStorageEventManagerAdapter; + + constructor( + rootHandle: FileSystemDirectoryHandle, + core: LiveSyncBaseCore, + dependencies: StorageEventManagerBaseDependencies + ) { + const adapter = new FSAPIStorageEventManagerAdapter(rootHandle); + super(adapter, dependencies); + this.fsapiAdapter = adapter; + this.core = core; + } + + /** + * Override _watchVaultRawEvents for webapp-specific logic + * In webapp, we don't have internal files like Obsidian's .obsidian folder + */ + protected override async _watchVaultRawEvents(path: string) { + // No-op in webapp version + // Internal file handling is not needed + } + + async cleanup() { + // Stop file watching + if (this.fsapiAdapter?.watch) { + await (this.fsapiAdapter.watch as any).stopWatch?.(); + } + } +} diff --git a/src/apps/webapp/package.json b/src/apps/webapp/package.json new file mode 100644 index 0000000..0e1f5b1 --- /dev/null +++ b/src/apps/webapp/package.json @@ -0,0 +1,21 @@ +{ + "name": "livesync-webapp", + "private": true, + "version": "0.0.1", + "type": "module", + "description": "Browser-based Obsidian LiveSync using FileSystem API", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": {}, + "devDependencies": { + "typescript": "5.9.3", + "vite": "^7.3.1" + }, + "imports": { + "../../src/worker/bgWorker.ts": "../../src/worker/bgWorker.mock.ts", + "@lib/worker/bgWorker.ts": "@lib/worker/bgWorker.mock.ts" + } +} diff --git a/src/apps/webapp/serviceModules/DatabaseFileAccess.ts b/src/apps/webapp/serviceModules/DatabaseFileAccess.ts new file mode 100644 index 0000000..6365042 --- /dev/null +++ b/src/apps/webapp/serviceModules/DatabaseFileAccess.ts @@ -0,0 +1,15 @@ +import { + ServiceDatabaseFileAccessBase, + type ServiceDatabaseFileAccessDependencies, +} from "../../../lib/src/serviceModules/ServiceDatabaseFileAccessBase"; +import type { DatabaseFileAccess } from "../../../lib/src/interfaces/DatabaseFileAccess"; + +/** + * FileSystem API-specific implementation of ServiceDatabaseFileAccess + * Same as Obsidian version, no platform-specific changes needed + */ +export class ServiceDatabaseFileAccessFSAPI extends ServiceDatabaseFileAccessBase implements DatabaseFileAccess { + constructor(services: ServiceDatabaseFileAccessDependencies) { + super(services); + } +} diff --git a/src/apps/webapp/serviceModules/FSAPIServiceModules.ts b/src/apps/webapp/serviceModules/FSAPIServiceModules.ts new file mode 100644 index 0000000..152422c --- /dev/null +++ b/src/apps/webapp/serviceModules/FSAPIServiceModules.ts @@ -0,0 +1,104 @@ +import type { InjectableServiceHub } from "../../../lib/src/services/implements/injectable/InjectableServiceHub"; +import { ServiceRebuilder } from "../../../lib/src/serviceModules/Rebuilder"; +import { ServiceFileHandler } from "../../../serviceModules/FileHandler"; +import { StorageAccessManager } from "../../../lib/src/managers/StorageProcessingManager"; +import type { ServiceModules } from "../../../types"; +import type { LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; +import type { ServiceContext } from "../../../lib/src/services/base/ServiceBase"; +import { FileAccessFSAPI } from "./FileAccessFSAPI"; +import { ServiceFileAccessFSAPI } from "./ServiceFileAccessImpl"; +import { ServiceDatabaseFileAccessFSAPI } from "./DatabaseFileAccess"; +import { StorageEventManagerFSAPI } from "../managers/StorageEventManagerFSAPI"; + +/** + * Initialize service modules for FileSystem API webapp version + * This is the webapp equivalent of ObsidianLiveSyncPlugin.initialiseServiceModules + * + * @param rootHandle - The root FileSystemDirectoryHandle for the vault + * @param core - The LiveSyncBaseCore instance + * @param services - The service hub + * @returns ServiceModules containing all initialized service modules + */ +export function initialiseServiceModulesFSAPI( + rootHandle: FileSystemDirectoryHandle, + core: LiveSyncBaseCore, + services: InjectableServiceHub +): ServiceModules { + const storageAccessManager = new StorageAccessManager(); + + // FileSystem API-specific file access + const vaultAccess = new FileAccessFSAPI(rootHandle, { + storageAccessManager: storageAccessManager, + vaultService: services.vault, + settingService: services.setting, + APIService: services.API, + pathService: services.path, + }); + + // FileSystem API-specific storage event manager + const storageEventManager = new StorageEventManagerFSAPI(rootHandle, core, { + fileProcessing: services.fileProcessing, + setting: services.setting, + vaultService: services.vault, + storageAccessManager: storageAccessManager, + APIService: services.API, + }); + + // Storage access using FileSystem API adapter + const storageAccess = new ServiceFileAccessFSAPI({ + API: services.API, + setting: services.setting, + fileProcessing: services.fileProcessing, + vault: services.vault, + appLifecycle: services.appLifecycle, + storageEventManager: storageEventManager, + storageAccessManager: storageAccessManager, + vaultAccess: vaultAccess, + }); + + // Database file access (platform-independent) + const databaseFileAccess = new ServiceDatabaseFileAccessFSAPI({ + API: services.API, + database: services.database, + path: services.path, + storageAccess: storageAccess, + vault: services.vault, + }); + + // File handler (platform-independent) + const fileHandler = new (ServiceFileHandler as any)({ + API: services.API, + databaseFileAccess: databaseFileAccess, + conflict: services.conflict, + setting: services.setting, + fileProcessing: services.fileProcessing, + vault: services.vault, + path: services.path, + replication: services.replication, + storageAccess: storageAccess, + }); + + // Rebuilder (platform-independent) + const rebuilder = new ServiceRebuilder({ + API: services.API, + database: services.database, + appLifecycle: services.appLifecycle, + setting: services.setting, + remote: services.remote, + databaseEvents: services.databaseEvents, + replication: services.replication, + replicator: services.replicator, + UI: services.UI, + vault: services.vault, + fileHandler: fileHandler, + storageAccess: storageAccess, + control: services.control, + }); + + return { + rebuilder, + fileHandler, + databaseFileAccess, + storageAccess, + }; +} diff --git a/src/apps/webapp/serviceModules/FileAccessFSAPI.ts b/src/apps/webapp/serviceModules/FileAccessFSAPI.ts new file mode 100644 index 0000000..45e5660 --- /dev/null +++ b/src/apps/webapp/serviceModules/FileAccessFSAPI.ts @@ -0,0 +1,20 @@ +import { FileAccessBase, type FileAccessBaseDependencies } from "../../../lib/src/serviceModules/FileAccessBase"; +import { FSAPIFileSystemAdapter } from "../adapters/FSAPIFileSystemAdapter"; + +/** + * FileSystem API-specific implementation of FileAccessBase + * Uses FSAPIFileSystemAdapter for browser file operations + */ +export class FileAccessFSAPI extends FileAccessBase { + constructor(rootHandle: FileSystemDirectoryHandle, dependencies: FileAccessBaseDependencies) { + const adapter = new FSAPIFileSystemAdapter(rootHandle); + super(adapter, dependencies); + } + + /** + * Expose the adapter for accessing scanDirectory and other methods + */ + get fsapiAdapter(): FSAPIFileSystemAdapter { + return this.adapter; + } +} diff --git a/src/apps/webapp/serviceModules/ServiceFileAccessImpl.ts b/src/apps/webapp/serviceModules/ServiceFileAccessImpl.ts new file mode 100644 index 0000000..4b6a474 --- /dev/null +++ b/src/apps/webapp/serviceModules/ServiceFileAccessImpl.ts @@ -0,0 +1,15 @@ +import { + ServiceFileAccessBase, + type StorageAccessBaseDependencies, +} from "../../../lib/src/serviceModules/ServiceFileAccessBase"; +import { FSAPIFileSystemAdapter } from "../adapters/FSAPIFileSystemAdapter"; + +/** + * FileSystem API-specific implementation of ServiceFileAccess + * Uses FSAPIFileSystemAdapter for platform-specific operations + */ +export class ServiceFileAccessFSAPI extends ServiceFileAccessBase { + constructor(services: StorageAccessBaseDependencies) { + super(services); + } +} diff --git a/src/apps/webapp/svelte.config.js b/src/apps/webapp/svelte.config.js new file mode 100644 index 0000000..9a3adfb --- /dev/null +++ b/src/apps/webapp/svelte.config.js @@ -0,0 +1,7 @@ +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; + +export default { + // Consult https://svelte.dev/docs#compile-time-svelte-preprocess + // for more information about preprocessors + preprocess: vitePreprocess(), +}; diff --git a/src/apps/webapp/tsconfig.json b/src/apps/webapp/tsconfig.json new file mode 100644 index 0000000..f79193a --- /dev/null +++ b/src/apps/webapp/tsconfig.json @@ -0,0 +1,32 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + /* Linting */ + "strict": false, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true, + + /* Path mapping */ + "baseUrl": ".", + "paths": { + "@/*": ["../../*"], + "@lib/*": ["../../lib/src/*"] + } + }, + "include": ["*.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules", "dist"] +} diff --git a/src/apps/webapp/vite.config.ts b/src/apps/webapp/vite.config.ts new file mode 100644 index 0000000..ea99b14 --- /dev/null +++ b/src/apps/webapp/vite.config.ts @@ -0,0 +1,34 @@ +import { defineConfig } from "vite"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import path from "node:path"; +import { readFileSync } from "node:fs"; +const packageJson = JSON.parse(readFileSync("../../../package.json", "utf-8")); +const manifestJson = JSON.parse(readFileSync("../../../manifest.json", "utf-8")); +// https://vite.dev/config/ +export default defineConfig({ + plugins: [svelte()], + resolve: { + alias: { + "@": path.resolve(__dirname, "../../"), + "@lib": path.resolve(__dirname, "../../lib/src"), + }, + }, + base: "./", + build: { + outDir: "dist", + emptyOutDir: true, + rollupOptions: { + input: { + index: path.resolve(__dirname, "index.html"), + }, + }, + }, + define: { + MANIFEST_VERSION: JSON.stringify(process.env.MANIFEST_VERSION || manifestJson.version || "0.0.0"), + PACKAGE_VERSION: JSON.stringify(process.env.PACKAGE_VERSION || packageJson.version || "0.0.0"), + }, + server: { + port: 3000, + open: true, + }, +}); diff --git a/src/apps/webpeer/src/P2PReplicatorShim.ts b/src/apps/webpeer/src/P2PReplicatorShim.ts index 73fe39a..735e572 100644 --- a/src/apps/webpeer/src/P2PReplicatorShim.ts +++ b/src/apps/webpeer/src/P2PReplicatorShim.ts @@ -2,6 +2,7 @@ import { PouchDB } from "@lib/pouchdb/pouchdb-browser"; import { type EntryDoc, type LOG_LEVEL, + type ObsidianLiveSyncSettings, type P2PSyncSetting, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, @@ -39,6 +40,7 @@ import type { InjectableVaultServiceCompat } from "@lib/services/implements/inje import { SimpleStoreIDBv2 } from "octagonal-wheels/databases/SimpleStoreIDBv2"; import type { InjectableAPIService } from "@/lib/src/services/implements/injectable/InjectableAPIService"; import type { BrowserAPIService } from "@/lib/src/services/implements/browser/BrowserAPIService"; +import type { InjectableSettingService } from "@/lib/src/services/implements/injectable/InjectableSettingService"; function addToList(item: string, list: string) { return unique( @@ -87,6 +89,22 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { (this.services.API as BrowserAPIService).getSystemVaultName.setHandler( () => "p2p-livesync-web-peer" ); + this.services.API.addLog.setHandler(Logger); + const repStore = SimpleStoreIDBv2.open("p2p-livesync-web-peer"); + this._simpleStore = repStore; + let _settings = { ...P2P_DEFAULT_SETTINGS, additionalSuffixOfDatabaseName: "" } as ObsidianLiveSyncSettings; + this.services.setting.settings = _settings as any; + (this.services.setting as InjectableSettingService).saveData.setHandler(async (data) => { + await repStore.set("settings", data); + eventHub.emitEvent(EVENT_SETTING_SAVED, data); + }); + (this.services.setting as InjectableSettingService).loadData.setHandler(async () => { + const settings = { ..._settings, ...((await repStore.get("settings")) as ObsidianLiveSyncSettings) }; + return settings; + }); + } + get settings() { + return this.services.setting.currentSettings() as P2PSyncSetting; } async init() { // const { simpleStoreAPI } = await getWrappedSynchromesh(); @@ -102,23 +120,27 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { Logger(ex, LOG_LEVEL_VERBOSE); } } - const repStore = SimpleStoreIDBv2.open("p2p-livesync-web-peer"); - this._simpleStore = repStore; - let _settings = (await repStore.get("settings")) || ({ ...P2P_DEFAULT_SETTINGS } as P2PSyncSetting); - this.services.setting.settings = _settings as any; + + await this.services.setting.loadSettings(); this.plugin = { - saveSettings: async () => { - await repStore.set("settings", _settings); - eventHub.emitEvent(EVENT_SETTING_SAVED, _settings); - }, - get settings() { - return _settings; - }, - set settings(newSettings: P2PSyncSetting) { - _settings = { ..._settings, ...newSettings }; - }, - rebuilder: null, + // saveSettings: async () => { + // await repStore.set("settings", _settings); + // eventHub.emitEvent(EVENT_SETTING_SAVED, _settings); + // }, + // get settings() { + // return _settings; + // }, + // set settings(newSettings: P2PSyncSetting) { + // _settings = { ..._settings, ...newSettings }; + // }, + // rebuilder: null, + // core: { + // settings: this.services.setting.settings, + // }, services: this.services, + core: { + services: this.services, + }, // $$scheduleAppReload: () => {}, // $$getVaultName: () => "p2p-livesync-web-peer", }; @@ -132,9 +154,7 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { }, 1000); return this; } - get settings() { - return this.plugin.settings; - } + _log(msg: any, level?: LOG_LEVEL): void { Logger(msg, level); } @@ -334,12 +354,11 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { } } } - this.plugin.settings = remoteConfig; - await this.plugin.saveSettings(); + await this.services.setting.applyPartial(remoteConfig, true); if (yn === DROP) { - await this.plugin.rebuilder.scheduleFetch(); + // await this.plugin.rebuilder.scheduleFetch(); } else { - await this.plugin.services.appLifecycle.scheduleRestart(); + await this.plugin.core.services.appLifecycle.scheduleRestart(); } } else { Logger(`Cancelled\nRemote config for ${peer.name} is not applied`, LOG_LEVEL_NOTICE); @@ -357,13 +376,18 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { } as const; const targetSetting = settingMap[prop]; + const currentSettingAll = this.plugin.core.services.setting.currentSettings(); + const currentSetting = { + [targetSetting]: currentSettingAll ? currentSettingAll[targetSetting] : "", + }; if (peer[prop]) { - this.plugin.settings[targetSetting] = removeFromList(peer.name, this.plugin.settings[targetSetting]); - await this.plugin.saveSettings(); + // this.plugin.settings[targetSetting] = removeFromList(peer.name, this.plugin.settings[targetSetting]); + // await this.plugin.saveSettings(); + currentSetting[targetSetting] = removeFromList(peer.name, currentSetting[targetSetting]); } else { - this.plugin.settings[targetSetting] = addToList(peer.name, this.plugin.settings[targetSetting]); - await this.plugin.saveSettings(); + currentSetting[targetSetting] = addToList(peer.name, currentSetting[targetSetting]); } + await this.plugin.core.services.setting.applyPartial(currentSetting, true); } } diff --git a/src/apps/webpeer/src/SyncMain.svelte b/src/apps/webpeer/src/SyncMain.svelte index 853ce86..538fff3 100644 --- a/src/apps/webpeer/src/SyncMain.svelte +++ b/src/apps/webpeer/src/SyncMain.svelte @@ -27,7 +27,7 @@
{#await synchronised then cmdSync} - + {:catch error}

{error.message}

{/await} diff --git a/src/common/PeriodicProcessor.ts b/src/common/PeriodicProcessor.ts new file mode 100644 index 0000000..04d92ba --- /dev/null +++ b/src/common/PeriodicProcessor.ts @@ -0,0 +1,45 @@ +import { Logger } from "octagonal-wheels/common/logger"; +import { fireAndForget } from "octagonal-wheels/promises"; +import { eventHub, EVENT_PLUGIN_UNLOADED } from "./events"; +import type { NecessaryServices } from "@lib/interfaces/ServiceModule"; +type PeriodicProcessorHost = NecessaryServices<"API" | "control", never>; +export class PeriodicProcessor { + _process: () => Promise; + _timer?: number = undefined; + _core: PeriodicProcessorHost; + constructor(core: PeriodicProcessorHost, process: () => Promise) { + // this._plugin = plugin; + this._core = core; + this._process = process; + eventHub.onceEvent(EVENT_PLUGIN_UNLOADED, () => { + this.disable(); + }); + } + async process() { + try { + await this._process(); + } catch (ex) { + Logger(ex); + } + } + enable(interval: number) { + this.disable(); + if (interval == 0) return; + this._timer = this._core.services.API.setInterval( + () => + fireAndForget(async () => { + await this.process(); + if (this._core.services?.control?.hasUnloaded()) { + this.disable(); + } + }), + interval + ); + } + disable() { + if (this._timer !== undefined) { + this._core.services.API.clearInterval(this._timer); + this._timer = undefined; + } + } +} diff --git a/src/common/events.ts b/src/common/events.ts index 0372882..e34346f 100644 --- a/src/common/events.ts +++ b/src/common/events.ts @@ -1,5 +1,5 @@ import { eventHub } from "../lib/src/hub/hub"; -import type ObsidianLiveSyncPlugin from "../main"; +// import type ObsidianLiveSyncPlugin from "../main"; export const EVENT_PLUGIN_LOADED = "plugin-loaded"; export const EVENT_PLUGIN_UNLOADED = "plugin-unloaded"; @@ -29,7 +29,7 @@ export const EVENT_REQUEST_PERFORM_GC_V3 = "request-perform-gc-v3"; declare global { interface LSEvents { - [EVENT_PLUGIN_LOADED]: ObsidianLiveSyncPlugin; + [EVENT_PLUGIN_LOADED]: undefined; [EVENT_PLUGIN_UNLOADED]: undefined; [EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG]: undefined; [EVENT_REQUEST_OPEN_SETTINGS]: undefined; diff --git a/src/common/utils.ts b/src/common/utils.ts index a64c26e..eacc758 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -24,13 +24,10 @@ import { type UXFileInfoStub, } from "../lib/src/common/types.ts"; export { ICHeader, ICXHeader } from "./types.ts"; -import type ObsidianLiveSyncPlugin from "../main.ts"; import { writeString } from "../lib/src/string_and_binary/convert.ts"; -import { fireAndForget } from "../lib/src/common/utils.ts"; import { sameChangePairs } from "./stores.ts"; import { scheduleTask } from "octagonal-wheels/concurrency/task"; -import { EVENT_PLUGIN_UNLOADED, eventHub } from "./events.ts"; import { AuthorizationHeaderGenerator } from "../lib/src/replication/httplib.ts"; import type { KeyValueDatabase } from "../lib/src/interfaces/KeyValueDatabase.ts"; @@ -126,47 +123,6 @@ export { stripInternalMetadataPrefix, } from "@lib/common/typeUtils.ts"; -export class PeriodicProcessor { - _process: () => Promise; - _timer?: number = undefined; - _plugin: ObsidianLiveSyncPlugin; - constructor(plugin: ObsidianLiveSyncPlugin, process: () => Promise) { - this._plugin = plugin; - this._process = process; - eventHub.onceEvent(EVENT_PLUGIN_UNLOADED, () => { - this.disable(); - }); - } - async process() { - try { - await this._process(); - } catch (ex) { - Logger(ex); - } - } - enable(interval: number) { - this.disable(); - if (interval == 0) return; - this._timer = window.setInterval( - () => - fireAndForget(async () => { - await this.process(); - if (this._plugin.services?.control?.hasUnloaded()) { - this.disable(); - } - }), - interval - ); - this._plugin.registerInterval(this._timer); - } - disable() { - if (this._timer !== undefined) { - window.clearInterval(this._timer); - this._timer = undefined; - } - } -} - export const _requestToCouchDBFetch = async ( baseUri: string, username: string, @@ -373,11 +329,6 @@ export function disposeAllMemo() { _cached.clear(); } -export function displayRev(rev: string) { - const [number, hash] = rev.split("-"); - return `${number}-${hash.substring(0, 6)}`; -} - export function getLogLevel(showNotice: boolean) { return showNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO; } @@ -446,3 +397,5 @@ export function onlyInNTimes(n: number, proc: (progress: number) => any) { } }; } + +export { displayRev } from "@lib/common/utils.ts"; diff --git a/src/features/ConfigSync/CmdConfigSync.ts b/src/features/ConfigSync/CmdConfigSync.ts index 0bc732b..c98142c 100644 --- a/src/features/ConfigSync/CmdConfigSync.ts +++ b/src/features/ConfigSync/CmdConfigSync.ts @@ -50,7 +50,6 @@ import { LiveSyncCommands } from "../LiveSyncCommands.ts"; import { stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts"; import { EVEN, - PeriodicProcessor, disposeMemoObject, isCustomisationSyncMetadata, isPluginMetadata, @@ -59,6 +58,7 @@ import { retrieveMemoObject, scheduleTask, } from "../../common/utils.ts"; +import { PeriodicProcessor } from "@/common/PeriodicProcessor.ts"; import { JsonResolveModal } from "../HiddenFileCommon/JsonResolveModal.ts"; import { QueueProcessor } from "octagonal-wheels/concurrency/processor"; import { pluginScanningCount } from "../../lib/src/mock_and_interop/stores.ts"; @@ -392,29 +392,32 @@ export type PluginDataEx = { }; export class ConfigSync extends LiveSyncCommands { - constructor(plugin: ObsidianLiveSyncPlugin) { - super(plugin); + constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) { + super(plugin, core); pluginScanningCount.onChanged((e) => { const total = e.value; pluginIsEnumerating.set(total != 0); }); } + get configDir() { + return this.core.services.API.getSystemConfigDir(); + } get kvDB() { - return this.plugin.kvDB; + return this.core.kvDB; } get useV2() { - return this.plugin.settings.usePluginSyncV2; + return this.core.settings.usePluginSyncV2; } get useSyncPluginEtc() { - return this.plugin.settings.usePluginEtc; + return this.core.settings.usePluginEtc; } isThisModuleEnabled() { - return this.plugin.settings.usePluginSync; + return this.core.settings.usePluginSync; } pluginDialog?: PluginDialogModal = undefined; - periodicPluginSweepProcessor = new PeriodicProcessor(this.plugin, async () => await this.scanAllConfigFiles(false)); + periodicPluginSweepProcessor = new PeriodicProcessor(this.core, async () => await this.scanAllConfigFiles(false)); pluginList: IPluginDataExDisplay[] = []; showPluginSyncModal() { @@ -439,7 +442,7 @@ export class ConfigSync extends LiveSyncCommands { this.hidePluginSyncModal(); this.periodicPluginSweepProcessor?.disable(); } - addRibbonIcon = this.plugin.addRibbonIcon.bind(this.plugin); + addRibbonIcon = this.services.API.addRibbonIcon.bind(this.services.API); onload() { addIcon( "custom-sync", @@ -447,7 +450,7 @@ export class ConfigSync extends LiveSyncCommands { ` ); - this.plugin.addCommand({ + this.services.API.addCommand({ id: "livesync-plugin-dialog-ex", name: "Show customization sync dialog", callback: () => { @@ -464,10 +467,9 @@ export class ConfigSync extends LiveSyncCommands { filePath: string ): "CONFIG" | "THEME" | "SNIPPET" | "PLUGIN_MAIN" | "PLUGIN_ETC" | "PLUGIN_DATA" | "" { if (filePath.split("/").length == 2 && filePath.endsWith(".json")) return "CONFIG"; - if (filePath.split("/").length == 4 && filePath.startsWith(`${this.app.vault.configDir}/themes/`)) - return "THEME"; - if (filePath.startsWith(`${this.app.vault.configDir}/snippets/`) && filePath.endsWith(".css")) return "SNIPPET"; - if (filePath.startsWith(`${this.app.vault.configDir}/plugins/`)) { + if (filePath.split("/").length == 4 && filePath.startsWith(`${this.configDir}/themes/`)) return "THEME"; + if (filePath.startsWith(`${this.configDir}/snippets/`) && filePath.endsWith(".css")) return "SNIPPET"; + if (filePath.startsWith(`${this.configDir}/plugins/`)) { if ( filePath.endsWith("/styles.css") || filePath.endsWith("/manifest.json") || @@ -485,7 +487,7 @@ export class ConfigSync extends LiveSyncCommands { return ""; } isTargetPath(filePath: string): boolean { - if (!filePath.startsWith(this.app.vault.configDir)) return false; + if (!filePath.startsWith(this.configDir)) return false; // Idea non-filter option? return this.getFileCategory(filePath) != ""; } @@ -854,7 +856,7 @@ export class ConfigSync extends LiveSyncCommands { children: [], eden: {}, }; - const r = await this.plugin.localDatabase.putDBEntry(saving); + const r = await this.core.localDatabase.putDBEntry(saving); if (r && r.ok) { this._log(`Migrated ${v1Path} / ${f.filename} to ${v2Path}`, LOG_LEVEL_INFO); const delR = await this.deleteConfigOnDatabase(v1Path); @@ -996,16 +998,16 @@ export class ConfigSync extends LiveSyncCommands { } } async applyDataV2(data: PluginDataExDisplayV2, content?: string): Promise { - const baseDir = this.app.vault.configDir; + const baseDir = this.configDir; try { if (content) { // const dt = createBlob(content); const filename = data.files[0].filename; this._log(`Applying ${filename} of ${data.displayName || data.name}..`); const path = `${baseDir}/${filename}` as FilePath; - await this.plugin.storageAccess.ensureDir(path); + await this.core.storageAccess.ensureDir(path); // If the content has applied, modified time will be updated to the current time. - await this.plugin.storageAccess.writeHiddenFileAuto(path, content); + await this.core.storageAccess.writeHiddenFileAuto(path, content); await this.storeCustomisationFileV2(path, this.services.setting.getDeviceAndVaultName()); } else { const files = data.files; @@ -1015,12 +1017,12 @@ export class ConfigSync extends LiveSyncCommands { const path = `${baseDir}/${f.filename}` as FilePath; this._log(`Applying ${f.filename} of ${data.displayName || data.name}..`); // const contentEach = createBlob(f.data); - await this.plugin.storageAccess.ensureDir(path); + await this.core.storageAccess.ensureDir(path); if (f.datatype == "newnote") { let oldData; try { - oldData = await this.plugin.storageAccess.readHiddenFileBinary(path); + oldData = await this.core.storageAccess.readHiddenFileBinary(path); } catch (ex) { this._log(`Could not read the file ${f.filename}`, LOG_LEVEL_VERBOSE); this._log(ex, LOG_LEVEL_VERBOSE); @@ -1031,11 +1033,11 @@ export class ConfigSync extends LiveSyncCommands { this._log(`The file ${f.filename} is already up-to-date`, LOG_LEVEL_VERBOSE); continue; } - await this.plugin.storageAccess.writeHiddenFileAuto(path, content, stat); + await this.core.storageAccess.writeHiddenFileAuto(path, content, stat); } else { let oldData; try { - oldData = await this.plugin.storageAccess.readHiddenFileText(path); + oldData = await this.core.storageAccess.readHiddenFileText(path); } catch (ex) { this._log(`Could not read the file ${f.filename}`, LOG_LEVEL_VERBOSE); this._log(ex, LOG_LEVEL_VERBOSE); @@ -1046,7 +1048,7 @@ export class ConfigSync extends LiveSyncCommands { this._log(`The file ${f.filename} is already up-to-date`, LOG_LEVEL_VERBOSE); continue; } - await this.plugin.storageAccess.writeHiddenFileAuto(path, content, stat); + await this.core.storageAccess.writeHiddenFileAuto(path, content, stat); } this._log(`Applied ${f.filename} of ${data.displayName || data.name}..`); await this.storeCustomisationFileV2(path, this.services.setting.getDeviceAndVaultName()); @@ -1065,7 +1067,7 @@ export class ConfigSync extends LiveSyncCommands { if (data instanceof PluginDataExDisplayV2) { return this.applyDataV2(data, content); } - const baseDir = this.app.vault.configDir; + const baseDir = this.configDir; try { if (!data.documentPath) throw "InternalError: Document path not exist"; const dx = await this.localDatabase.getDBEntry(data.documentPath); @@ -1078,12 +1080,12 @@ export class ConfigSync extends LiveSyncCommands { try { // console.dir(f); const path = `${baseDir}/${f.filename}`; - await this.plugin.storageAccess.ensureDir(path); + await this.core.storageAccess.ensureDir(path); if (!content) { const dt = decodeBinary(f.data); - await this.plugin.storageAccess.writeHiddenFileAuto(path, dt); + await this.core.storageAccess.writeHiddenFileAuto(path, dt); } else { - await this.plugin.storageAccess.writeHiddenFileAuto(path, content); + await this.core.storageAccess.writeHiddenFileAuto(path, content); } this._log(`Applying ${f.filename} of ${data.displayName || data.name}.. Done`); } catch (ex) { @@ -1172,7 +1174,7 @@ export class ConfigSync extends LiveSyncCommands { (docs as AnyEntry).path ? (docs as AnyEntry).path : this.getPath(docs as AnyEntry) ); } - if (this.isThisModuleEnabled() && this.plugin.settings.notifyPluginOrSettingUpdated) { + if (this.isThisModuleEnabled() && this.core.settings.notifyPluginOrSettingUpdated) { if (!this.pluginDialog || (this.pluginDialog && !this.pluginDialog.isOpened())) { const fragment = createFragment((doc) => { doc.createEl("span", undefined, (a) => { @@ -1230,13 +1232,13 @@ export class ConfigSync extends LiveSyncCommands { recentProcessedInternalFiles = [] as string[]; async makeEntryFromFile(path: FilePath): Promise { - const stat = await this.plugin.storageAccess.statHidden(path); + const stat = await this.core.storageAccess.statHidden(path); let version: string | undefined; let displayName: string | undefined; if (!stat) { return false; } - const contentBin = await this.plugin.storageAccess.readHiddenFileBinary(path); + const contentBin = await this.core.storageAccess.readHiddenFileBinary(path); let content: string[]; try { content = await arrayBufferToBase64(contentBin); @@ -1265,7 +1267,7 @@ export class ConfigSync extends LiveSyncCommands { } const mtime = stat.mtime; return { - filename: path.substring(this.app.vault.configDir.length + 1), + filename: path.substring(this.configDir.length + 1), data: content, mtime, size: stat.size, @@ -1280,12 +1282,12 @@ export class ConfigSync extends LiveSyncCommands { const prefixedFileName = vf; const id = await this.path2id(prefixedFileName); - const stat = await this.plugin.storageAccess.statHidden(path); + const stat = await this.core.storageAccess.statHidden(path); if (!stat) { return false; } const mtime = stat.mtime; - const content = await this.plugin.storageAccess.readHiddenFileBinary(path); + const content = await this.core.storageAccess.readHiddenFileBinary(path); const contentBlob = createBlob([DUMMY_HEAD, DUMMY_END, ...(await arrayBufferToBase64(content))]); // const contentBlob = createBlob(content); try { @@ -1504,11 +1506,11 @@ export class ConfigSync extends LiveSyncCommands { if (this._isMainSuspended()) return false; if (!this.isThisModuleEnabled()) return false; // if (!this.isTargetPath(path)) return false; - const stat = await this.plugin.storageAccess.statHidden(path); + const stat = await this.core.storageAccess.statHidden(path); // Make sure that target is a file. if (stat && stat.type != "file") return false; - const configDir = normalizePath(this.app.vault.configDir); + const configDir = normalizePath(this.configDir); const synchronisedInConfigSync = Object.values(this.settings.pluginSyncExtendedSetting) .filter((e) => e.mode != MODE_SELECTIVE && e.mode != MODE_SHINY) .map((e) => e.files) @@ -1674,7 +1676,7 @@ export class ConfigSync extends LiveSyncCommands { } async scanInternalFiles(): Promise { - const filenames = (await this.getFiles(this.app.vault.configDir, 2)) + const filenames = (await this.getFiles(this.configDir, 2)) .filter((e) => e.startsWith(".")) .filter((e) => !e.startsWith(".trash")); return filenames as FilePath[]; @@ -1705,7 +1707,7 @@ export class ConfigSync extends LiveSyncCommands { choices.push(CHOICE_DISABLE); choices.push(CHOICE_DISMISS); - const ret = await this.plugin.confirm.askSelectStringDialogue(message, choices, { + const ret = await this.core.confirm.askSelectStringDialogue(message, choices, { defaultAction: CHOICE_DISMISS, timeout: 40, title: "Customisation sync", @@ -1728,13 +1730,13 @@ export class ConfigSync extends LiveSyncCommands { } private _allSuspendExtraSync(): Promise { - if (this.plugin.settings.usePluginSync || this.plugin.settings.autoSweepPlugins) { + if (this.core.settings.usePluginSync || this.core.settings.autoSweepPlugins) { this._log( "Customisation sync have been temporarily disabled. Please enable them after the fetching, if you need them.", LOG_LEVEL_NOTICE ); - this.plugin.settings.usePluginSync = false; - this.plugin.settings.autoSweepPlugins = false; + this.core.settings.usePluginSync = false; + this.core.settings.autoSweepPlugins = false; } return Promise.resolve(true); } @@ -1745,14 +1747,20 @@ export class ConfigSync extends LiveSyncCommands { } async configureHiddenFileSync(mode: keyof OPTIONAL_SYNC_FEATURES) { if (mode == "DISABLE") { - this.plugin.settings.usePluginSync = false; - await this.plugin.saveSettings(); + // this.plugin.settings.usePluginSync = false; + // await this.plugin.saveSettings(); + await this.core.services.setting.applyPartial( + { + usePluginSync: false, + }, + true + ); return; } if (mode == "CUSTOMIZE") { if (!this.services.setting.getDeviceAndVaultName()) { - let name = await this.plugin.confirm.askString("Device name", "Please set this device name", `desktop`); + let name = await this.core.confirm.askString("Device name", "Please set this device name", `desktop`); if (!name) { if (Platform.isAndroidApp) { name = "android-app"; @@ -1777,9 +1785,16 @@ export class ConfigSync extends LiveSyncCommands { } this.services.setting.setDeviceAndVaultName(name); } - this.plugin.settings.usePluginSync = true; - this.plugin.settings.useAdvancedMode = true; - await this.plugin.saveSettings(); + // this.core.settings.usePluginSync = true; + // this.core.settings.useAdvancedMode = true; + // await this.core.saveSettings(); + await this.core.services.setting.applyPartial( + { + usePluginSync: true, + useAdvancedMode: true, + }, + true + ); await this.scanAllConfigFiles(true); } } diff --git a/src/features/ConfigSync/PluginCombo.svelte b/src/features/ConfigSync/PluginCombo.svelte index ceb9256..e2b7dc4 100644 --- a/src/features/ConfigSync/PluginCombo.svelte +++ b/src/features/ConfigSync/PluginCombo.svelte @@ -30,7 +30,8 @@ export let plugin: ObsidianLiveSyncPlugin; export let isMaintenanceMode: boolean = false; export let isFlagged: boolean = false; - const addOn = plugin.getAddOn(ConfigSync.name)!; + $: core = plugin.core; + const addOn = plugin.core.getAddOn(ConfigSync.name)!; if (!addOn) { Logger(`Could not load the add-on ${ConfigSync.name}`, LOG_LEVEL_INFO); throw new Error(`Could not load the add-on ${ConfigSync.name}`); @@ -334,13 +335,13 @@ Logger(`Could not find local item`, LOG_LEVEL_VERBOSE); return; } - const duplicateTermName = await plugin.confirm.askString("Duplicate", "device name", ""); + const duplicateTermName = await core.confirm.askString("Duplicate", "device name", ""); if (duplicateTermName) { if (duplicateTermName.contains("/")) { Logger(`We can not use "/" to the device name`, LOG_LEVEL_NOTICE); return; } - const key = `${plugin.app.vault.configDir}/${local.files[0].filename}`; + const key = `${plugin.core.services.API.getSystemConfigDir()}/${local.files[0].filename}`; await addOn.storeCustomizationFiles(key as FilePath, duplicateTermName); await addOn.updatePluginList(false, addOn.filenameToUnifiedKey(key, duplicateTermName)); } diff --git a/src/features/ConfigSync/PluginDialogModal.ts b/src/features/ConfigSync/PluginDialogModal.ts index d75043b..08825ba 100644 --- a/src/features/ConfigSync/PluginDialogModal.ts +++ b/src/features/ConfigSync/PluginDialogModal.ts @@ -23,7 +23,7 @@ export class PluginDialogModal extends Modal { if (!this.component) { this.component = mount(PluginPane, { target: contentEl, - props: { plugin: this.plugin }, + props: { plugin: this.plugin, core: this.plugin.core }, }); } } diff --git a/src/features/ConfigSync/PluginPane.svelte b/src/features/ConfigSync/PluginPane.svelte index a7a2e61..5b0a167 100644 --- a/src/features/ConfigSync/PluginPane.svelte +++ b/src/features/ConfigSync/PluginPane.svelte @@ -22,19 +22,22 @@ import { normalizePath } from "../../deps"; import { HiddenFileSync } from "../HiddenFileSync/CmdHiddenFileSync.ts"; import { LOG_LEVEL_NOTICE, Logger } from "octagonal-wheels/common/logger"; + import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts"; export let plugin: ObsidianLiveSyncPlugin; + export let core :LiveSyncBaseCore; + // $: core = plugin.core; $: hideNotApplicable = false; - $: thisTerm = plugin.services.setting.getDeviceAndVaultName(); + $: thisTerm = core.services.setting.getDeviceAndVaultName(); - const addOn = plugin.getAddOn(ConfigSync.name) as ConfigSync; + const addOn = core.getAddOn(ConfigSync.name)!; if (!addOn) { const msg = "AddOn Module (ConfigSync) has not been loaded. This is very unexpected situation. Please report this issue."; Logger(msg, LOG_LEVEL_NOTICE); throw new Error(msg); } - const addOnHiddenFileSync = plugin.getAddOn(HiddenFileSync.name) as HiddenFileSync; + const addOnHiddenFileSync = core.getAddOn(HiddenFileSync.name) as HiddenFileSync; if (!addOnHiddenFileSync) { const msg = "AddOn Module (HiddenFileSync) has not been loaded. This is very unexpected situation. Please report this issue."; @@ -98,7 +101,7 @@ await requestUpdate(); } async function replicate() { - await plugin.services.replication.replicate(true); + await core.services.replication.replicate(true); } function selectAllNewest(selectMode: boolean) { selectNewestPulse++; @@ -147,8 +150,8 @@ } function applyAutomaticSync(key: string, direction: "pushForce" | "pullForce" | "safe") { setMode(key, MODE_AUTOMATIC); - const configDir = normalizePath(plugin.app.vault.configDir); - const files = (plugin.settings.pluginSyncExtendedSetting[key]?.files ?? []).map((e) => `${configDir}/${e}`); + const configDir = normalizePath(plugin.core.services.API.getSystemConfigDir()); + const files = (plugin.core.settings.pluginSyncExtendedSetting[key]?.files ?? []).map((e) => `${configDir}/${e}`); addOnHiddenFileSync.initialiseInternalFileSync(direction, true, files); } function askOverwriteModeForAutomatic(evt: MouseEvent, key: string) { @@ -222,22 +225,22 @@ ); if (mode == MODE_SELECTIVE) { automaticList.delete(key); - delete plugin.settings.pluginSyncExtendedSetting[key]; + delete plugin.core.settings.pluginSyncExtendedSetting[key]; automaticListDisp = automaticList; } else { automaticList.set(key, mode); automaticListDisp = automaticList; - if (!(key in plugin.settings.pluginSyncExtendedSetting)) { - plugin.settings.pluginSyncExtendedSetting[key] = { + if (!(key in plugin.core.settings.pluginSyncExtendedSetting)) { + plugin.core.settings.pluginSyncExtendedSetting[key] = { key, mode, files: [], }; } - plugin.settings.pluginSyncExtendedSetting[key].files = files; - plugin.settings.pluginSyncExtendedSetting[key].mode = mode; + plugin.core.settings.pluginSyncExtendedSetting[key].files = files; + plugin.core.settings.pluginSyncExtendedSetting[key].mode = mode; } - plugin.services.setting.saveSettingData(); + core.services.setting.saveSettingData(); } function getIcon(mode: SYNC_MODE) { if (mode in ICONS) { @@ -250,7 +253,7 @@ let automaticListDisp = new Map(); // apply current configuration to the dialogue - for (const { key, mode } of Object.values(plugin.settings.pluginSyncExtendedSetting)) { + for (const { key, mode } of Object.values(plugin.core.settings.pluginSyncExtendedSetting)) { automaticList.set(key, mode); } @@ -259,7 +262,7 @@ let displayKeys: Record = {}; function computeDisplayKeys(list: IPluginDataExDisplay[]) { - const extraKeys = Object.keys(plugin.settings.pluginSyncExtendedSetting); + const extraKeys = Object.keys(plugin.core.settings.pluginSyncExtendedSetting); return [ ...list, ...extraKeys @@ -321,7 +324,7 @@ $: { pluginEntries = groupBy(filterList(list, ["PLUGIN_MAIN", "PLUGIN_DATA", "PLUGIN_ETC"]), "name"); } - let useSyncPluginEtc = plugin.settings.usePluginEtc; + let useSyncPluginEtc = plugin.core.settings.usePluginEtc;
diff --git a/src/features/HiddenFileSync/CmdHiddenFileSync.ts b/src/features/HiddenFileSync/CmdHiddenFileSync.ts index 2ada77e..d20ae55 100644 --- a/src/features/HiddenFileSync/CmdHiddenFileSync.ts +++ b/src/features/HiddenFileSync/CmdHiddenFileSync.ts @@ -30,7 +30,6 @@ import { import { compareMTime, isInternalMetadata, - PeriodicProcessor, TARGET_IS_NEW, scheduleTask, getLogLevel, @@ -41,6 +40,7 @@ import { EVEN, displayRev, } from "../../common/utils.ts"; +import { PeriodicProcessor } from "@/common/PeriodicProcessor.ts"; import { serialized, skipIfDuplicated } from "octagonal-wheels/concurrency/lock"; import { JsonResolveModal } from "../HiddenFileCommon/JsonResolveModal.ts"; import { LiveSyncCommands } from "../LiveSyncCommands.ts"; @@ -78,25 +78,25 @@ function getComparingMTime( export class HiddenFileSync extends LiveSyncCommands { isThisModuleEnabled() { - return this.plugin.settings.syncInternalFiles; + return this.core.settings.syncInternalFiles; } periodicInternalFileScanProcessor: PeriodicProcessor = new PeriodicProcessor( - this.plugin, + this.core, async () => this.isThisModuleEnabled() && this._isDatabaseReady() && (await this.scanAllStorageChanges(false)) ); get kvDB() { - return this.plugin.kvDB; + return this.core.kvDB; } getConflictedDoc(path: FilePathWithPrefix, rev: string) { - return this.localDatabase.managers.conflictManager.getConflictedDoc(path, rev); + return this.core.localDatabase.managers.conflictManager.getConflictedDoc(path, rev); } onunload() { this.periodicInternalFileScanProcessor?.disable(); } onload() { - this.plugin.addCommand({ + this.services.API.addCommand({ id: "livesync-sync-internal", name: "(re)initialise hidden files between storage and database", callback: () => { @@ -105,7 +105,7 @@ export class HiddenFileSync extends LiveSyncCommands { } }, }); - this.plugin.addCommand({ + this.services.API.addCommand({ id: "livesync-scaninternal-storage", name: "Scan hidden file changes on the storage", callback: () => { @@ -114,7 +114,7 @@ export class HiddenFileSync extends LiveSyncCommands { } }, }); - this.plugin.addCommand({ + this.services.API.addCommand({ id: "livesync-scaninternal-database", name: "Scan hidden file changes on the local database", callback: () => { @@ -123,7 +123,7 @@ export class HiddenFileSync extends LiveSyncCommands { } }, }); - this.plugin.addCommand({ + this.services.API.addCommand({ id: "livesync-internal-scan-offline-changes", name: "Scan and apply all offline hidden-file changes", callback: () => { @@ -267,7 +267,7 @@ export class HiddenFileSync extends LiveSyncCommands { } async loadFileWithInfo(path: FilePath): Promise { - const stat = await this.plugin.storageAccess.statHidden(path); + const stat = await this.core.storageAccess.statHidden(path); if (!stat) return { name: path.split("/").pop() ?? "", @@ -282,7 +282,7 @@ export class HiddenFileSync extends LiveSyncCommands { deleted: true, body: createBlob(new Uint8Array(0)), }; - const content = await this.plugin.storageAccess.readHiddenFileAuto(path); + const content = await this.core.storageAccess.readHiddenFileAuto(path); return { name: path.split("/").pop() ?? "", path, @@ -304,7 +304,7 @@ export class HiddenFileSync extends LiveSyncCommands { return `${doc.mtime}-${doc.size}-${doc._rev}-${doc._deleted || doc.deleted || false ? "-0" : "-1"}`; } async fileToStatKey(file: FilePath, stat: UXStat | null = null) { - if (!stat) stat = await this.plugin.storageAccess.statHidden(file); + if (!stat) stat = await this.core.storageAccess.statHidden(file); return this.statToKey(stat); } @@ -318,7 +318,7 @@ export class HiddenFileSync extends LiveSyncCommands { } async updateLastProcessedAsActualFile(file: FilePath, stat?: UXStat | null | undefined) { - if (!stat) stat = await this.plugin.storageAccess.statHidden(file); + if (!stat) stat = await this.core.storageAccess.statHidden(file); this._fileInfoLastProcessed.set(file, this.statToKey(stat)); } @@ -371,27 +371,27 @@ export class HiddenFileSync extends LiveSyncCommands { this.updateLastProcessedFile(path, this.statToKey(null)); } async ensureDir(path: FilePath) { - const isExists = await this.plugin.storageAccess.isExistsIncludeHidden(path); + const isExists = await this.core.storageAccess.isExistsIncludeHidden(path); if (!isExists) { - await this.plugin.storageAccess.ensureDir(path); + await this.core.storageAccess.ensureDir(path); } } async writeFile(path: FilePath, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise { - await this.plugin.storageAccess.writeHiddenFileAuto(path, data, opt); - const stat = await this.plugin.storageAccess.statHidden(path); + await this.core.storageAccess.writeHiddenFileAuto(path, data, opt); + const stat = await this.core.storageAccess.statHidden(path); // this.updateLastProcessedFile(path, this.statToKey(stat)); return stat; } async __removeFile(path: FilePath): Promise<"OK" | "ALREADY" | false> { try { - if (!(await this.plugin.storageAccess.isExistsIncludeHidden(path))) { + if (!(await this.core.storageAccess.isExistsIncludeHidden(path))) { // Already deleted // this.updateLastProcessedFile(path, this.statToKey(null)); return "ALREADY"; } - if (await this.plugin.storageAccess.removeHidden(path)) { + if (await this.core.storageAccess.removeHidden(path)) { // this.updateLastProcessedFile(path, this.statToKey(null)); return "OK"; } @@ -404,7 +404,7 @@ export class HiddenFileSync extends LiveSyncCommands { async triggerEvent(path: FilePath) { try { // await this.app.vault.adapter.reconcileInternalFile(filename); - await this.plugin.storageAccess.triggerHiddenFile(path); + await this.core.storageAccess.triggerHiddenFile(path); } catch (ex) { this._log("Failed to call internal API(reconcileInternalFile)", LOG_LEVEL_VERBOSE); this._log(ex, LOG_LEVEL_VERBOSE); @@ -518,7 +518,7 @@ export class HiddenFileSync extends LiveSyncCommands { LOG_LEVEL_VERBOSE ); const taskNameAndMeta = [...files].map( - async (e) => [e, await this.plugin.storageAccess.statHidden(e)] as const + async (e) => [e, await this.core.storageAccess.statHidden(e)] as const ); const nameAndMeta = await Promise.all(taskNameAndMeta); const processFiles = nameAndMeta @@ -560,7 +560,7 @@ Offline Changed files: ${processFiles.length}`; } try { return await this.serializedForEvent(path, async () => { - let stat = await this.plugin.storageAccess.statHidden(path); + let stat = await this.core.storageAccess.statHidden(path); // sometimes folder is coming. if (stat != null && stat.type != "file") { return false; @@ -815,9 +815,9 @@ Offline Changed files: ${processFiles.length}`; } } if (!keep && result) { - const isExists = await this.plugin.storageAccess.isExistsIncludeHidden(storageFilePath); + const isExists = await this.core.storageAccess.isExistsIncludeHidden(storageFilePath); if (!isExists) { - await this.plugin.storageAccess.ensureDir(storageFilePath); + await this.core.storageAccess.ensureDir(storageFilePath); } const stat = await this.writeFile(storageFilePath, result); if (!stat) { @@ -894,7 +894,7 @@ Offline Changed files: ${processFiles.length}`; * @returns An object containing the ignore and target filters. */ parseRegExpSettings() { - const regExpKey = `${this.plugin.settings.syncInternalFilesTargetPatterns}||${this.plugin.settings.syncInternalFilesIgnorePatterns}`; + const regExpKey = `${this.core.settings.syncInternalFilesTargetPatterns}||${this.core.settings.syncInternalFilesIgnorePatterns}`; let ignoreFilter: CustomRegExp[]; let targetFilter: CustomRegExp[]; if (this.cacheFileRegExps.has(regExpKey)) { @@ -902,8 +902,8 @@ Offline Changed files: ${processFiles.length}`; ignoreFilter = cached[1]; targetFilter = cached[0]; } else { - ignoreFilter = getFileRegExp(this.plugin.settings, "syncInternalFilesIgnorePatterns"); - targetFilter = getFileRegExp(this.plugin.settings, "syncInternalFilesTargetPatterns"); + ignoreFilter = getFileRegExp(this.core.settings, "syncInternalFilesIgnorePatterns"); + targetFilter = getFileRegExp(this.core.settings, "syncInternalFilesTargetPatterns"); this.cacheFileRegExps.clear(); this.cacheFileRegExps.set(regExpKey, [targetFilter, ignoreFilter]); } @@ -941,7 +941,7 @@ Offline Changed files: ${processFiles.length}`; * @returns An array of ignored file paths (lowercase). */ getCustomisationSynchronizationIgnoredFiles(): string[] { - const configDir = this.plugin.app.vault.configDir; + const configDir = this.services.API.getSystemConfigDir(); const key = JSON.stringify(this.settings.pluginSyncExtendedSetting) + `||${this.settings.usePluginSync}||${configDir}`; if (this.cacheCustomisationSyncIgnoredFiles.has(key)) { @@ -1058,7 +1058,7 @@ Common untracked files: ${bothUntracked.length}`; notifyProgress(); const rel = await semaphores.acquire(); try { - const fileStat = await this.plugin.storageAccess.statHidden(file); + const fileStat = await this.core.storageAccess.statHidden(file); if (fileStat == null) { // This should not be happened. But, if it happens, we should skip this. this._log(`Unexpected error: Failed to stat file during applyOfflineChange :${file}`); @@ -1206,7 +1206,7 @@ Offline Changed files: ${files.length}`; // If notified about plug-ins, reloading Obsidian may not be necessary. const updatePluginId = manifest.id; const updatePluginName = manifest.name; - this.plugin.confirm.askInPopup( + this.core.confirm.askInPopup( `updated-${updatePluginId}`, `Files in ${updatePluginName} has been updated!\nPress {HERE} to reload ${updatePluginName}, or press elsewhere to dismiss this message.`, (anchor) => { @@ -1238,9 +1238,9 @@ Offline Changed files: ${files.length}`; } // If something changes left, notify for reloading Obsidian. - if (updatedFolders.indexOf(this.plugin.app.vault.configDir) >= 0) { + if (updatedFolders.indexOf(this.services.API.getSystemConfigDir()) >= 0) { if (!this.services.appLifecycle.isReloadingScheduled()) { - this.plugin.confirm.askInPopup( + this.core.confirm.askInPopup( `updated-any-hidden`, `Some setting files have been modified\nPress {HERE} to schedule a reload of Obsidian, or press elsewhere to dismiss this message.`, (anchor) => { @@ -1258,7 +1258,7 @@ Offline Changed files: ${files.length}`; if (this.settings.suppressNotifyHiddenFilesChange) { return; } - const configDir = this.plugin.app.vault.configDir; + const configDir = this.services.API.getSystemConfigDir(); if (!key.startsWith(configDir)) return; const dirName = key.split("/").slice(0, -1).join("/"); this.queuedNotificationFiles.add(dirName); @@ -1296,7 +1296,7 @@ Offline Changed files: ${files.length}`; const eachProgress = onlyInNTimes(100, (progress) => p.log(`Checking ${progress}/${allFileNames.size}`)); for (const file of allFileNames) { eachProgress(); - const storageMTime = await this.plugin.storageAccess.statHidden(file); + const storageMTime = await this.core.storageAccess.statHidden(file); const mtimeStorage = getComparingMTime(storageMTime); const dbEntry = allDatabaseMap.get(file)!; const mtimeDB = getComparingMTime(dbEntry); @@ -1616,7 +1616,7 @@ Offline Changed files: ${files.length}`; if (onlyNew) { // Check the file is new or not. const dbMTime = getComparingMTime(metaOnDB, includeDeletion); // metaOnDB.mtime; - const storageStat = await this.plugin.storageAccess.statHidden(storageFilePath); + const storageStat = await this.core.storageAccess.statHidden(storageFilePath); const storageMTimeActual = storageStat?.mtime ?? 0; const storageMTime = storageMTimeActual == 0 ? this.getLastProcessedFileMTime(storageFilePath) : storageMTimeActual; @@ -1670,7 +1670,7 @@ Offline Changed files: ${files.length}`; async __checkIsNeedToWriteFile(storageFilePath: FilePath, content: string | ArrayBuffer): Promise { try { - const storageContent = await this.plugin.storageAccess.readHiddenFileAuto(storageFilePath); + const storageContent = await this.core.storageAccess.readHiddenFileAuto(storageFilePath); const needWrite = !(await isDocContentSame(storageContent, content)); return needWrite; } catch (ex) { @@ -1682,7 +1682,7 @@ Offline Changed files: ${files.length}`; async __writeFile(storageFilePath: FilePath, fileOnDB: LoadedEntry, force: boolean): Promise { try { - const statBefore = await this.plugin.storageAccess.statHidden(storageFilePath); + const statBefore = await this.core.storageAccess.statHidden(storageFilePath); const isExist = statBefore != null; const writeContent = readContent(fileOnDB); await this.ensureDir(storageFilePath); @@ -1768,7 +1768,7 @@ ${messageFetch}${messageOverwrite}${messageMerge} choices.push(CHOICE_MERGE); choices.push(CHOICE_DISABLE); - const ret = await this.plugin.confirm.confirmWithMessage( + const ret = await this.core.confirm.confirmWithMessage( "Hidden file sync", message, choices, @@ -1787,12 +1787,12 @@ ${messageFetch}${messageOverwrite}${messageMerge} } private _allSuspendExtraSync(): Promise { - if (this.plugin.settings.syncInternalFiles) { + if (this.core.settings.syncInternalFiles) { this._log( "Hidden file synchronization have been temporarily disabled. Please enable them after the fetching, if you need them.", LOG_LEVEL_NOTICE ); - this.plugin.settings.syncInternalFiles = false; + this.core.settings.syncInternalFiles = false; } return Promise.resolve(true); } @@ -1815,9 +1815,15 @@ ${messageFetch}${messageOverwrite}${messageMerge} } if (mode == "DISABLE" || mode == "DISABLE_HIDDEN") { - // await this.plugin.$allSuspendExtraSync(); - this.plugin.settings.syncInternalFiles = false; - await this.plugin.saveSettings(); + // await this.core.$allSuspendExtraSync(); + await this.core.services.setting.applyPartial( + { + syncInternalFiles: false, + }, + true + ); + // this.core.settings.syncInternalFiles = false; + // await this.core.saveSettings(); return; } this._log("Gathering files for enabling Hidden File Sync", LOG_LEVEL_NOTICE); @@ -1828,10 +1834,17 @@ ${messageFetch}${messageOverwrite}${messageMerge} } else if (mode == "MERGE") { await this.initialiseInternalFileSync("safe", true); } - this.plugin.settings.useAdvancedMode = true; - this.plugin.settings.syncInternalFiles = true; + await this.core.services.setting.applyPartial( + { + useAdvancedMode: true, + syncInternalFiles: true, + }, + true + ); + // this.plugin.settings.useAdvancedMode = true; + // this.plugin.settings.syncInternalFiles = true; - await this.plugin.saveSettings(); + // await this.plugin.saveSettings(); this._log(`Done! Restarting the app is strongly recommended!`, LOG_LEVEL_NOTICE); } // <-- Configuration handling @@ -1851,7 +1864,7 @@ ${messageFetch}${messageOverwrite}${messageMerge} const files = fileNames.map(async (e) => { return { path: e, - stat: await this.plugin.storageAccess.statHidden(e), // this.plugin.vaultAccess.adapterStat(e) + stat: await this.core.storageAccess.statHidden(e), // this.plugin.vaultAccess.adapterStat(e) }; }); const result: InternalFileInfo[] = []; @@ -1956,5 +1969,6 @@ ${messageFetch}${messageOverwrite}${messageMerge} services.setting.suspendExtraSync.addHandler(this._allSuspendExtraSync.bind(this)); services.setting.suggestOptionalFeatures.addHandler(this._allAskUsingOptionalSyncFeature.bind(this)); services.setting.enableOptionalFeature.addHandler(this._allConfigureOptionalSyncFeature.bind(this)); + services.vault.isTargetFileInExtra.addHandler(this.isTargetFile.bind(this)); } } diff --git a/src/features/LiveSyncCommands.ts b/src/features/LiveSyncCommands.ts index 17978ac..2f79fc9 100644 --- a/src/features/LiveSyncCommands.ts +++ b/src/features/LiveSyncCommands.ts @@ -16,18 +16,22 @@ import { createInstanceLogFunction } from "@/lib/src/services/lib/logUtils.ts"; let noticeIndex = 0; export abstract class LiveSyncCommands { + /** + * @deprecated This class is deprecated. Please use core + */ plugin: ObsidianLiveSyncPlugin; + core: LiveSyncCore; get app() { return this.plugin.app; } get settings() { - return this.plugin.settings; + return this.core.settings; } get localDatabase() { - return this.plugin.localDatabase; + return this.core.localDatabase; } get services() { - return this.plugin.services; + return this.core.services; } // id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix { @@ -41,9 +45,10 @@ export abstract class LiveSyncCommands { return this.services.path.getPath(entry); } - constructor(plugin: ObsidianLiveSyncPlugin) { + constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) { this.plugin = plugin; - this.onBindFunction(plugin, plugin.services); + this.core = core; + this.onBindFunction(this.core, this.core.services); this._log = createInstanceLogFunction(this.constructor.name, this.services.API); __$checkInstanceBinding(this); } @@ -51,7 +56,7 @@ export abstract class LiveSyncCommands { abstract onload(): void | Promise; _isMainReady() { - return this.plugin.services.appLifecycle.isReady(); + return this.services.appLifecycle.isReady(); } _isMainSuspended() { return this.services.appLifecycle.isSuspended(); diff --git a/src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts b/src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts index d8fd482..1f75385 100644 --- a/src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts +++ b/src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts @@ -71,7 +71,7 @@ export class LocalDatabaseMaintenance extends LiveSyncCommands { async confirm(title: string, message: string, affirmative = "Yes", negative = "No") { return ( - (await this.plugin.confirm.askSelectStringDialogue(message, [affirmative, negative], { + (await this.core.confirm.askSelectStringDialogue(message, [affirmative, negative], { title, defaultAction: affirmative, })) === affirmative @@ -302,7 +302,7 @@ Note: **Make sure to synchronise all devices before deletion.** } async scanUnusedChunks() { - const kvDB = this.plugin.kvDB; + const kvDB = this.core.kvDB; const chunkSet = (await kvDB.get>(DB_KEY_CHUNK_SET)) || new Set(); const chunkUsageMap = (await kvDB.get(DB_KEY_DOC_USAGE_MAP)) || new Map(); const KEEP_MAX_REVS = 10; @@ -328,7 +328,7 @@ Note: **Make sure to synchronise all devices before deletion.** async trackChanges(fromStart: boolean = false, showNotice: boolean = false) { if (!this.isAvailable()) return; const logLevel = showNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO; - const kvDB = this.plugin.kvDB; + const kvDB = this.core.kvDB; const previousSeq = fromStart ? "" : await kvDB.get(DB_KEY_SEQ); const chunkSet = (await kvDB.get>(DB_KEY_CHUNK_SET)) || new Set(); @@ -457,7 +457,7 @@ Are you ready to delete unused chunks?`; const BUTTON_OK = `Yes, delete chunks`; const BUTTON_CANCEL = "Cancel"; - const result = await this.plugin.confirm.askSelectStringDialogue( + const result = await this.core.confirm.askSelectStringDialogue( confirmMessage, [BUTTON_OK, BUTTON_CANCEL] as const, { @@ -506,7 +506,7 @@ Are you ready to delete unused chunks?`; const message = `Garbage Collection completed. Success: ${successCount}, Errored: ${errored}`; this._log(message, logLevel); - const kvDB = this.plugin.kvDB; + const kvDB = this.core.kvDB; await kvDB.set(DB_KEY_CHUNK_SET, chunkSet); } @@ -723,7 +723,7 @@ Success: ${successCount}, Errored: ${errored}`; } async compactDatabase() { - const replicator = this.plugin.replicator as LiveSyncCouchDBReplicator; + const replicator = this.core.replicator as LiveSyncCouchDBReplicator; const remote = await replicator.connectRemoteCouchDBWithSetting(this.settings, false, false, true); if (!remote) { this._notice("Failed to connect to remote for compaction.", "gc-compact"); @@ -767,7 +767,7 @@ Success: ${successCount}, Errored: ${errored}`; // Temporarily set revs_limit to 1, perform compaction, and restore the original revs_limit. // Very dangerous operation, so now suppressed. return false; - const replicator = this.plugin.replicator as LiveSyncCouchDBReplicator; + const replicator = this.core.replicator as LiveSyncCouchDBReplicator; const remote = await replicator.connectRemoteCouchDBWithSetting(this.settings, false, false, true); if (!remote) { this._notice("Failed to connect to remote for compaction."); @@ -822,7 +822,7 @@ Success: ${successCount}, Errored: ${errored}`; } async gcv3() { if (!this.isAvailable()) return; - const replicator = this.plugin.replicator as LiveSyncCouchDBReplicator; + const replicator = this.core.replicator as LiveSyncCouchDBReplicator; // Start one-shot replication to ensure all changes are synced before GC. const r0 = await replicator.openOneShotReplication(this.settings, false, false, "sync"); if (!r0) { @@ -835,7 +835,7 @@ Success: ${successCount}, Errored: ${errored}`; // Delete the chunk, but first verify the following: // Fetch the list of accepted nodes from the replicator. const OPTION_CANCEL = "Cancel Garbage Collection"; - const info = await this.plugin.replicator.getConnectedDeviceList(); + const info = await this.core.replicator.getConnectedDeviceList(); if (!info) { this._notice("No connected device information found. Cancelling Garbage Collection."); return; @@ -855,7 +855,7 @@ It is preferable to update all devices if possible. If you have any devices that const OPTION_IGNORE = "Ignore and Proceed"; // const OPTION_DELETE = "Delete them and proceed"; const buttons = [OPTION_CANCEL, OPTION_IGNORE] as const; - const result = await this.plugin.confirm.askSelectStringDialogue(message, buttons, { + const result = await this.core.confirm.askSelectStringDialogue(message, buttons, { title: "Node Information Missing", defaultAction: OPTION_CANCEL, }); @@ -896,7 +896,7 @@ This may indicate that some devices have not completed synchronisation, which co : `All devices have the same progress value (${maxProgress}). Your devices seem to be synchronised. And be able to proceed with Garbage Collection.`; const buttons = [OPTION_PROCEED, OPTION_CANCEL] as const; const defaultAction = progressDifference != 0 ? OPTION_CANCEL : OPTION_PROCEED; - const result = await this.plugin.confirm.askSelectStringDialogue(message + "\n\n" + detail, buttons, { + const result = await this.core.confirm.askSelectStringDialogue(message + "\n\n" + detail, buttons, { title: "Garbage Collection Confirmation", defaultAction, }); diff --git a/src/features/P2PSync/CmdP2PReplicator.ts b/src/features/P2PSync/CmdP2PReplicator.ts index e9b2125..fbbd37b 100644 --- a/src/features/P2PSync/CmdP2PReplicator.ts +++ b/src/features/P2PSync/CmdP2PReplicator.ts @@ -38,14 +38,14 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase storeP2PStatusLine = reactiveSource(""); getSettings(): P2PSyncSetting { - return this.plugin.settings; + return this.core.settings; } getDB() { - return this.plugin.localDatabase.localDatabase; + return this.core.localDatabase.localDatabase; } get confirm(): Confirm { - return this.plugin.confirm; + return this.core.confirm; } _simpleStore!: SimpleStore; @@ -53,8 +53,8 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase return this._simpleStore; } - constructor(plugin: ObsidianLiveSyncPlugin) { - super(plugin); + constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) { + super(plugin, core); setReplicatorFunc(() => this._replicatorInstance); addP2PEventHandlers(this); this.afterConstructor(); @@ -72,7 +72,7 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase _anyNewReplicator(settingOverride: Partial = {}): Promise { const settings = { ...this.settings, ...settingOverride }; if (settings.remoteType == REMOTE_P2P) { - return Promise.resolve(new LiveSyncTrysteroReplicator(this.plugin)); + return Promise.resolve(new LiveSyncTrysteroReplicator(this.plugin.core)); } return undefined!; } @@ -183,12 +183,12 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase } private async _allSuspendExtraSync() { - this.plugin.settings.P2P_Enabled = false; - this.plugin.settings.P2P_AutoAccepting = AutoAccepting.NONE; - this.plugin.settings.P2P_AutoBroadcast = false; - this.plugin.settings.P2P_AutoStart = false; - this.plugin.settings.P2P_AutoSyncPeers = ""; - this.plugin.settings.P2P_AutoWatchPeers = ""; + this.plugin.core.settings.P2P_Enabled = false; + this.plugin.core.settings.P2P_AutoAccepting = AutoAccepting.NONE; + this.plugin.core.settings.P2P_AutoBroadcast = false; + this.plugin.core.settings.P2P_AutoStart = false; + this.plugin.core.settings.P2P_AutoSyncPeers = ""; + this.plugin.core.settings.P2P_AutoWatchPeers = ""; return await Promise.resolve(true); } @@ -201,7 +201,10 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase } async _everyOnloadStart(): Promise { - this.plugin.registerView(VIEW_TYPE_P2P, (leaf) => new P2PReplicatorPaneView(leaf, this.plugin)); + this.plugin.registerView( + VIEW_TYPE_P2P, + (leaf) => new P2PReplicatorPaneView(leaf, this.plugin.core, this.plugin) + ); this.plugin.addCommand({ id: "open-p2p-replicator", name: "P2P Sync : Open P2P Replicator", diff --git a/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte b/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte index a0382bb..7ac67eb 100644 --- a/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte +++ b/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte @@ -20,17 +20,18 @@ 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"; + import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore"; interface Props { - plugin: PluginShim; cmdSync: CommandShim; + core: LiveSyncBaseCore; } - let { plugin, cmdSync }: Props = $props(); + let { cmdSync, core }: Props = $props(); // const cmdSync = plugin.getAddOn("P2PReplicator")!; setContext("getReplicator", () => cmdSync); - - const initialSettings = { ...plugin.settings }; + const currentSettings = () => core.services.setting.currentSettings() as P2PSyncSetting; + const initialSettings = { ...currentSettings() } as P2PSyncSetting; let settings = $state(initialSettings); @@ -70,21 +71,33 @@ ); async function saveAndApply() { - const newSettings = { - ...plugin.settings, - P2P_Enabled: eP2PEnabled, - P2P_relays: eRelay, - P2P_roomID: eRoomId, - P2P_passphrase: ePassword, - P2P_AppID: eAppId, - P2P_AutoAccepting: eAutoAccept ? AutoAccepting.ALL : AutoAccepting.NONE, - P2P_AutoStart: eAutoStart, - P2P_AutoBroadcast: eAutoBroadcast, - }; - plugin.settings = newSettings; + // const newSettings = { + // ...currentSettings(), + // P2P_Enabled: eP2PEnabled, + // P2P_relays: eRelay, + // P2P_roomID: eRoomId, + // P2P_passphrase: ePassword, + // P2P_AppID: eAppId, + // P2P_AutoAccepting: eAutoAccept ? AutoAccepting.ALL : AutoAccepting.NONE, + // P2P_AutoStart: eAutoStart, + // P2P_AutoBroadcast: eAutoBroadcast, + // }; + await core.services.setting.applyPartial( + { + P2P_Enabled: eP2PEnabled, + P2P_relays: eRelay, + P2P_roomID: eRoomId, + P2P_passphrase: ePassword, + P2P_AppID: eAppId, + P2P_AutoAccepting: eAutoAccept ? AutoAccepting.ALL : AutoAccepting.NONE, + P2P_AutoStart: eAutoStart, + P2P_AutoBroadcast: eAutoBroadcast, + }, + true + ); cmdSync.setConfig(SETTING_KEY_P2P_DEVICE_NAME, eDeviceName); deviceName = eDeviceName; - await plugin.saveSettings(); + // await plugin.saveSettings(); } async function revert() { eP2PEnabled = settings.P2P_Enabled; @@ -100,8 +113,9 @@ let serverInfo = $state(undefined); let replicatorInfo = $state(undefined); const applyLoadSettings = (d: P2PSyncSetting, force: boolean) => { - if(force){ - const initDeviceName = cmdSync.getConfig(SETTING_KEY_P2P_DEVICE_NAME) ?? plugin.services.vault.getVaultName(); + if (force) { + const initDeviceName = + cmdSync.getConfig(SETTING_KEY_P2P_DEVICE_NAME) ?? core.services.vault.getVaultName(); deviceName = initDeviceName; eDeviceName = initDeviceName; } @@ -124,7 +138,7 @@ closeServer(); }); const rx = eventHub.onEvent(EVENT_LAYOUT_READY, () => { - applyLoadSettings(plugin.settings, true); + applyLoadSettings(currentSettings(), true); }); const r2 = eventHub.onEvent(EVENT_SERVER_STATUS, (status) => { serverInfo = status; @@ -254,7 +268,7 @@ cmdSync.setConfig(initialDialogStatusKey, JSON.stringify(dialogStatus)); }); let isObsidian = $derived.by(() => { - return plugin.services.API.getPlatform() === "obsidian"; + return core.services.API.getPlatform() === "obsidian"; }); diff --git a/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts b/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts index 806da90..fcde9a2 100644 --- a/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts +++ b/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts @@ -13,6 +13,7 @@ import { EVENT_P2P_PEER_SHOW_EXTRA_MENU, type PeerStatus, } from "../../../lib/src/replication/trystero/P2PReplicatorPaneCommon.ts"; +import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts"; export const VIEW_TYPE_P2P = "p2p-replicator"; function addToList(item: string, list: string) { @@ -34,7 +35,8 @@ function removeFromList(item: string, list: string) { } export class P2PReplicatorPaneView extends SvelteItemView { - plugin: ObsidianLiveSyncPlugin; + // plugin: ObsidianLiveSyncPlugin; + core: LiveSyncBaseCore; override icon = "waypoints"; title: string = ""; override navigation = false; @@ -43,7 +45,7 @@ export class P2PReplicatorPaneView extends SvelteItemView { return "waypoints"; } get replicator() { - const r = this.plugin.getAddOn(P2PReplicator.name); + const r = this.core.getAddOn(P2PReplicator.name); if (!r || !r._replicatorInstance) { throw new Error("Replicator not found"); } @@ -66,7 +68,7 @@ export class P2PReplicatorPaneView extends SvelteItemView { const DROP = "Yes, and drop local database"; const KEEP = "Yes, but keep local database"; const CANCEL = "No, cancel"; - const yn = await this.plugin.confirm.askSelectStringDialogue( + const yn = await this.core.confirm.askSelectStringDialogue( `Do you really want to apply the remote config? This will overwrite your current config immediately and restart. And you can also drop the local database to rebuild from the remote device.`, [DROP, KEEP, CANCEL] as const, @@ -78,7 +80,7 @@ And you can also drop the local database to rebuild from the remote device.`, if (yn === DROP || yn === KEEP) { if (yn === DROP) { if (remoteConfig.remoteType !== REMOTE_P2P) { - const yn2 = await this.plugin.confirm.askYesNoDialog( + const yn2 = await this.core.confirm.askYesNoDialog( `Do you want to set the remote type to "P2P Sync" to rebuild by "P2P replication"?`, { title: "Rebuild from remote device", @@ -90,12 +92,14 @@ And you can also drop the local database to rebuild from the remote device.`, } } } - this.plugin.settings = remoteConfig; - await this.plugin.saveSettings(); + + // this.plugin.settings = remoteConfig; + // await this.plugin.saveSettings(); + await this.core.services.setting.applyPartial(remoteConfig); if (yn === DROP) { - await this.plugin.rebuilder.scheduleFetch(); + await this.core.rebuilder.scheduleFetch(); } else { - this.plugin.services.appLifecycle.scheduleRestart(); + this.core.services.appLifecycle.scheduleRestart(); } } else { Logger(`Cancelled\nRemote config for ${peer.name} is not applied`, LOG_LEVEL_NOTICE); @@ -113,19 +117,24 @@ And you can also drop the local database to rebuild from the remote device.`, } as const; const targetSetting = settingMap[prop]; + const currentSettingAll = this.core.services.setting.currentSettings(); + const currentSetting = { + [targetSetting]: currentSettingAll ? currentSettingAll[targetSetting] : "", + }; if (peer[prop]) { - this.plugin.settings[targetSetting] = removeFromList(peer.name, this.plugin.settings[targetSetting]); - await this.plugin.saveSettings(); + // this.plugin.settings[targetSetting] = removeFromList(peer.name, this.plugin.settings[targetSetting]); + // await this.plugin.saveSettings(); + currentSetting[targetSetting] = removeFromList(peer.name, currentSetting[targetSetting]); } else { - this.plugin.settings[targetSetting] = addToList(peer.name, this.plugin.settings[targetSetting]); - await this.plugin.saveSettings(); + currentSetting[targetSetting] = addToList(peer.name, currentSetting[targetSetting]); } - await this.plugin.saveSettings(); + await this.core.services.setting.applyPartial(currentSetting, true); } m?: Menu; - constructor(leaf: WorkspaceLeaf, plugin: ObsidianLiveSyncPlugin) { + constructor(leaf: WorkspaceLeaf, core: LiveSyncBaseCore, plugin: ObsidianLiveSyncPlugin) { super(leaf); - this.plugin = plugin; + // this.plugin = plugin; + this.core = core; eventHub.onEvent(EVENT_P2P_PEER_SHOW_EXTRA_MENU, ({ peer, event }) => { if (this.m) { this.m.hide(); @@ -183,15 +192,15 @@ And you can also drop the local database to rebuild from the remote device.`, } } instantiateComponent(target: HTMLElement) { - const cmdSync = this.plugin.getAddOn(P2PReplicator.name); + const cmdSync = this.core.getAddOn(P2PReplicator.name); if (!cmdSync) { throw new Error("Replicator not found"); } return mount(ReplicatorPaneComponent, { target: target, props: { - plugin: cmdSync.plugin, cmdSync: cmdSync, + core: this.core, }, }); } diff --git a/src/lib b/src/lib index 27d1d4a..7989f57 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 27d1d4a6e727a42307fe10c35d618864302fbbf7 +Subproject commit 7989f57e06c6858e3a99ebde02ec71d6a7811dbf diff --git a/src/main.ts b/src/main.ts index 3d63841..807ec55 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,396 +1,110 @@ import { Notice, Plugin, type App, type PluginManifest } from "./deps"; -import { - type EntryDoc, - type ObsidianLiveSyncSettings, - type HasSettings, - LOG_LEVEL_INFO, -} from "./lib/src/common/types.ts"; -import { type SimpleStore } from "./lib/src/common/utils.ts"; -import { type LiveSyncLocalDBEnv } from "./lib/src/pouchdb/LiveSyncLocalDB.ts"; -import { type LiveSyncReplicatorEnv } from "./lib/src/replication/LiveSyncAbstractReplicator.js"; + import { LiveSyncCommands } from "./features/LiveSyncCommands.ts"; import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts"; import { ConfigSync } from "./features/ConfigSync/CmdConfigSync.ts"; -import { type LiveSyncJournalReplicatorEnv } from "./lib/src/replication/journal/LiveSyncJournalReplicator.js"; -import { type LiveSyncCouchDBReplicatorEnv } from "./lib/src/replication/couchdb/LiveSyncReplicator.js"; -import type { CheckPointInfo } from "./lib/src/replication/journal/JournalSyncTypes.js"; -import type { IObsidianModule } from "./modules/AbstractObsidianModule.ts"; import { ModuleDev } from "./modules/extras/ModuleDev.ts"; -import { ModuleMigration } from "./modules/essential/ModuleMigration.ts"; -// import { ModuleCheckRemoteSize } from "./modules/essentialObsidian/ModuleCheckRemoteSize.ts"; -import { ModuleConflictResolver } from "./modules/coreFeatures/ModuleConflictResolver.ts"; import { ModuleInteractiveConflictResolver } from "./modules/features/ModuleInteractiveConflictResolver.ts"; import { ModuleLog } from "./modules/features/ModuleLog.ts"; -// import { ModuleRedFlag } from "./modules/coreFeatures/ModuleRedFlag.ts"; -import { ModuleObsidianMenu } from "./modules/essentialObsidian/ModuleObsidianMenu.ts"; -import { ModuleSetupObsidian } from "./modules/features/ModuleSetupObsidian.ts"; -import { SetupManager } from "./modules/features/SetupManager.ts"; -import type { StorageAccess } from "@lib/interfaces/StorageAccess.ts"; -import type { Confirm } from "./lib/src/interfaces/Confirm.ts"; -import type { Rebuilder } from "@lib/interfaces/DatabaseRebuilder.ts"; -import type { DatabaseFileAccess } from "@lib/interfaces/DatabaseFileAccess.ts"; import { ModuleObsidianEvents } from "./modules/essentialObsidian/ModuleObsidianEvents.ts"; -import { AbstractModule } from "./modules/AbstractModule.ts"; import { ModuleObsidianSettingDialogue } from "./modules/features/ModuleObsidianSettingTab.ts"; import { ModuleObsidianDocumentHistory } from "./modules/features/ModuleObsidianDocumentHistory.ts"; import { ModuleObsidianGlobalHistory } from "./modules/features/ModuleGlobalHistory.ts"; -import { ModuleObsidianSettingsAsMarkdown } from "./modules/features/ModuleObsidianSettingAsMarkdown.ts"; -// import { ModuleInitializerFile } from "./modules/essential/ModuleInitializerFile.ts"; -import { ModuleReplicator } from "./modules/core/ModuleReplicator.ts"; -import { ModuleReplicatorCouchDB } from "./modules/core/ModuleReplicatorCouchDB.ts"; -import { ModuleReplicatorMinIO } from "./modules/core/ModuleReplicatorMinIO.ts"; -import { ModulePeriodicProcess } from "./modules/core/ModulePeriodicProcess.ts"; -import { ModuleConflictChecker } from "./modules/coreFeatures/ModuleConflictChecker.ts"; -import { ModuleResolvingMismatchedTweaks } from "./modules/coreFeatures/ModuleResolveMismatchedTweaks.ts"; import { ModuleIntegratedTest } from "./modules/extras/ModuleIntegratedTest.ts"; import { ModuleReplicateTest } from "./modules/extras/ModuleReplicateTest.ts"; -import { ModuleLiveSyncMain } from "./modules/main/ModuleLiveSyncMain.ts"; import { LocalDatabaseMaintenance } from "./features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts"; import { P2PReplicator } from "./features/P2PSync/CmdP2PReplicator.ts"; import type { InjectableServiceHub } from "./lib/src/services/implements/injectable/InjectableServiceHub.ts"; import { ObsidianServiceHub } from "./modules/services/ObsidianServiceHub.ts"; -import type { ServiceContext } from "./lib/src/services/base/ServiceBase.ts"; import { ServiceRebuilder } from "@lib/serviceModules/Rebuilder.ts"; -import type { IFileHandler } from "@lib/interfaces/FileHandler.ts"; import { ServiceDatabaseFileAccess } from "@/serviceModules/DatabaseFileAccess.ts"; import { ServiceFileAccessObsidian } from "@/serviceModules/ServiceFileAccessImpl.ts"; import { StorageAccessManager } from "@lib/managers/StorageProcessingManager.ts"; -import { __$checkInstanceBinding } from "./lib/src/dev/checks.ts"; import { ServiceFileHandler } from "./serviceModules/FileHandler.ts"; import { FileAccessObsidian } from "./serviceModules/FileAccessObsidian.ts"; import { StorageEventManagerObsidian } from "./managers/StorageEventManagerObsidian.ts"; -import { onLayoutReadyFeatures } from "./serviceFeatures/onLayoutReady.ts"; import type { ServiceModules } from "./types.ts"; -import { useTargetFilters } from "@lib/serviceFeatures/targetFilter.ts"; import { setNoticeClass } from "@lib/mock_and_interop/wrapper.ts"; -import { useCheckRemoteSize } from "./lib/src/serviceFeatures/checkRemoteSize.ts"; -import { useRedFlagFeatures } from "./serviceFeatures/redFlag.ts"; -import { useOfflineScanner } from "./lib/src/serviceFeatures/offlineScanner.ts"; +import type { ObsidianServiceContext } from "./lib/src/services/implements/obsidian/ObsidianServiceContext.ts"; +import { LiveSyncBaseCore } from "./LiveSyncBaseCore.ts"; +import { ModuleSetupObsidian } from "./modules/features/ModuleSetupObsidian.ts"; +import { ModuleObsidianMenu } from "./modules/essentialObsidian/ModuleObsidianMenu.ts"; +import { ModuleObsidianSettingsAsMarkdown } from "./modules/features/ModuleObsidianSettingAsMarkdown.ts"; +import { SetupManager } from "./modules/features/SetupManager.ts"; +import { ModuleMigration } from "./modules/essential/ModuleMigration.ts"; +import { enableI18nFeature } from "./serviceFeatures/onLayoutReady/enablei18n.ts"; +export type LiveSyncCore = LiveSyncBaseCore; +export default class ObsidianLiveSyncPlugin extends Plugin { + core: LiveSyncCore; -export default class ObsidianLiveSyncPlugin - extends Plugin - implements - LiveSyncLocalDBEnv, - LiveSyncReplicatorEnv, - LiveSyncJournalReplicatorEnv, - LiveSyncCouchDBReplicatorEnv, - HasSettings -{ - /** - * The service hub for managing all services. - */ - _services: InjectableServiceHub | undefined = undefined; - - get services() { - if (!this._services) { - throw new Error("Services not initialised yet"); - } - return this._services; - } - - /** - * Service Modules - */ - protected _serviceModules: ServiceModules; - - get serviceModules() { - return this._serviceModules; - } - - /** - * addOns: Non-essential and graphically features - */ - addOns = [] as LiveSyncCommands[]; - - /** - * The modules of the plug-in. Modules are responsible for specific features or functionalities of the plug-in, such as file handling, conflict resolution, replication, etc. - */ - private modules = [ - // Move to registerModules - ] as (IObsidianModule | AbstractModule)[]; - - /** - * register an add-onn to the plug-in. - * Add-ons are features that are not essential to the core functionality of the plugin, - * @param addOn - */ - private _registerAddOn(addOn: LiveSyncCommands) { - this.addOns.push(addOn); - this.services.appLifecycle.onUnload.addHandler(() => Promise.resolve(addOn.onunload()).then(() => true)); - } - - private registerAddOns() { - this._registerAddOn(new ConfigSync(this)); - this._registerAddOn(new HiddenFileSync(this)); - this._registerAddOn(new LocalDatabaseMaintenance(this)); - this._registerAddOn(new P2PReplicator(this)); - } - - /** - * Get an add-on by its class name. Returns undefined if not found. - * @param cls - * @returns - */ - getAddOn(cls: string) { - for (const addon of this.addOns) { - if (addon.constructor.name == cls) return addon as T; - } - return undefined; - } - - /** - * Get a module by its class. Throws an error if not found. - * Mostly used for getting SetupManager. - * @param constructor - * @returns - */ - getModule(constructor: new (...args: any[]) => T): T { - for (const module of this.modules) { - if (module.constructor === constructor) return module as T; - } - throw new Error(`Module ${constructor} not found or not loaded.`); - } - - /** - * Register a module to the plug-in. - * @param module The module to register. - */ - private _registerModule(module: IObsidianModule) { - this.modules.push(module); - } - private registerModules() { - this._registerModule(new ModuleLiveSyncMain(this)); - this._registerModule(new ModuleConflictChecker(this)); - this._registerModule(new ModuleReplicatorMinIO(this)); - this._registerModule(new ModuleReplicatorCouchDB(this)); - this._registerModule(new ModuleReplicator(this)); - this._registerModule(new ModuleConflictResolver(this)); - this._registerModule(new ModulePeriodicProcess(this)); - // this._registerModule(new ModuleInitializerFile(this)); - this._registerModule(new ModuleObsidianEvents(this, this)); - this._registerModule(new ModuleResolvingMismatchedTweaks(this)); - this._registerModule(new ModuleObsidianSettingsAsMarkdown(this)); - this._registerModule(new ModuleObsidianSettingDialogue(this, this)); - this._registerModule(new ModuleLog(this, this)); - this._registerModule(new ModuleObsidianMenu(this)); - this._registerModule(new ModuleSetupObsidian(this)); - this._registerModule(new ModuleObsidianDocumentHistory(this, this)); - this._registerModule(new ModuleMigration(this)); - // this._registerModule(new ModuleRedFlag(this)); - this._registerModule(new ModuleInteractiveConflictResolver(this, this)); - this._registerModule(new ModuleObsidianGlobalHistory(this, this)); - // this._registerModule(new ModuleCheckRemoteSize(this)); - // Test and Dev Modules - this._registerModule(new ModuleDev(this, this)); - this._registerModule(new ModuleReplicateTest(this, this)); - this._registerModule(new ModuleIntegratedTest(this, this)); - this._registerModule(new SetupManager(this)); - } - - /** - * Bind module functions to services. - */ - private bindModuleFunctions() { - for (const module of this.modules) { - if (module instanceof AbstractModule) { - module.onBindFunction(this, this.services); - __$checkInstanceBinding(module); // Check if all functions are properly bound, and log warnings if not. - } else { - this.services.API.addLog( - `Module ${module.constructor.name} does not have onBindFunction, skipping binding.`, - LOG_LEVEL_INFO - ); - } - } - } - - /** - * @obsolete Use services.UI.confirm instead. The confirm function to show a confirmation dialog to the user. - */ - get confirm(): Confirm { - return this.services.UI.confirm; - } - - /** - * @obsolete Use services.setting.currentSettings instead. The current settings of the plug-in. - */ - get settings() { - return this.services.setting.settings; - } - - /** - * @obsolete Use services.setting.settings instead. Set the settings of the plug-in. - */ - set settings(value: ObsidianLiveSyncSettings) { - this.services.setting.settings = value; - } - - /** - * @obsolete Use services.setting.currentSettings instead. Get the settings of the plug-in. - * @returns The current settings of the plug-in. - */ - getSettings(): ObsidianLiveSyncSettings { - return this.settings; - } - - /** - * @obsolete Use services.database.localDatabase instead. The local database instance. - */ - get localDatabase() { - return this.services.database.localDatabase; - } - - /** - * @obsolete Use services.database.localDatabase instead. Get the PouchDB database instance. Note that this is not the same as the local database instance, which is a wrapper around the PouchDB database. - * @returns The PouchDB database instance. - */ - getDatabase(): PouchDB.Database { - return this.localDatabase.localDatabase; - } - - /** - * @obsolete Use services.keyValueDB.simpleStore instead. A simple key-value store for storing non-file data, such as checkpoints, sync status, etc. - */ - get simpleStore() { - return this.services.keyValueDB.simpleStore as SimpleStore; - } - - /** - * @obsolete Use services.replication.getActiveReplicator instead. Get the active replicator instance. Note that there can be multiple replicators, but only one can be active at a time. - */ - get replicator() { - return this.services.replicator.getActiveReplicator()!; - } - - /** - * @obsolete Use services.keyValueDB.kvDB instead. Get the key-value database instance. This is used for storing large data that cannot be stored in the simple store, such as file metadata, etc. - */ - get kvDB() { - return this.services.keyValueDB.kvDB; - } - - /// Modules which were relied on services - /** - * Storage Accessor for handling file operations. - * @obsolete Use serviceModules.storageAccess instead. - */ - get storageAccess(): StorageAccess { - return this.serviceModules.storageAccess; - } - /** - * Database File Accessor for handling file operations related to the database, such as exporting the database, importing from a file, etc. - * @obsolete Use serviceModules.databaseFileAccess instead. - */ - get databaseFileAccess(): DatabaseFileAccess { - return this.serviceModules.databaseFileAccess; - } - /** - * File Handler for handling file operations related to replication, such as resolving conflicts, applying changes from replication, etc. - * @obsolete Use serviceModules.fileHandler instead. - */ - get fileHandler(): IFileHandler { - return this.serviceModules.fileHandler; - } - /** - * Rebuilder for handling database rebuilding operations. - * @obsolete Use serviceModules.rebuilder instead. - */ - get rebuilder(): Rebuilder { - return this.serviceModules.rebuilder; - } - - // requestCount = reactiveSource(0); - // responseCount = reactiveSource(0); - // totalQueued = reactiveSource(0); - // batched = reactiveSource(0); - // processing = reactiveSource(0); - // databaseQueueCount = reactiveSource(0); - // storageApplyingCount = reactiveSource(0); - // replicationResultCount = reactiveSource(0); - - // pendingFileEventCount = reactiveSource(0); - // processingFileEventCount = reactiveSource(0); - - // _totalProcessingCount?: ReactiveValue; - - // replicationStat = reactiveSource({ - // sent: 0, - // arrived: 0, - // maxPullSeq: 0, - // maxPushSeq: 0, - // lastSyncPullSeq: 0, - // lastSyncPushSeq: 0, - // syncStatus: "CLOSED" as DatabaseConnectingStatus, - // }); - - private initialiseServices() { - this._services = new ObsidianServiceHub(this); - } /** * Initialise service modules. */ - private initialiseServiceModules() { + private initialiseServiceModules( + core: LiveSyncBaseCore, + services: InjectableServiceHub + ): ServiceModules { const storageAccessManager = new StorageAccessManager(); // If we want to implement to the other platform, implement ObsidianXXXXXService. const vaultAccess = new FileAccessObsidian(this.app, { storageAccessManager: storageAccessManager, - vaultService: this.services.vault, - settingService: this.services.setting, - APIService: this.services.API, - pathService: this.services.path, + vaultService: services.vault, + settingService: services.setting, + APIService: services.API, + pathService: services.path, }); - const storageEventManager = new StorageEventManagerObsidian(this, this, { - fileProcessing: this.services.fileProcessing, - setting: this.services.setting, - vaultService: this.services.vault, + const storageEventManager = new StorageEventManagerObsidian(this, core, { + fileProcessing: services.fileProcessing, + setting: services.setting, + vaultService: services.vault, storageAccessManager: storageAccessManager, - APIService: this.services.API, + APIService: services.API, }); const storageAccess = new ServiceFileAccessObsidian({ - API: this.services.API, - setting: this.services.setting, - fileProcessing: this.services.fileProcessing, - vault: this.services.vault, - appLifecycle: this.services.appLifecycle, + API: services.API, + setting: services.setting, + fileProcessing: services.fileProcessing, + vault: services.vault, + appLifecycle: services.appLifecycle, storageEventManager: storageEventManager, storageAccessManager: storageAccessManager, vaultAccess: vaultAccess, }); const databaseFileAccess = new ServiceDatabaseFileAccess({ - API: this.services.API, - database: this.services.database, - path: this.services.path, + API: services.API, + database: services.database, + path: services.path, storageAccess: storageAccess, - vault: this.services.vault, + vault: services.vault, }); const fileHandler = new ServiceFileHandler({ - API: this.services.API, + API: services.API, databaseFileAccess: databaseFileAccess, - conflict: this.services.conflict, - setting: this.services.setting, - fileProcessing: this.services.fileProcessing, - vault: this.services.vault, - path: this.services.path, - replication: this.services.replication, + conflict: services.conflict, + setting: services.setting, + fileProcessing: services.fileProcessing, + vault: services.vault, + path: services.path, + replication: services.replication, storageAccess: storageAccess, }); const rebuilder = new ServiceRebuilder({ - API: this.services.API, - database: this.services.database, - appLifecycle: this.services.appLifecycle, - setting: this.services.setting, - remote: this.services.remote, - databaseEvents: this.services.databaseEvents, - replication: this.services.replication, - replicator: this.services.replicator, - UI: this.services.UI, - vault: this.services.vault, + API: services.API, + database: services.database, + appLifecycle: services.appLifecycle, + setting: services.setting, + remote: services.remote, + databaseEvents: services.databaseEvents, + replication: services.replication, + replicator: services.replicator, + UI: services.UI, + vault: services.vault, fileHandler: fileHandler, storageAccess: storageAccess, - control: this.services.control, + control: services.control, }); return { rebuilder, @@ -404,24 +118,7 @@ export default class ObsidianLiveSyncPlugin * @obsolete Use services.setting.saveSettingData instead. Save the settings to the disk. This is usually called after changing the settings in the code, to persist the changes. */ async saveSettings() { - await this.services.setting.saveSettingData(); - } - - /** - * Initialise ServiceFeatures. - * (Please refer `serviceFeatures` for more details) - */ - initialiseServiceFeatures() { - for (const feature of onLayoutReadyFeatures) { - const curriedFeature = () => feature(this); - this.services.appLifecycle.onLayoutReady.addHandler(curriedFeature); - } - useRedFlagFeatures(this); - useOfflineScanner(this); - - // enable target filter feature. - useTargetFilters(this); - useCheckRemoteSize(this); + await this.core.services.setting.saveSettingData(); } constructor(app: App, manifest: PluginManifest) { @@ -429,26 +126,60 @@ export default class ObsidianLiveSyncPlugin // Maybe no more need to setNoticeClass, but for safety, set it in the constructor of the main plugin class. // TODO: remove this. setNoticeClass(Notice); - this.initialiseServices(); - this.registerModules(); - this.registerAddOns(); - this._serviceModules = this.initialiseServiceModules(); - this.initialiseServiceFeatures(); - this.bindModuleFunctions(); + + const serviceHub = new ObsidianServiceHub(this); + + this.core = new LiveSyncBaseCore( + serviceHub, + (core, serviceHub) => { + return this.initialiseServiceModules(core, serviceHub); + }, + (core) => { + const extraModules = [ + new ModuleObsidianEvents(this, core), + new ModuleObsidianSettingDialogue(this, core), + new ModuleObsidianMenu(core), + new ModuleSetupObsidian(core), + new ModuleObsidianSettingsAsMarkdown(core), + new ModuleLog(this, core), + new ModuleObsidianDocumentHistory(this, core), + new ModuleInteractiveConflictResolver(this, core), + new ModuleObsidianGlobalHistory(this, core), + new ModuleDev(this, core), + new ModuleReplicateTest(this, core), + new ModuleIntegratedTest(this, core), + new SetupManager(core), // this should be moved to core? + new ModuleMigration(core), + ]; + return extraModules; + }, + (core) => { + const addOns = [ + new ConfigSync(this, core), + new HiddenFileSync(this, core), + new LocalDatabaseMaintenance(this, core), + new P2PReplicator(this, core), + ]; + return addOns; + }, + (core) => { + //TODO Fix: useXXXX + const featuresInitialiser = enableI18nFeature; + const curriedFeature = () => featuresInitialiser(core); + core.services.appLifecycle.onLayoutReady.addHandler(curriedFeature); + } + ); } private async _startUp() { - if (!(await this.services.control.onLoad())) return; - const onReady = this.services.control.onReady.bind(this.services.control); + if (!(await this.core.services.control.onLoad())) return; + const onReady = this.core.services.control.onReady.bind(this.core.services.control); this.app.workspace.onLayoutReady(onReady); } override onload() { void this._startUp(); } override onunload() { - return void this.services.control.onUnload(); + return void this.core.services.control.onUnload(); } } - -// For now, -export type LiveSyncCore = ObsidianLiveSyncPlugin; diff --git a/src/managers/StorageEventManagerObsidian.ts b/src/managers/StorageEventManagerObsidian.ts index 979ccdc..d57f204 100644 --- a/src/managers/StorageEventManagerObsidian.ts +++ b/src/managers/StorageEventManagerObsidian.ts @@ -1,4 +1,3 @@ -import { HiddenFileSync } from "@/features/HiddenFileSync/CmdHiddenFileSync"; import type { FilePath } from "@lib/common/types"; import type ObsidianLiveSyncPlugin from "@/main"; import type { LiveSyncCore } from "@/main"; @@ -6,18 +5,15 @@ import { StorageEventManagerBase, type StorageEventManagerBaseDependencies } fro import { ObsidianStorageEventManagerAdapter } from "./ObsidianStorageEventManagerAdapter"; export class StorageEventManagerObsidian extends StorageEventManagerBase { - plugin: ObsidianLiveSyncPlugin; core: LiveSyncCore; // Necessary evil. - cmdHiddenFileSync: HiddenFileSync; + // cmdHiddenFileSync: HiddenFileSync; constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore, dependencies: StorageEventManagerBaseDependencies) { const adapter = new ObsidianStorageEventManagerAdapter(plugin, core, dependencies.fileProcessing); super(adapter, dependencies); - this.plugin = plugin; this.core = core; - this.cmdHiddenFileSync = this.plugin.getAddOn(HiddenFileSync.name) as HiddenFileSync; } /** @@ -26,12 +22,12 @@ export class StorageEventManagerObsidian extends StorageEventManagerBase = LiveSyncBaseCore< + ServiceContext, + IMinimumLiveSyncCommands + >, +> { _log = createInstanceLogFunction(this.constructor.name, this.services.API); get services() { if (!this.core._services) { @@ -36,13 +42,13 @@ export abstract class AbstractModule { return stripAllPrefixes(this.services.path.getPath(entry)); } - onBindFunction(core: LiveSyncCore, services: typeof core.services) { + onBindFunction(core: T, services: typeof core.services) { // Override if needed. } - constructor(public core: LiveSyncCore) { + constructor(public core: T) { Logger(`[${this.constructor.name}] Loaded`, LOG_LEVEL_VERBOSE); } - saveSettings = this.core.saveSettings.bind(this.core); + saveSettings = this.core.services.setting.saveSettingData.bind(this.core.services.setting); addTestResult(key: string, value: boolean, summary?: string, message?: string) { this.services.test.addTestResult(`${this.constructor.name}`, key, value, summary, message); diff --git a/src/modules/core/ModulePeriodicProcess.ts b/src/modules/core/ModulePeriodicProcess.ts index a50a95e..2eef12f 100644 --- a/src/modules/core/ModulePeriodicProcess.ts +++ b/src/modules/core/ModulePeriodicProcess.ts @@ -1,4 +1,4 @@ -import { PeriodicProcessor } from "../../common/utils"; +import { PeriodicProcessor } from "@/common/PeriodicProcessor"; import type { LiveSyncCore } from "../../main"; import { AbstractModule } from "../AbstractModule"; diff --git a/src/modules/core/ModuleReplicator.ts b/src/modules/core/ModuleReplicator.ts index 0150fbe..79b7b49 100644 --- a/src/modules/core/ModuleReplicator.ts +++ b/src/modules/core/ModuleReplicator.ts @@ -6,7 +6,8 @@ import { balanceChunkPurgedDBs } from "@lib/pouchdb/chunks"; import { purgeUnreferencedChunks } from "@lib/pouchdb/chunks"; import { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator"; import { type EntryDoc, type RemoteType } from "../../lib/src/common/types"; -import { scheduleTask } from "../../common/utils"; + +import { scheduleTask } from "octagonal-wheels/concurrency/task"; import { EVENT_FILE_SAVED, EVENT_SETTING_SAVED, eventHub } from "../../common/events"; import { $msg } from "../../lib/src/common/i18n"; diff --git a/src/modules/core/ReplicateResultProcessor.ts b/src/modules/core/ReplicateResultProcessor.ts index 1fe9a53..fb35a5b 100644 --- a/src/modules/core/ReplicateResultProcessor.ts +++ b/src/modules/core/ReplicateResultProcessor.ts @@ -8,8 +8,7 @@ import { type MetaEntry, } from "@lib/common/types"; import type { ModuleReplicator } from "./ModuleReplicator"; -import { isChunk, isValidPath } from "@/common/utils"; -import type { LiveSyncCore } from "@/main"; +import { isChunk } from "@/lib/src/common/typeUtils"; import { LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, @@ -22,6 +21,7 @@ import { fireAndForget, isAnyNote, throttle } from "@lib/common/utils"; import { Semaphore } from "octagonal-wheels/concurrency/semaphore_v2"; import { serialized } from "octagonal-wheels/concurrency/lock"; import type { ReactiveSource } from "octagonal-wheels/dataobject/reactive_v2"; +import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore"; const KV_KEY_REPLICATION_RESULT_PROCESSOR_SNAPSHOT = "replicationResultProcessorSnapshot"; type ReplicateResultProcessorState = { @@ -54,7 +54,7 @@ export class ReplicateResultProcessor { get services() { return this.replicator.core.services; } - get core(): LiveSyncCore { + get core(): LiveSyncBaseCore { return this.replicator.core; } @@ -414,7 +414,7 @@ export class ReplicateResultProcessor { if (await this.services.replication.processOptionalSynchroniseResult(dbDoc)) { // Already processed this.log(`Processed by other processor: ${docNote}`, LOG_LEVEL_DEBUG); - } else if (isValidPath(this.getPath(doc))) { + } else if (this.services.vault.isValidPath(this.getPath(doc))) { // Apply to storage if the path is valid await this.applyToStorage(doc as MetaEntry); this.log(`Processed: ${docNote}`, LOG_LEVEL_DEBUG); diff --git a/src/modules/coreFeatures/ModuleConflictResolver.ts b/src/modules/coreFeatures/ModuleConflictResolver.ts index f09271a..ebce9d4 100644 --- a/src/modules/coreFeatures/ModuleConflictResolver.ts +++ b/src/modules/coreFeatures/ModuleConflictResolver.ts @@ -11,13 +11,9 @@ import { type diff_check_result, type FilePathWithPrefix, } from "../../lib/src/common/types"; -import { - compareMTime, - displayRev, - isCustomisationSyncMetadata, - isPluginMetadata, - TARGET_IS_NEW, -} from "../../common/utils"; +import { isCustomisationSyncMetadata, isPluginMetadata } from "@lib/common/typeUtils.ts"; +import { TARGET_IS_NEW } from "@lib/common/models/shared.const.symbols.ts"; +import { compareMTime, displayRev } from "@lib/common/utils.ts"; import diff_match_patch from "diff-match-patch"; import { stripAllPrefixes, isPlainText } from "../../lib/src/string_and_binary/path"; import { eventHub } from "../../common/events.ts"; @@ -214,7 +210,7 @@ export class ModuleConflictResolver extends AbstractModule { private async _resolveAllConflictedFilesByNewerOnes() { this._log(`Resolving conflicts by newer ones`, LOG_LEVEL_NOTICE); - const files = this.core.storageAccess.getFileNames(); + const files = await this.core.storageAccess.getFileNames(); let i = 0; for (const file of files) { diff --git a/src/modules/essential/ModuleBasicMenu.ts b/src/modules/essential/ModuleBasicMenu.ts new file mode 100644 index 0000000..5728be8 --- /dev/null +++ b/src/modules/essential/ModuleBasicMenu.ts @@ -0,0 +1,86 @@ +import type { LiveSyncCore } from "@/main"; +import { LOG_LEVEL_NOTICE } from "octagonal-wheels/common/logger"; +import { fireAndForget } from "octagonal-wheels/promises"; +import { AbstractModule } from "../AbstractModule"; +// Separated Module for basic menu commands, which are not related to obsidian specific features. It is expected to be used in other platforms with minimal changes. +// However, it is odd that it has here at all; it really ought to be in each respective feature. It will likely be moved eventually. Until now, addCommand pointed to Obsidian's version. +export class ModuleBasicMenu extends AbstractModule { + _everyOnloadStart(): Promise { + this.addCommand({ + id: "livesync-replicate", + name: "Replicate now", + callback: async () => { + await this.services.replication.replicate(); + }, + }); + this.addCommand({ + id: "livesync-dump", + name: "Dump information of this doc ", + callback: () => { + const file = this.services.vault.getActiveFilePath(); + if (!file) return; + fireAndForget(() => this.localDatabase.getDBEntry(file, {}, true, false)); + }, + }); + this.addCommand({ + id: "livesync-toggle", + name: "Toggle LiveSync", + callback: async () => { + if (this.settings.liveSync) { + this.settings.liveSync = false; + this._log("LiveSync Disabled.", LOG_LEVEL_NOTICE); + } else { + this.settings.liveSync = true; + this._log("LiveSync Enabled.", LOG_LEVEL_NOTICE); + } + await this.services.control.applySettings(); + await this.services.setting.saveSettingData(); + }, + }); + this.addCommand({ + id: "livesync-suspendall", + name: "Toggle All Sync.", + callback: async () => { + if (this.services.appLifecycle.isSuspended()) { + this.services.appLifecycle.setSuspended(false); + this._log("Self-hosted LiveSync resumed", LOG_LEVEL_NOTICE); + } else { + this.services.appLifecycle.setSuspended(true); + this._log("Self-hosted LiveSync suspended", LOG_LEVEL_NOTICE); + } + await this.services.control.applySettings(); + await this.services.setting.saveSettingData(); + }, + }); + + this.addCommand({ + id: "livesync-scan-files", + name: "Scan storage and database again", + callback: async () => { + await this.services.vault.scanVault(true); + }, + }); + + this.addCommand({ + id: "livesync-runbatch", + name: "Run pended batch processes", + callback: async () => { + await this.services.fileProcessing.commitPendingFileEvents(); + }, + }); + + // TODO, Replicator is possibly one of features. It should be moved to features. + this.addCommand({ + id: "livesync-abortsync", + name: "Abort synchronization immediately", + callback: () => { + this.core.replicator.terminateSync(); + }, + }); + return Promise.resolve(true); + } + + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); + } +} diff --git a/src/modules/essential/ModuleInitializerFile.ts b/src/modules/essential/ModuleInitializerFile.ts index b122b94..b26ac88 100644 --- a/src/modules/essential/ModuleInitializerFile.ts +++ b/src/modules/essential/ModuleInitializerFile.ts @@ -73,7 +73,7 @@ export class ModuleInitializerFile extends AbstractModule { await this.collectDeletedFiles(); this._log("Collecting local files on the storage", LOG_LEVEL_VERBOSE); - const filesStorageSrc = this.core.storageAccess.getFiles(); + const filesStorageSrc = await this.core.storageAccess.getFiles(); const _filesStorage = [] as typeof filesStorageSrc; @@ -300,7 +300,7 @@ export class ModuleInitializerFile extends AbstractModule { throw new Error(`Missing doc:${(file as any).path}`); } if ("path" in file) { - const w = this.core.storageAccess.getFileStub((file as any).path); + const w = await this.core.storageAccess.getFileStub((file as any).path); if (w) { file = w; } else { diff --git a/src/modules/essential/ModuleMigration.ts b/src/modules/essential/ModuleMigration.ts index edd172c..e19dc39 100644 --- a/src/modules/essential/ModuleMigration.ts +++ b/src/modules/essential/ModuleMigration.ts @@ -8,7 +8,7 @@ import { eventHub, } from "../../common/events.ts"; import { AbstractModule } from "../AbstractModule.ts"; -import { $msg } from "src/lib/src/common/i18n.ts"; +import { $msg } from "@lib/common/i18n.ts"; import { performDoctorConsultation, RebuildOptions } from "../../lib/src/common/configForDoc.ts"; import { isValidPath } from "../../common/utils.ts"; import { isMetaEntry } from "../../lib/src/common/types.ts"; @@ -40,7 +40,7 @@ export class ModuleMigration extends AbstractModule { ); if (isModified) { this.settings = settings; - await this.core.saveSettings(); + await this.saveSettings(); } if (!skipRebuild) { if (shouldRebuild) { @@ -231,7 +231,7 @@ export class ModuleMigration extends AbstractModule { if (ret == FIX) { for (const file of recoverable) { // Overwrite the database with the files on the storage - const stubFile = this.core.storageAccess.getFileStub(file.path); + const stubFile = await this.core.storageAccess.getFileStub(file.path); if (stubFile == null) { Logger(`Could not find stub file for ${file.path}`, LOG_LEVEL_NOTICE); continue; diff --git a/src/modules/essentialObsidian/ModuleCheckRemoteSize_obsolete.ts b/src/modules/essentialObsidian/ModuleCheckRemoteSize_obsolete.ts index b27c286..7d1c9a3 100644 --- a/src/modules/essentialObsidian/ModuleCheckRemoteSize_obsolete.ts +++ b/src/modules/essentialObsidian/ModuleCheckRemoteSize_obsolete.ts @@ -35,13 +35,13 @@ export class ModuleCheckRemoteSize extends AbstractModule { ); if (ret == ANSWER_0) { this.settings.notifyThresholdOfRemoteStorageSize = 0; - await this.core.saveSettings(); + await this.saveSettings(); } else if (ret == ANSWER_800) { this.settings.notifyThresholdOfRemoteStorageSize = 800; - await this.core.saveSettings(); + await this.saveSettings(); } else if (ret == ANSWER_2000) { this.settings.notifyThresholdOfRemoteStorageSize = 2000; - await this.core.saveSettings(); + await this.saveSettings(); } } if (this.settings.notifyThresholdOfRemoteStorageSize > 0) { @@ -88,7 +88,8 @@ export class ModuleCheckRemoteSize extends AbstractModule { }), LOG_LEVEL_NOTICE ); - await this.core.saveSettings(); + // await this.core.saveSettings(); + await this.core.services.setting.saveSettingData(); } else { // Dismiss or Close the dialog } diff --git a/src/modules/essentialObsidian/ModuleObsidianMenu.ts b/src/modules/essentialObsidian/ModuleObsidianMenu.ts index cab9093..40597a0 100644 --- a/src/modules/essentialObsidian/ModuleObsidianMenu.ts +++ b/src/modules/essentialObsidian/ModuleObsidianMenu.ts @@ -1,10 +1,10 @@ -import { fireAndForget } from "octagonal-wheels/promises"; -import { addIcon, type Editor, type MarkdownFileInfo, type MarkdownView } from "../../deps.ts"; -import { LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "../../lib/src/common/types.ts"; -import { $msg } from "src/lib/src/common/i18n.ts"; -import type { LiveSyncCore } from "../../main.ts"; +import { type Editor, type MarkdownFileInfo, type MarkdownView } from "@/deps.ts"; +import { addIcon } from "@/deps.ts"; +import { type FilePathWithPrefix } from "@lib/common/types.ts"; +import { $msg } from "@lib/common/i18n.ts"; +import type { LiveSyncCore } from "@/main.ts"; import { AbstractModule } from "../AbstractModule.ts"; - +// Obsidian specific menu commands. export class ModuleObsidianMenu extends AbstractModule { _everyOnloadStart(): Promise { // UI @@ -22,22 +22,6 @@ export class ModuleObsidianMenu extends AbstractModule { await this.services.replication.replicate(true); }).addClass("livesync-ribbon-replicate"); - this.addCommand({ - id: "livesync-replicate", - name: "Replicate now", - callback: async () => { - await this.services.replication.replicate(); - }, - }); - this.addCommand({ - id: "livesync-dump", - name: "Dump information of this doc ", - callback: () => { - const file = this.services.vault.getActiveFilePath(); - if (!file) return; - fireAndForget(() => this.localDatabase.getDBEntry(file, {}, true, false)); - }, - }); this.addCommand({ id: "livesync-checkdoc-conflicted", name: "Resolve if conflicted.", @@ -48,61 +32,6 @@ export class ModuleObsidianMenu extends AbstractModule { }, }); - this.addCommand({ - id: "livesync-toggle", - name: "Toggle LiveSync", - callback: async () => { - if (this.settings.liveSync) { - this.settings.liveSync = false; - this._log("LiveSync Disabled.", LOG_LEVEL_NOTICE); - } else { - this.settings.liveSync = true; - this._log("LiveSync Enabled.", LOG_LEVEL_NOTICE); - } - await this.services.control.applySettings(); - await this.services.setting.saveSettingData(); - }, - }); - this.addCommand({ - id: "livesync-suspendall", - name: "Toggle All Sync.", - callback: async () => { - if (this.services.appLifecycle.isSuspended()) { - this.services.appLifecycle.setSuspended(false); - this._log("Self-hosted LiveSync resumed", LOG_LEVEL_NOTICE); - } else { - this.services.appLifecycle.setSuspended(true); - this._log("Self-hosted LiveSync suspended", LOG_LEVEL_NOTICE); - } - await this.services.control.applySettings(); - await this.services.setting.saveSettingData(); - }, - }); - - this.addCommand({ - id: "livesync-scan-files", - name: "Scan storage and database again", - callback: async () => { - await this.services.vault.scanVault(true); - }, - }); - - this.addCommand({ - id: "livesync-runbatch", - name: "Run pended batch processes", - callback: async () => { - await this.services.fileProcessing.commitPendingFileEvents(); - }, - }); - - // TODO, Replicator is possibly one of features. It should be moved to features. - this.addCommand({ - id: "livesync-abortsync", - name: "Abort synchronization immediately", - callback: () => { - this.core.replicator.terminateSync(); - }, - }); return Promise.resolve(true); } diff --git a/src/modules/extras/ModuleReplicateTest.ts b/src/modules/extras/ModuleReplicateTest.ts index 999bdce..394042a 100644 --- a/src/modules/extras/ModuleReplicateTest.ts +++ b/src/modules/extras/ModuleReplicateTest.ts @@ -1,3 +1,4 @@ +// I intend to discontinue maintenance of this class. It seems preferable to test it externally. import { delay } from "octagonal-wheels/promises"; import { AbstractObsidianModule } from "../AbstractObsidianModule.ts"; import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger"; @@ -169,7 +170,7 @@ export class ModuleReplicateTest extends AbstractObsidianModule { this._log("No storage access", LOG_LEVEL_INFO); return; } - const files = this.core.storageAccess.getFiles(); + const files = await this.core.storageAccess.getFiles(); const out = [] as any[]; const webcrypto = await getWebCrypto(); for (const file of files) { @@ -205,8 +206,8 @@ export class ModuleReplicateTest extends AbstractObsidianModule { } async __dumpFileListIncludeHidden(outFile?: string) { - const ignorePatterns = getFileRegExp(this.plugin.settings, "syncInternalFilesIgnorePatterns"); - const targetPatterns = getFileRegExp(this.plugin.settings, "syncInternalFilesTargetPatterns"); + const ignorePatterns = getFileRegExp(this.core.settings, "syncInternalFilesIgnorePatterns"); + const targetPatterns = getFileRegExp(this.core.settings, "syncInternalFilesTargetPatterns"); const out = [] as any[]; const files = await this.core.storageAccess.getFilesIncludeHidden("", targetPatterns, ignorePatterns); // console.dir(files); diff --git a/src/modules/extras/devUtil/TestPane.svelte b/src/modules/extras/devUtil/TestPane.svelte index f7d6bbb..6ea0eea 100644 --- a/src/modules/extras/devUtil/TestPane.svelte +++ b/src/modules/extras/devUtil/TestPane.svelte @@ -9,6 +9,7 @@ import { writable } from "svelte/store"; export let plugin: ObsidianLiveSyncPlugin; export let moduleDev: ModuleDev; + $: core = plugin.core; let performanceTestResult = ""; let functionCheckResult = ""; let testRunning = false; @@ -42,7 +43,7 @@ // performTest(); eventHub.onceEvent(EVENT_LAYOUT_READY, async () => { - if (await plugin.storageAccess.isExistsIncludeHidden("_AUTO_TEST.md")) { + if (await core.storageAccess.isExistsIncludeHidden("_AUTO_TEST.md")) { new Notice("Auto test file found, running tests..."); fireAndForget(async () => { await allTest(); @@ -57,14 +58,14 @@ function moduleMultiDeviceTest() { if (moduleTesting) return; moduleTesting = true; - plugin.services.test.testMultiDevice().finally(() => { + core.services.test.testMultiDevice().finally(() => { moduleTesting = false; }); } function moduleSingleDeviceTest() { if (moduleTesting) return; moduleTesting = true; - plugin.services.test.test().finally(() => { + core.services.test.test().finally(() => { moduleTesting = false; }); } @@ -72,8 +73,8 @@ if (moduleTesting) return; moduleTesting = true; try { - await plugin.services.test.test(); - await plugin.services.test.testMultiDevice(); + await core.services.test.test(); + await core.services.test.testMultiDevice(); } finally { moduleTesting = false; } diff --git a/src/modules/extras/devUtil/testUtils.ts b/src/modules/extras/devUtil/testUtils.ts index 6d3e079..6d1e86e 100644 --- a/src/modules/extras/devUtil/testUtils.ts +++ b/src/modules/extras/devUtil/testUtils.ts @@ -38,8 +38,8 @@ export function addDebugFileLog(message: any, stackLog = false) { // const out = "--" + timestamp + "--\n" + messageContent + " " + (stack || ""); // const out try { - await plugin.storageAccess.appendHiddenFile( - plugin.app.vault.configDir + "/ls-debug/" + outFile, + await plugin.core.storageAccess.appendHiddenFile( + plugin.core.services.API.getSystemConfigDir() + "/ls-debug/" + outFile, JSON.stringify(out) + "\n" ); } catch { diff --git a/src/modules/extras/devUtil/tests.ts b/src/modules/extras/devUtil/tests.ts index ce5b0ba..207bf89 100644 --- a/src/modules/extras/devUtil/tests.ts +++ b/src/modules/extras/devUtil/tests.ts @@ -48,7 +48,7 @@ async function formatPerfResults(items: NamedMeasureResult[]) { } export async function perf_trench(plugin: ObsidianLiveSyncPlugin) { clearResult("trench"); - const trench = new Trench(plugin.simpleStore); + const trench = new Trench(plugin.core.simpleStore); const result = [] as NamedMeasureResult[]; result.push( await measure("trench-short-string", async () => { @@ -57,7 +57,7 @@ export async function perf_trench(plugin: ObsidianLiveSyncPlugin) { }) ); { - const testBinary = await plugin.storageAccess.readHiddenFileBinary("testdata/10kb.png"); + const testBinary = await plugin.core.storageAccess.readHiddenFileBinary("testdata/10kb.png"); const uint8Array = new Uint8Array(testBinary); result.push( await measure("trench-binary-10kb", async () => { @@ -67,7 +67,7 @@ export async function perf_trench(plugin: ObsidianLiveSyncPlugin) { ); } { - const testBinary = await plugin.storageAccess.readHiddenFileBinary("testdata/100kb.jpeg"); + const testBinary = await plugin.core.storageAccess.readHiddenFileBinary("testdata/100kb.jpeg"); const uint8Array = new Uint8Array(testBinary); result.push( await measure("trench-binary-100kb", async () => { @@ -77,7 +77,7 @@ export async function perf_trench(plugin: ObsidianLiveSyncPlugin) { ); } { - const testBinary = await plugin.storageAccess.readHiddenFileBinary("testdata/1mb.png"); + const testBinary = await plugin.core.storageAccess.readHiddenFileBinary("testdata/1mb.png"); const uint8Array = new Uint8Array(testBinary); result.push( await measure("trench-binary-1mb", async () => { diff --git a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts index 00bcf64..f28f263 100644 --- a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts +++ b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts @@ -15,6 +15,7 @@ import { isErrorOfMissingDoc } from "../../../lib/src/pouchdb/utils_couchdb.ts"; import { fireAndForget, getDocData, readContent } from "../../../lib/src/common/utils.ts"; import { isPlainText, stripPrefix } from "../../../lib/src/string_and_binary/path.ts"; import { scheduleOnceIfDuplicated } from "octagonal-wheels/concurrency/lock"; +import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts"; function isImage(path: string) { const ext = path.split(".").splice(-1)[0].toLowerCase(); @@ -46,8 +47,9 @@ function readDocument(w: LoadedEntry) { } export class DocumentHistoryModal extends Modal { plugin: ObsidianLiveSyncPlugin; + core: LiveSyncBaseCore; get services() { - return this.plugin.services; + return this.core.services; } range!: HTMLInputElement; contentView!: HTMLDivElement; @@ -66,6 +68,7 @@ export class DocumentHistoryModal extends Modal { constructor( app: App, + core: LiveSyncBaseCore, plugin: ObsidianLiveSyncPlugin, file: TFile | FilePathWithPrefix, id?: DocumentID, @@ -73,6 +76,7 @@ export class DocumentHistoryModal extends Modal { ) { super(app); this.plugin = plugin; + this.core = core; this.file = file instanceof TFile ? getPathFromTFile(file) : file; this.id = id; this.initialRev = revision; @@ -88,7 +92,7 @@ export class DocumentHistoryModal extends Modal { if (!this.id) { this.id = await this.services.path.path2id(this.file); } - const db = this.plugin.localDatabase; + const db = this.core.localDatabase; try { const w = await db.getRaw(this.id, { revs_info: true }); this.revs_info = w._revs_info?.filter((e) => e?.status == "available") ?? []; @@ -137,7 +141,7 @@ export class DocumentHistoryModal extends Modal { } async showExactRev(rev: string) { - const db = this.plugin.localDatabase; + const db = this.core.localDatabase; const w = await db.getDBEntry(this.file, { rev: rev }, false, false, true); this.currentText = ""; this.currentDeleted = false; @@ -292,7 +296,7 @@ export class DocumentHistoryModal extends Modal { return; } const d = readContent(this.currentDoc); - await this.plugin.storageAccess.writeHiddenFileAuto(pathToWrite, d); + await this.core.storageAccess.writeHiddenFileAuto(pathToWrite, d); await focusFile(pathToWrite); this.close(); }); diff --git a/src/modules/features/GlobalHistory/GlobalHistory.svelte b/src/modules/features/GlobalHistory/GlobalHistory.svelte index a30177b..f150e72 100644 --- a/src/modules/features/GlobalHistory/GlobalHistory.svelte +++ b/src/modules/features/GlobalHistory/GlobalHistory.svelte @@ -6,7 +6,9 @@ import { diff_match_patch } from "../../../deps.ts"; import { DocumentHistoryModal } from "../DocumentHistory/DocumentHistoryModal.ts"; import { isPlainText, stripAllPrefixes } from "../../../lib/src/string_and_binary/path.ts"; + import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts"; export let plugin: ObsidianLiveSyncPlugin; + export let core: LiveSyncBaseCore; let showDiffInfo = false; let showChunkCorrected = false; @@ -44,12 +46,12 @@ let history = [] as HistoryData[]; let loading = false; function getPath(entry: AnyEntry): FilePathWithPrefix { - return plugin.services.path.getPath(entry); + return core.services.path.getPath(entry); } async function fetchChanges(): Promise { try { - const db = plugin.localDatabase; + const db = core.localDatabase; let result = [] as typeof history; for await (const docA of db.findAllNormalDocs()) { if (docA.mtime < range_from_epoch) { @@ -112,11 +114,11 @@ } if (rev == docA._rev) { if (checkStorageDiff) { - const isExist = await plugin.storageAccess.isExistsIncludeHidden( + const isExist = await core.storageAccess.isExistsIncludeHidden( stripAllPrefixes(getPath(docA)) ); if (isExist) { - const data = await plugin.storageAccess.readHiddenFileBinary( + const data = await core.storageAccess.readHiddenFileBinary( stripAllPrefixes(getPath(docA)) ); const d = readAsBlob(doc); @@ -189,7 +191,7 @@ onDestroy(() => {}); function showHistory(file: string, rev: string) { - new DocumentHistoryModal(plugin.app, plugin, file as unknown as FilePathWithPrefix, undefined, rev).open(); + new DocumentHistoryModal(plugin.app, plugin.core, plugin, file as unknown as FilePathWithPrefix, undefined, rev).open(); } function openFile(file: string) { plugin.app.workspace.openLinkText(file, file); diff --git a/src/modules/features/GlobalHistory/GlobalHistoryView.ts b/src/modules/features/GlobalHistory/GlobalHistoryView.ts index 0edcfe4..63ef474 100644 --- a/src/modules/features/GlobalHistory/GlobalHistoryView.ts +++ b/src/modules/features/GlobalHistory/GlobalHistoryView.ts @@ -11,6 +11,7 @@ export class GlobalHistoryView extends SvelteItemView { target: target, props: { plugin: this.plugin, + core: this.plugin.core, }, }); } diff --git a/src/modules/features/ModuleLog.ts b/src/modules/features/ModuleLog.ts index 7d9c3e9..d04f9c3 100644 --- a/src/modules/features/ModuleLog.ts +++ b/src/modules/features/ModuleLog.ts @@ -254,8 +254,7 @@ export class ModuleLog extends AbstractObsidianModule { } // Case Sensitivity if (this.services.vault.shouldCheckCaseInsensitively()) { - const f = this.core.storageAccess - .getFiles() + const f = (await this.core.storageAccess.getFiles()) .map((e) => e.path) .filter((e) => e.toLowerCase() == thisFile.path.toLowerCase()); if (f.length > 1) { @@ -405,8 +404,8 @@ export class ModuleLog extends AbstractObsidianModule { this.logHistory = this.statusDiv.createDiv({ cls: "livesync-status-loghistory" }); eventHub.onEvent(EVENT_LAYOUT_READY, () => this.adjustStatusDivPosition()); if (this.settings?.showStatusOnStatusbar) { - this.statusBar = this.core.addStatusBarItem(); - this.statusBar.addClass("syncstatusbar"); + this.statusBar = this.services.API.addStatusBarItem(); + this.statusBar?.addClass("syncstatusbar"); } this.adjustStatusDivPosition(); return Promise.resolve(true); diff --git a/src/modules/features/ModuleObsidianDocumentHistory.ts b/src/modules/features/ModuleObsidianDocumentHistory.ts index d1182fc..1e6a8bb 100644 --- a/src/modules/features/ModuleObsidianDocumentHistory.ts +++ b/src/modules/features/ModuleObsidianDocumentHistory.ts @@ -34,7 +34,7 @@ export class ModuleObsidianDocumentHistory extends AbstractObsidianModule { } showHistory(file: TFile | FilePathWithPrefix, id?: DocumentID) { - new DocumentHistoryModal(this.app, this.plugin, file, id).open(); + new DocumentHistoryModal(this.app, this.core, this.plugin, file, id).open(); } async fileHistory() { diff --git a/src/modules/features/ModuleObsidianSettingTab.ts b/src/modules/features/ModuleObsidianSettingTab.ts index 52e8ccd..29c4862 100644 --- a/src/modules/features/ModuleObsidianSettingTab.ts +++ b/src/modules/features/ModuleObsidianSettingTab.ts @@ -2,6 +2,7 @@ import { ObsidianLiveSyncSettingTab } from "./SettingDialogue/ObsidianLiveSyncSe import { AbstractObsidianModule } from "../AbstractObsidianModule.ts"; // import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser"; import { EVENT_REQUEST_OPEN_SETTING_WIZARD, EVENT_REQUEST_OPEN_SETTINGS, eventHub } from "../../common/events.ts"; +import type { LiveSyncCore } from "@/main.ts"; export class ModuleObsidianSettingDialogue extends AbstractObsidianModule { settingTab!: ObsidianLiveSyncSettingTab; @@ -29,7 +30,7 @@ export class ModuleObsidianSettingDialogue extends AbstractObsidianModule { get appId() { return `${"appId" in this.app ? this.app.appId : ""}`; } - override onBindFunction(core: typeof this.plugin, services: typeof core.services): void { + override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); } } diff --git a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts index 23c65bc..6d5d5e9 100644 --- a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts +++ b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts @@ -86,8 +86,11 @@ export function createStub(name: string, key: string, value: string, panel: stri export class ObsidianLiveSyncSettingTab extends PluginSettingTab { plugin: ObsidianLiveSyncPlugin; + get core() { + return this.plugin.core; + } get services() { - return this.plugin.services; + return this.core.services; } selectedScreen = ""; @@ -122,9 +125,9 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { continue; } //@ts-ignore - this.plugin.settings[k] = this.editingSettings[k]; + this.core.settings[k] = this.editingSettings[k]; //@ts-ignore - this.initialSettings[k] = this.plugin.settings[k]; + this.initialSettings[k] = this.core.settings[k]; } keys.forEach((e) => this.refreshSetting(e)); } @@ -164,14 +167,14 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { continue; } //@ts-ignore - this.plugin.settings[k] = this.editingSettings[k]; + this.core.settings[k] = this.editingSettings[k]; //@ts-ignore - this.initialSettings[k] = this.plugin.settings[k]; + this.initialSettings[k] = this.core.settings[k]; hasChanged = true; } if (hasChanged) { - await this.plugin.saveSettings(); + await this.services.setting.saveSettingData(); } // if (runOnSaved) { @@ -231,7 +234,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { */ reloadAllSettings(skipUpdate: boolean = false) { const localSetting = this.reloadAllLocalSettings(); - this._editingSettings = { ...this.plugin.settings, ...localSetting }; + this._editingSettings = { ...this.core.settings, ...localSetting }; this._editingSettings = { ...this.editingSettings, ...this.computeAllLocalSettings() }; this.initialSettings = { ...this.editingSettings }; if (!skipUpdate) this.requestUpdate(); @@ -242,7 +245,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { */ refreshSetting(key: AllSettingItemKey) { const localSetting = this.reloadAllLocalSettings(); - if (key in this.plugin.settings) { + if (key in this.core.settings) { if (key in localSetting) { //@ts-ignore this.initialSettings[key] = localSetting[key]; @@ -250,7 +253,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { this.editingSettings[key] = localSetting[key]; } else { //@ts-ignore - this.initialSettings[key] = this.plugin.settings[key]; + this.initialSettings[key] = this.core.settings[key]; //@ts-ignore this.editingSettings[key] = this.initialSettings[key]; } @@ -319,7 +322,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { closeSetting() { // @ts-ignore - this.plugin.app.setting.close(); + this.core.app.setting.close(); } handleElement(element: HTMLElement, func: OnUpdateFunc) { @@ -381,7 +384,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { requestReload() { if (this.isShown) { - const newConf = this.plugin.settings; + const newConf = this.core.settings; const keys = Object.keys(newConf) as (keyof ObsidianLiveSyncSettings)[]; let hasLoaded = false; for (const k of keys) { @@ -389,7 +392,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { // Something has changed if (this.isDirty(k as AllSettingItemKey)) { // And modified. - this.plugin.confirm.askInPopup( + this.core.confirm.askInPopup( `config-reloaded-${k}`, $msg("obsidianLiveSyncSettingTab.msgSettingModified", { setting: getConfName(k as AllSettingItemKey), @@ -457,7 +460,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { this.editingSettings.syncOnStart = false; this.editingSettings.syncOnFileOpen = false; this.editingSettings.syncAfterMerge = false; - this.plugin.replicator.closeReplication(); + this.core.replicator.closeReplication(); await this.saveAllDirtySettings(); this.containerEl.addClass("isWizard"); this.inWizard = true; @@ -514,8 +517,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { if (this.isConfiguredAs("syncOnStart", true)) return true; if (this.isConfiguredAs("syncAfterMerge", true)) return true; if (this.isConfiguredAs("syncOnFileOpen", true)) return true; - if (this.plugin?.replicator?.syncStatus == "CONNECTED") return true; - if (this.plugin?.replicator?.syncStatus == "PAUSED") return true; + if (this.core?.replicator?.syncStatus == "CONNECTED") return true; + if (this.core?.replicator?.syncStatus == "PAUSED") return true; return false; } @@ -605,7 +608,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { await this.saveAllDirtySettings(); this.closeSetting(); await delay(2000); - await this.plugin.rebuilder.$performRebuildDB(method); + await this.core.rebuilder.$performRebuildDB(method); }; async confirmRebuild() { if (!(await this.isPassphraseValid())) { @@ -633,7 +636,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { if (result == OPTION_FETCH) { if (!(await this.checkWorkingPassphrase())) { if ( - (await this.plugin.confirm.askYesNoDialog($msg("obsidianLiveSyncSettingTab.msgAreYouSureProceed"), { + (await this.core.confirm.askYesNoDialog($msg("obsidianLiveSyncSettingTab.msgAreYouSureProceed"), { defaultOption: "No", })) != "yes" ) @@ -646,16 +649,16 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { await this.saveAllDirtySettings(); await this.applyAllSettings(); if (result == OPTION_FETCH) { - await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG3_HR, ""); + await this.core.storageAccess.writeFileAuto(FLAGMD_REDFLAG3_HR, ""); this.services.appLifecycle.scheduleRestart(); this.closeSetting(); // await rebuildDB("localOnly"); } else if (result == OPTION_REBUILD_BOTH) { - await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG2_HR, ""); + await this.core.storageAccess.writeFileAuto(FLAGMD_REDFLAG2_HR, ""); this.services.appLifecycle.scheduleRestart(); this.closeSetting(); } else if (result == OPTION_ONLY_SETTING) { - await this.plugin.saveSettings(); + await this.services.setting.saveSettingData(); } } @@ -868,7 +871,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { } getMinioJournalSyncClient() { - return new JournalSyncMinio(this.plugin.settings, this.plugin.simpleStore, this.plugin); + return new JournalSyncMinio(this.core.settings, this.core.simpleStore, this.core); } async resetRemoteBucket() { const minioJournal = this.getMinioJournalSyncClient(); diff --git a/src/modules/features/SettingDialogue/PaneHatch.ts b/src/modules/features/SettingDialogue/PaneHatch.ts index 2898afa..92b9326 100644 --- a/src/modules/features/SettingDialogue/PaneHatch.ts +++ b/src/modules/features/SettingDialogue/PaneHatch.ts @@ -165,7 +165,7 @@ export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement, } const obsidianInfo = { navigator: navigator.userAgent, - fileSystem: this.plugin.services.vault.isStorageInsensitive() ? "insensitive" : "sensitive", + fileSystem: this.core.services.vault.isStorageInsensitive() ? "insensitive" : "sensitive", }; const msgConfig = `# ---- Obsidian info ---- ${stringifyYaml(obsidianInfo)} @@ -221,7 +221,7 @@ ${stringifyYaml({ void addPanel(paneEl, "Recovery and Repair").then((paneEl) => { const addResult = async (path: string, file: FilePathWithPrefix | false, fileOnDB: LoadedEntry | false) => { - const storageFileStat = file ? await this.plugin.storageAccess.statHidden(file) : null; + const storageFileStat = file ? await this.core.storageAccess.statHidden(file) : null; resultArea.appendChild( this.createEl(resultArea, "div", {}, (el) => { el.appendChild(this.createEl(el, "h6", { text: path })); @@ -256,7 +256,7 @@ ${stringifyYaml({ this.createEl(el, "button", { text: "Storage -> Database" }, (buttonEl) => { buttonEl.onClickEvent(async () => { if (file.startsWith(".")) { - const addOn = this.plugin.getAddOn(HiddenFileSync.name); + const addOn = this.core.getAddOn(HiddenFileSync.name); if (addOn) { const file = (await addOn.scanInternalFiles()).find((e) => e.path == path); if (!file) { @@ -275,7 +275,7 @@ ${stringifyYaml({ } } } else { - if (!(await this.plugin.fileHandler.storeFileToDB(file as FilePath, true))) { + if (!(await this.core.fileHandler.storeFileToDB(file as FilePath, true))) { Logger( `Failed to store the file to the database: ${file}`, LOG_LEVEL_NOTICE @@ -293,7 +293,7 @@ ${stringifyYaml({ this.createEl(el, "button", { text: "Database -> Storage" }, (buttonEl) => { buttonEl.onClickEvent(async () => { if (fileOnDB.path.startsWith(ICHeader)) { - const addOn = this.plugin.getAddOn(HiddenFileSync.name); + const addOn = this.core.getAddOn(HiddenFileSync.name); if (addOn) { if ( !(await addOn.extractInternalFileFromDatabase(path as FilePath, true)) @@ -307,7 +307,7 @@ ${stringifyYaml({ } } else { if ( - !(await this.plugin.fileHandler.dbToStorage( + !(await this.core.fileHandler.dbToStorage( fileOnDB as MetaEntry, null, true @@ -332,7 +332,7 @@ ${stringifyYaml({ const checkBetweenStorageAndDatabase = async (file: FilePathWithPrefix, fileOnDB: LoadedEntry) => { const dataContent = readAsBlob(fileOnDB); - const content = createBlob(await this.plugin.storageAccess.readHiddenFileBinary(file)); + const content = createBlob(await this.core.storageAccess.readHiddenFileBinary(file)); if (await isDocContentSame(content, dataContent)) { Logger(`Compare: SAME: ${file}`); } else { @@ -348,7 +348,7 @@ ${stringifyYaml({ .setButtonText("Recreate all") .setCta() .onClick(async () => { - await this.plugin.fileHandler.createAllChunks(true); + await this.core.fileHandler.createAllChunks(true); }) ); new Setting(paneEl) @@ -377,21 +377,21 @@ ${stringifyYaml({ .setCta() .onClick(async () => { Logger("Start verifying all files", LOG_LEVEL_NOTICE, "verify"); - const ignorePatterns = getFileRegExp(this.plugin.settings, "syncInternalFilesIgnorePatterns"); - const targetPatterns = getFileRegExp(this.plugin.settings, "syncInternalFilesTargetPatterns"); - this.plugin.localDatabase.clearCaches(); + const ignorePatterns = getFileRegExp(this.core.settings, "syncInternalFilesIgnorePatterns"); + const targetPatterns = getFileRegExp(this.core.settings, "syncInternalFilesTargetPatterns"); + this.core.localDatabase.clearCaches(); Logger("Start verifying all files", LOG_LEVEL_NOTICE, "verify"); - const files = this.plugin.settings.syncInternalFiles - ? await this.plugin.storageAccess.getFilesIncludeHidden("/", targetPatterns, ignorePatterns) - : await this.plugin.storageAccess.getFileNames(); + const files = this.core.settings.syncInternalFiles + ? await this.core.storageAccess.getFilesIncludeHidden("/", targetPatterns, ignorePatterns) + : await this.core.storageAccess.getFileNames(); const documents = [] as FilePath[]; - const adn = this.plugin.localDatabase.findAllDocs(); + const adn = this.core.localDatabase.findAllDocs(); for await (const i of adn) { const path = this.services.path.getPath(i); if (path.startsWith(ICXHeader)) continue; if (path.startsWith(PSCHeader)) continue; - if (!this.plugin.settings.syncInternalFiles && path.startsWith(ICHeader)) continue; + if (!this.core.settings.syncInternalFiles && path.startsWith(ICHeader)) continue; documents.push(stripAllPrefixes(path)); } const allPaths = [...new Set([...documents, ...files])]; @@ -411,8 +411,8 @@ ${stringifyYaml({ if (shouldBeIgnored(path)) { return incProc(); } - const stat = (await this.plugin.storageAccess.isExistsIncludeHidden(path)) - ? await this.plugin.storageAccess.statHidden(path) + const stat = (await this.core.storageAccess.isExistsIncludeHidden(path)) + ? await this.core.storageAccess.statHidden(path) : false; const fileOnStorage = stat != null ? stat : false; if (!(await this.services.vault.isTargetFile(path))) return incProc(); @@ -422,7 +422,7 @@ ${stringifyYaml({ try { const isHiddenFile = path.startsWith("."); const dbPath = isHiddenFile ? addPrefix(path, ICHeader) : path; - const fileOnDB = await this.plugin.localDatabase.getDBEntry(dbPath); + const fileOnDB = await this.core.localDatabase.getDBEntry(dbPath); if (fileOnDB && this.services.vault.isFileSizeTooLarge(fileOnDB.size)) return incProc(); @@ -466,10 +466,10 @@ ${stringifyYaml({ .setDisabled(false) .setWarning() .onClick(async () => { - for await (const docName of this.plugin.localDatabase.findAllDocNames()) { + for await (const docName of this.core.localDatabase.findAllDocNames()) { if (!docName.startsWith("f:")) { const idEncoded = await this.services.path.path2id(docName as FilePathWithPrefix); - const doc = await this.plugin.localDatabase.getRaw(docName as DocumentID); + const doc = await this.core.localDatabase.getRaw(docName as DocumentID); if (!doc) continue; if (doc.type != "newnote" && doc.type != "plain") { continue; @@ -482,7 +482,7 @@ ${stringifyYaml({ // @ts-ignore delete newDoc._rev; try { - const obfuscatedDoc = await this.plugin.localDatabase.getRaw(idEncoded, { + const obfuscatedDoc = await this.core.localDatabase.getRaw(idEncoded, { revs_info: true, }); // Unfortunately we have to delete one of them. @@ -499,14 +499,14 @@ ${stringifyYaml({ -32 ); } - const ret = await this.plugin.localDatabase.putRaw(newDoc, { force: true }); + const ret = await this.core.localDatabase.putRaw(newDoc, { force: true }); if (ret.ok) { Logger( `${docName} has been converted as conflicted document`, LOG_LEVEL_NOTICE ); doc._deleted = true; - if ((await this.plugin.localDatabase.putRaw(doc)).ok) { + if ((await this.core.localDatabase.putRaw(doc)).ok) { Logger(`Old ${docName} has been deleted`, LOG_LEVEL_NOTICE); } await this.services.conflict.queueCheckForIfOpen(docName as FilePathWithPrefix); @@ -517,10 +517,10 @@ ${stringifyYaml({ } catch (ex: any) { if (ex?.status == 404) { // We can perform this safely - if ((await this.plugin.localDatabase.putRaw(newDoc)).ok) { + if ((await this.core.localDatabase.putRaw(newDoc)).ok) { Logger(`${docName} has been converted`, LOG_LEVEL_NOTICE); doc._deleted = true; - if ((await this.plugin.localDatabase.putRaw(doc)).ok) { + if ((await this.core.localDatabase.putRaw(doc)).ok) { Logger(`Old ${docName} has been deleted`, LOG_LEVEL_NOTICE); } } @@ -555,7 +555,7 @@ ${stringifyYaml({ .setWarning() .onClick(async () => { Logger(`Deleting customization sync data`, LOG_LEVEL_NOTICE); - const entriesToDelete = await this.plugin.localDatabase.allDocsRaw({ + const entriesToDelete = await this.core.localDatabase.allDocsRaw({ startkey: "ix:", endkey: "ix:\u{10ffff}", include_docs: true, @@ -564,7 +564,7 @@ ${stringifyYaml({ ...e.doc, _deleted: true, })); - const r = await this.plugin.localDatabase.bulkDocsRaw(newData as any[]); + const r = await this.core.localDatabase.bulkDocsRaw(newData as any[]); // Do not care about the result. Logger( `${r.length} items have been removed, to confirm how many items are left, please perform it again.`, diff --git a/src/modules/features/SettingDialogue/PaneMaintenance.ts b/src/modules/features/SettingDialogue/PaneMaintenance.ts index 4a9838a..71ac0d7 100644 --- a/src/modules/features/SettingDialogue/PaneMaintenance.ts +++ b/src/modules/features/SettingDialogue/PaneMaintenance.ts @@ -11,8 +11,8 @@ export function paneMaintenance( paneEl: HTMLElement, { addPanel }: PageFunctions ): void { - const isRemoteLockedAndDeviceNotAccepted = () => this.plugin?.replicator?.remoteLockedAndDeviceNotAccepted; - const isRemoteLocked = () => this.plugin?.replicator?.remoteLocked; + const isRemoteLockedAndDeviceNotAccepted = () => this.core?.replicator?.remoteLockedAndDeviceNotAccepted; + const isRemoteLocked = () => this.core?.replicator?.remoteLocked; // if (this.plugin?.replicator?.remoteLockedAndDeviceNotAccepted) { this.createEl( paneEl, @@ -92,7 +92,7 @@ export function paneMaintenance( .setDisabled(false) .setWarning() .onClick(async () => { - await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG, ""); + await this.core.storageAccess.writeFileAuto(FLAGMD_REDFLAG, ""); this.services.appLifecycle.performRestart(); }) ); @@ -108,7 +108,7 @@ export function paneMaintenance( .setCta() .setDisabled(false) .onClick(async () => { - await this.plugin.storageAccess.writeFileAuto(FlagFilesHumanReadable.FETCH_ALL, ""); + await this.core.storageAccess.writeFileAuto(FlagFilesHumanReadable.FETCH_ALL, ""); this.services.appLifecycle.performRestart(); }) ); @@ -121,7 +121,7 @@ export function paneMaintenance( .setCta() .setDisabled(false) .onClick(async () => { - await this.plugin.storageAccess.writeFileAuto(FlagFilesHumanReadable.REBUILD_ALL, ""); + await this.core.storageAccess.writeFileAuto(FlagFilesHumanReadable.REBUILD_ALL, ""); this.services.appLifecycle.performRestart(); }) ); @@ -137,8 +137,8 @@ export function paneMaintenance( .setWarning() .setDisabled(false) .onClick(async () => { - if (this.plugin.replicator instanceof LiveSyncCouchDBReplicator) { - await this.plugin.replicator.sendChunks(this.plugin.settings, undefined, true, 0); + if (this.core.replicator instanceof LiveSyncCouchDBReplicator) { + await this.core.replicator.sendChunks(this.core.settings, undefined, true, 0); } }) ) @@ -299,7 +299,7 @@ export function paneMaintenance( .setButtonText("Perform") .setDisabled(false) .onClick(async () => { - const replicator = this.plugin.replicator as LiveSyncCouchDBReplicator; + const replicator = this.core.replicator as LiveSyncCouchDBReplicator; Logger(`Cleanup has been began`, LOG_LEVEL_NOTICE, "compaction"); if (await replicator.compactRemote(this.editingSettings)) { Logger(`Cleanup has been completed!`, LOG_LEVEL_NOTICE, "compaction"); diff --git a/src/modules/features/SettingDialogue/PanePatches.ts b/src/modules/features/SettingDialogue/PanePatches.ts index 906127e..0631bbd 100644 --- a/src/modules/features/SettingDialogue/PanePatches.ts +++ b/src/modules/features/SettingDialogue/PanePatches.ts @@ -31,7 +31,7 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen void addPanel(paneEl, "Compatibility (Database structure)").then((paneEl) => { const migrateAllToIndexedDB = async () => { - const dbToName = this.plugin.localDatabase.dbname + SuffixDatabaseName + ExtraSuffixIndexedDB; + const dbToName = this.core.localDatabase.dbname + SuffixDatabaseName + ExtraSuffixIndexedDB; const options = { adapter: "indexeddb", //@ts-ignore :missing def @@ -42,18 +42,19 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen const openTo = () => { return new PouchDB(dbToName, options); }; - if (await migrateDatabases("to IndexedDB", this.plugin.localDatabase.localDatabase, openTo)) { + if (await migrateDatabases("to IndexedDB", this.core.localDatabase.localDatabase, openTo)) { Logger( "Migration to IndexedDB completed. Obsidian will be restarted with new configuration immediately.", LOG_LEVEL_NOTICE ); - this.plugin.settings.useIndexedDBAdapter = true; - await this.services.setting.saveSettingData(); + // this.plugin.settings.useIndexedDBAdapter = true; + // await this.services.setting.saveSettingData(); + await this.core.services.setting.applyPartial({ useIndexedDBAdapter: true }, true); this.services.appLifecycle.performRestart(); } }; const migrateAllToIDB = async () => { - const dbToName = this.plugin.localDatabase.dbname + SuffixDatabaseName; + const dbToName = this.core.localDatabase.dbname + SuffixDatabaseName; const options = { adapter: "idb", auto_compaction: false, @@ -62,13 +63,14 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen const openTo = () => { return new PouchDB(dbToName, options); }; - if (await migrateDatabases("to IDB", this.plugin.localDatabase.localDatabase, openTo)) { + if (await migrateDatabases("to IDB", this.core.localDatabase.localDatabase, openTo)) { Logger( "Migration to IDB completed. Obsidian will be restarted with new configuration immediately.", LOG_LEVEL_NOTICE ); - this.plugin.settings.useIndexedDBAdapter = false; - await this.services.setting.saveSettingData(); + await this.core.services.setting.applyPartial({ useIndexedDBAdapter: false }, true); + // this.core.settings.useIndexedDBAdapter = false; + // await this.services.setting.saveSettingData(); this.services.appLifecycle.performRestart(); } }; @@ -151,7 +153,7 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen } as Record, }); this.addOnSaved("hashAlg", async () => { - await this.plugin.localDatabase._prepareHashFunctions(); + await this.core.localDatabase._prepareHashFunctions(); }); }); void addPanel(paneEl, "Edge case addressing (Behaviour)").then((paneEl) => { @@ -215,7 +217,7 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen this.addOnSaved("maxMTimeForReflectEvents", async (key) => { const buttons = ["Restart Now", "Later"] as const; - const reboot = await this.plugin.confirm.askSelectStringDialogue( + const reboot = await this.core.confirm.askSelectStringDialogue( "Restarting Obsidian is strongly recommended. Until restart, some changes may not take effect, and display may be inconsistent. Are you sure to restart now?", buttons, { diff --git a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts index cd3473e..216fa47 100644 --- a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts +++ b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts @@ -68,7 +68,7 @@ export function paneRemoteConfig( .addButton((button) => button .onClick(async () => { - const setupManager = this.plugin.getModule(SetupManager); + const setupManager = this.core.getModule(SetupManager); const originalSettings = getSettingsFromEditingSettings(this.editingSettings); await setupManager.onlyE2EEConfiguration(UserMode.Update, originalSettings); updateE2EESummary(); @@ -79,7 +79,7 @@ export function paneRemoteConfig( .addButton((button) => button .onClick(async () => { - const setupManager = this.plugin.getModule(SetupManager); + const setupManager = this.core.getModule(SetupManager); const originalSettings = getSettingsFromEditingSettings(this.editingSettings); await setupManager.onConfigureManually(originalSettings, UserMode.Update); updateE2EESummary(); @@ -101,7 +101,7 @@ export function paneRemoteConfig( .setButtonText("Change Remote and Setup") .setCta() .onClick(async () => { - const setupManager = this.plugin.getModule(SetupManager); + const setupManager = this.core.getModule(SetupManager); const originalSettings = getSettingsFromEditingSettings(this.editingSettings); await setupManager.onSelectServer(originalSettings, UserMode.Update); }) @@ -127,7 +127,7 @@ export function paneRemoteConfig( .setButtonText("Configure") .setCta() .onClick(async () => { - const setupManager = this.plugin.getModule(SetupManager); + const setupManager = this.core.getModule(SetupManager); const originalSettings = getSettingsFromEditingSettings(this.editingSettings); await setupManager.onCouchDBManualSetup( UserMode.Update, @@ -162,7 +162,7 @@ export function paneRemoteConfig( .setButtonText("Configure") .setCta() .onClick(async () => { - const setupManager = this.plugin.getModule(SetupManager); + const setupManager = this.core.getModule(SetupManager); const originalSettings = getSettingsFromEditingSettings(this.editingSettings); await setupManager.onBucketManualSetup( UserMode.Update, @@ -202,7 +202,7 @@ export function paneRemoteConfig( .setButtonText("Configure") .setCta() .onClick(async () => { - const setupManager = this.plugin.getModule(SetupManager); + const setupManager = this.core.getModule(SetupManager); const originalSettings = getSettingsFromEditingSettings(this.editingSettings); await setupManager.onP2PManualSetup( UserMode.Update, diff --git a/src/modules/features/SettingDialogue/PaneSetup.ts b/src/modules/features/SettingDialogue/PaneSetup.ts index 0b131a1..de996bc 100644 --- a/src/modules/features/SettingDialogue/PaneSetup.ts +++ b/src/modules/features/SettingDialogue/PaneSetup.ts @@ -35,7 +35,7 @@ export function paneSetup( .setDesc($msg("Rerun the onboarding wizard to set up Self-hosted LiveSync again.")) .addButton((text) => { text.setButtonText($msg("Rerun Wizard")).onClick(async () => { - const setupManager = this.plugin.getModule(SetupManager); + const setupManager = this.core.getModule(SetupManager); await setupManager.onOnboard(UserMode.ExistingUser); // await this.plugin.moduleSetupObsidian.onBoardingWizard(true); }); @@ -86,14 +86,14 @@ export function paneSetup( text.setButtonText($msg("obsidianLiveSyncSettingTab.btnDiscard")) .onClick(async () => { if ( - (await this.plugin.confirm.askYesNoDialog( + (await this.core.confirm.askYesNoDialog( $msg("obsidianLiveSyncSettingTab.msgDiscardConfirmation"), { defaultOption: "No" } )) == "yes" ) { this.editingSettings = { ...this.editingSettings, ...DEFAULT_SETTINGS }; await this.saveAllDirtySettings(); - this.plugin.settings = { ...DEFAULT_SETTINGS }; + this.core.settings = { ...DEFAULT_SETTINGS }; await this.services.setting.saveSettingData(); await this.services.database.resetDatabase(); // await this.plugin.initializeDatabase(); diff --git a/src/modules/features/SettingDialogue/PaneSyncSettings.ts b/src/modules/features/SettingDialogue/PaneSyncSettings.ts index aba3296..394c7c1 100644 --- a/src/modules/features/SettingDialogue/PaneSyncSettings.ts +++ b/src/modules/features/SettingDialogue/PaneSyncSettings.ts @@ -109,7 +109,7 @@ export function paneSyncSettings( await this.rebuildDB("localOnly"); // this.resetEditingSettings(); if ( - (await this.plugin.confirm.askYesNoDialog( + (await this.core.confirm.askYesNoDialog( $msg("obsidianLiveSyncSettingTab.msgGenerateSetupURI"), { defaultOption: "Yes", diff --git a/src/modules/main/ModuleLiveSyncMain.ts b/src/modules/main/ModuleLiveSyncMain.ts index 3618c61..0dec4ff 100644 --- a/src/modules/main/ModuleLiveSyncMain.ts +++ b/src/modules/main/ModuleLiveSyncMain.ts @@ -80,7 +80,7 @@ export class ModuleLiveSyncMain extends AbstractModule { initialiseWorkerModule(); await this.services.appLifecycle.onWireUpEvents(); // debugger; - eventHub.emitEvent(EVENT_PLUGIN_LOADED, this.core); + eventHub.emitEvent(EVENT_PLUGIN_LOADED); this._log($msg("moduleLiveSyncMain.logLoadingPlugin")); if (!(await this.services.appLifecycle.onInitialise())) { this._log($msg("moduleLiveSyncMain.logPluginInitCancelled"), LOG_LEVEL_NOTICE); diff --git a/src/modules/services/ObsidianAPIService.ts b/src/modules/services/ObsidianAPIService.ts index 6ec05c0..f543c30 100644 --- a/src/modules/services/ObsidianAPIService.ts +++ b/src/modules/services/ObsidianAPIService.ts @@ -171,4 +171,18 @@ export class ObsidianAPIService extends InjectableAPIService void, timeout: number): number { + const timerId = globalThis.setInterval(handler, timeout) as unknown as number; + this.context.plugin.registerInterval(timerId); + return timerId; + } + + override getSystemConfigDir() { + return this.app.vault.configDir; + } } diff --git a/src/serviceModules/FileSystemAdapters/ObsidianFileSystemAdapter.ts b/src/serviceModules/FileSystemAdapters/ObsidianFileSystemAdapter.ts index d0c261e..944a007 100644 --- a/src/serviceModules/FileSystemAdapters/ObsidianFileSystemAdapter.ts +++ b/src/serviceModules/FileSystemAdapters/ObsidianFileSystemAdapter.ts @@ -42,16 +42,16 @@ export class ObsidianFileSystemAdapter implements IFileSystemAdapter { + return Promise.resolve(this.app.vault.getAbstractFileByPath(path)); } - getAbstractFileByPathInsensitive(path: FilePath | string): TAbstractFile | null { - return this.app.vault.getAbstractFileByPathInsensitive(path); + getAbstractFileByPathInsensitive(path: FilePath | string): Promise { + return Promise.resolve(this.app.vault.getAbstractFileByPathInsensitive(path)); } - getFiles(): TFile[] { - return this.app.vault.getFiles(); + getFiles(): Promise { + return Promise.resolve(this.app.vault.getFiles()); } statFromNative(file: TFile): Promise { diff --git a/test/harness/harness.ts b/test/harness/harness.ts index cbb4e74..ef2d20e 100644 --- a/test/harness/harness.ts +++ b/test/harness/harness.ts @@ -109,7 +109,7 @@ export async function generateHarness( } export async function waitForReady(harness: LiveSyncHarness): Promise { for (let i = 0; i < 10; i++) { - if (harness.plugin.services.appLifecycle.isReady()) { + if (harness.plugin.core.services.appLifecycle.isReady()) { console.log("App Lifecycle is ready"); return; } @@ -122,11 +122,11 @@ export async function waitForIdle(harness: LiveSyncHarness): Promise { for (let i = 0; i < 20; i++) { await delay(25); const processing = - harness.plugin.services.replication.databaseQueueCount.value + - harness.plugin.services.fileProcessing.totalQueued.value + - harness.plugin.services.fileProcessing.batched.value + - harness.plugin.services.fileProcessing.processing.value + - harness.plugin.services.replication.storageApplyingCount.value; + harness.plugin.core.services.replication.databaseQueueCount.value + + harness.plugin.core.services.fileProcessing.totalQueued.value + + harness.plugin.core.services.fileProcessing.batched.value + + harness.plugin.core.services.fileProcessing.processing.value + + harness.plugin.core.services.replication.storageApplyingCount.value; if (processing === 0) { if (i > 0) { @@ -139,7 +139,7 @@ export async function waitForIdle(harness: LiveSyncHarness): Promise { export async function waitForClosed(harness: LiveSyncHarness): Promise { await delay(100); for (let i = 0; i < 10; i++) { - if (harness.plugin.services.control.hasUnloaded()) { + if (harness.plugin.core.services.control.hasUnloaded()) { console.log("App has unloaded"); return; } diff --git a/test/suite/db_common.ts b/test/suite/db_common.ts index e9d3831..f81f084 100644 --- a/test/suite/db_common.ts +++ b/test/suite/db_common.ts @@ -40,12 +40,12 @@ export async function storeFile( expect(readContent).toBe(content); } } - await harness.plugin.services.fileProcessing.commitPendingFileEvents(); + await harness.plugin.core.services.fileProcessing.commitPendingFileEvents(); await waitForIdle(harness); return file; } export async function readFromLocalDB(harness: LiveSyncHarness, path: string) { - const entry = await harness.plugin.localDatabase.getDBEntry(path as FilePath); + const entry = await harness.plugin.core.localDatabase.getDBEntry(path as FilePath); expect(entry).not.toBe(false); return entry; } @@ -95,11 +95,11 @@ export async function testFileWrite( ) { const file = await storeFile(harness, path, content, false, fileOptions); expect(file).toBeInstanceOf(TFile); - await harness.plugin.services.fileProcessing.commitPendingFileEvents(); + await harness.plugin.core.services.fileProcessing.commitPendingFileEvents(); await waitForIdle(harness); const vaultFile = await readFromVault(harness, path, content instanceof Blob, fileOptions); expect(await isDocContentSame(vaultFile, content)).toBe(true); - await harness.plugin.services.fileProcessing.commitPendingFileEvents(); + await harness.plugin.core.services.fileProcessing.commitPendingFileEvents(); await waitForIdle(harness); if (skipCheckToBeWritten) { return Promise.resolve(); diff --git a/test/suite/onlylocaldb.test.ts b/test/suite/onlylocaldb.test.ts index 3734ab8..acfbb65 100644 --- a/test/suite/onlylocaldb.test.ts +++ b/test/suite/onlylocaldb.test.ts @@ -28,12 +28,12 @@ describe.skip("Plugin Integration Test (Local Database)", async () => { }); it("should have services initialized", async () => { - expect(harness.plugin.services).toBeDefined(); + expect(harness.plugin.core.services).toBeDefined(); return await Promise.resolve(); }); it("should have local database initialized", async () => { - expect(harness.plugin.localDatabase).toBeDefined(); - expect(harness.plugin.localDatabase.isReady).toBe(true); + expect(harness.plugin.core.localDatabase).toBeDefined(); + expect(harness.plugin.core.localDatabase.isReady).toBe(true); return await Promise.resolve(); }); @@ -54,11 +54,11 @@ describe.skip("Plugin Integration Test (Local Database)", async () => { const readContent = await harness.app.vault.read(file); expect(readContent).toBe(content); } - await harness.plugin.services.fileProcessing.commitPendingFileEvents(); + await harness.plugin.core.services.fileProcessing.commitPendingFileEvents(); await waitForIdle(harness); // await delay(100); // Wait a bit for the local database to process - const entry = await harness.plugin.localDatabase.getDBEntry(path as FilePath); + const entry = await harness.plugin.core.localDatabase.getDBEntry(path as FilePath); expect(entry).not.toBe(false); if (entry) { expect(readContent(entry)).toBe(content); @@ -80,10 +80,10 @@ describe.skip("Plugin Integration Test (Local Database)", async () => { const readContent = await harness.app.vault.read(file); expect(readContent).toBe(content); } - await harness.plugin.services.fileProcessing.commitPendingFileEvents(); + await harness.plugin.core.services.fileProcessing.commitPendingFileEvents(); await waitForIdle(harness); - const entry = await harness.plugin.localDatabase.getDBEntry(path as FilePath); + const entry = await harness.plugin.core.localDatabase.getDBEntry(path as FilePath); expect(entry).not.toBe(false); if (entry) { expect(readContent(entry)).toBe(content); @@ -108,9 +108,9 @@ describe.skip("Plugin Integration Test (Local Database)", async () => { expect(await isDocContentSame(readContent, content)).toBe(true); } - await harness.plugin.services.fileProcessing.commitPendingFileEvents(); + await harness.plugin.core.services.fileProcessing.commitPendingFileEvents(); await waitForIdle(harness); - const entry = await harness.plugin.localDatabase.getDBEntry(path as FilePath); + const entry = await harness.plugin.core.localDatabase.getDBEntry(path as FilePath); expect(entry).not.toBe(false); if (entry) { const entryContent = await readContent(entry); diff --git a/test/suite/sync.senario.basic.ts b/test/suite/sync.senario.basic.ts index 84b9935..3eafe3c 100644 --- a/test/suite/sync.senario.basic.ts +++ b/test/suite/sync.senario.basic.ts @@ -68,7 +68,7 @@ export function syncBasicCase(label: string, { setting, fileOptions }: TestOptio await waitForIdle(harnessInit); }); afterAll(async () => { - await harnessInit.plugin.services.replicator.getActiveReplicator()?.closeReplication(); + await harnessInit.plugin.core.services.replicator.getActiveReplicator()?.closeReplication(); await harnessInit.dispose(); await delay(1000); }); @@ -81,7 +81,7 @@ export function syncBasicCase(label: string, { setting, fileOptions }: TestOptio it("should be prepared for replication", async () => { await waitForReady(harnessInit); if (setting.remoteType !== RemoteTypes.REMOTE_P2P) { - const status = await harnessInit.plugin.services.replicator + const status = await harnessInit.plugin.core.services.replicator .getActiveReplicator() ?.getRemoteStatus(sync_test_setting_init); console.log("Connected devices after reset:", status); @@ -120,12 +120,12 @@ export function syncBasicCase(label: string, { setting, fileOptions }: TestOptio }); it("should have services initialized", () => { - expect(harnessUpload.plugin.services).toBeDefined(); + expect(harnessUpload.plugin.core.services).toBeDefined(); }); it("should have local database initialized", () => { - expect(harnessUpload.plugin.localDatabase).toBeDefined(); - expect(harnessUpload.plugin.localDatabase.isReady).toBe(true); + expect(harnessUpload.plugin.core.localDatabase).toBeDefined(); + expect(harnessUpload.plugin.core.localDatabase.isReady).toBe(true); }); it("should prepare remote database", async () => { @@ -138,7 +138,7 @@ export function syncBasicCase(label: string, { setting, fileOptions }: TestOptio const path = nameFile("store", "md", 0); await testFileWrite(harnessUpload, path, content, false, fileOptions); // Perform replication - // await harness.plugin.services.replication.replicate(true); + // await harness.plugin.core.services.replication.replicate(true); }); it("should different content of several files have been created correctly", async () => { await testFileWrite(harnessUpload, nameFile("test-diff-1", "md", 0), "Content A", false, fileOptions); @@ -149,7 +149,7 @@ export function syncBasicCase(label: string, { setting, fileOptions }: TestOptio test.each(FILE_SIZE_MD)("should large file of size %i bytes has been created", async (size) => { const content = Array.from(generateFile(size)).join(""); const path = nameFile("large", "md", size); - const isTooLarge = harnessUpload.plugin.services.vault.isFileSizeTooLarge(size); + const isTooLarge = harnessUpload.plugin.core.services.vault.isFileSizeTooLarge(size); if (isTooLarge) { console.log(`Skipping file of size ${size} bytes as it is too large to sync.`); expect(true).toBe(true); @@ -162,7 +162,7 @@ export function syncBasicCase(label: string, { setting, fileOptions }: TestOptio const content = new Blob([...generateBinaryFile(size)], { type: "application/octet-stream" }); const path = nameFile("binary", "bin", size); await testFileWrite(harnessUpload, path, content, true, fileOptions); - const isTooLarge = harnessUpload.plugin.services.vault.isFileSizeTooLarge(size); + const isTooLarge = harnessUpload.plugin.core.services.vault.isFileSizeTooLarge(size); if (isTooLarge) { console.log(`Skipping file of size ${size} bytes as it is too large to sync.`); expect(true).toBe(true); @@ -210,12 +210,12 @@ export function syncBasicCase(label: string, { setting, fileOptions }: TestOptio }); it("should have services initialized", () => { - expect(harnessDownload.plugin.services).toBeDefined(); + expect(harnessDownload.plugin.core.services).toBeDefined(); }); it("should have local database initialized", () => { - expect(harnessDownload.plugin.localDatabase).toBeDefined(); - expect(harnessDownload.plugin.localDatabase.isReady).toBe(true); + expect(harnessDownload.plugin.core.localDatabase).toBeDefined(); + expect(harnessDownload.plugin.core.localDatabase.isReady).toBe(true); }); it("should a file has been synchronised", async () => { @@ -232,9 +232,9 @@ export function syncBasicCase(label: string, { setting, fileOptions }: TestOptio test.each(FILE_SIZE_MD)("should the file %i bytes had been synchronised", async (size) => { const content = Array.from(generateFile(size)).join(""); const path = nameFile("large", "md", size); - const isTooLarge = harnessDownload.plugin.services.vault.isFileSizeTooLarge(size); + const isTooLarge = harnessDownload.plugin.core.services.vault.isFileSizeTooLarge(size); if (isTooLarge) { - const entry = await harnessDownload.plugin.localDatabase.getDBEntry(path as FilePath); + const entry = await harnessDownload.plugin.core.localDatabase.getDBEntry(path as FilePath); console.log(`Skipping file of size ${size} bytes as it is too large to sync.`); expect(entry).toBe(false); } else { @@ -245,9 +245,9 @@ export function syncBasicCase(label: string, { setting, fileOptions }: TestOptio test.each(FILE_SIZE_BINS)("should binary file of size %i bytes had been synchronised", async (size) => { const path = nameFile("binary", "bin", size); - const isTooLarge = harnessDownload.plugin.services.vault.isFileSizeTooLarge(size); + const isTooLarge = harnessDownload.plugin.core.services.vault.isFileSizeTooLarge(size); if (isTooLarge) { - const entry = await harnessDownload.plugin.localDatabase.getDBEntry(path as FilePath); + const entry = await harnessDownload.plugin.core.localDatabase.getDBEntry(path as FilePath); console.log(`Skipping file of size ${size} bytes as it is too large to sync.`); expect(entry).toBe(false); } else { diff --git a/test/suite/sync_common.ts b/test/suite/sync_common.ts index ad4d724..f8ee4c2 100644 --- a/test/suite/sync_common.ts +++ b/test/suite/sync_common.ts @@ -7,11 +7,11 @@ import { commands } from "vitest/browser"; import { LiveSyncTrysteroReplicator } from "@/lib/src/replication/trystero/LiveSyncTrysteroReplicator"; import { waitTaskWithFollowups } from "../lib/util"; async function waitForP2PPeers(harness: LiveSyncHarness) { - if (harness.plugin.settings.remoteType === RemoteTypes.REMOTE_P2P) { + if (harness.plugin.core.settings.remoteType === RemoteTypes.REMOTE_P2P) { // Wait for peers to connect const maxRetries = 20; let retries = maxRetries; - const replicator = await harness.plugin.services.replicator.getActiveReplicator(); + const replicator = await harness.plugin.core.services.replicator.getActiveReplicator(); if (!(replicator instanceof LiveSyncTrysteroReplicator)) { throw new Error("Replicator is not an instance of LiveSyncTrysteroReplicator"); } @@ -38,8 +38,8 @@ async function waitForP2PPeers(harness: LiveSyncHarness) { } } export async function closeP2PReplicatorConnections(harness: LiveSyncHarness) { - if (harness.plugin.settings.remoteType === RemoteTypes.REMOTE_P2P) { - const replicator = await harness.plugin.services.replicator.getActiveReplicator(); + if (harness.plugin.core.settings.remoteType === RemoteTypes.REMOTE_P2P) { + const replicator = await harness.plugin.core.services.replicator.getActiveReplicator(); if (!(replicator instanceof LiveSyncTrysteroReplicator)) { throw new Error("Replicator is not an instance of LiveSyncTrysteroReplicator"); } @@ -58,9 +58,9 @@ export async function closeP2PReplicatorConnections(harness: LiveSyncHarness) { export async function performReplication(harness: LiveSyncHarness) { await waitForP2PPeers(harness); await delay(500); - const p = harness.plugin.services.replication.replicate(true); + const p = harness.plugin.core.services.replication.replicate(true); const task = - harness.plugin.settings.remoteType === RemoteTypes.REMOTE_P2P + harness.plugin.core.settings.remoteType === RemoteTypes.REMOTE_P2P ? waitTaskWithFollowups( p, () => { @@ -74,17 +74,17 @@ export async function performReplication(harness: LiveSyncHarness) { : p; const result = await task; // await waitForIdle(harness); - // if (harness.plugin.settings.remoteType === RemoteTypes.REMOTE_P2P) { + // if (harness.plugin.core.settings.remoteType === RemoteTypes.REMOTE_P2P) { // await closeP2PReplicatorConnections(harness); // } return result; } export async function closeReplication(harness: LiveSyncHarness) { - if (harness.plugin.settings.remoteType === RemoteTypes.REMOTE_P2P) { + if (harness.plugin.core.settings.remoteType === RemoteTypes.REMOTE_P2P) { return await closeP2PReplicatorConnections(harness); } - const replicator = await harness.plugin.services.replicator.getActiveReplicator(); + const replicator = await harness.plugin.core.services.replicator.getActiveReplicator(); if (!replicator) { console.log("No active replicator to close"); return; @@ -98,19 +98,21 @@ export async function prepareRemote(harness: LiveSyncHarness, setting: ObsidianL if (setting.remoteType !== RemoteTypes.REMOTE_P2P) { if (shouldReset) { await delay(1000); - await harness.plugin.services.replicator + await harness.plugin.core.services.replicator .getActiveReplicator() - ?.tryResetRemoteDatabase(harness.plugin.settings); + ?.tryResetRemoteDatabase(harness.plugin.core.settings); } else { - await harness.plugin.services.replicator + await harness.plugin.core.services.replicator .getActiveReplicator() - ?.tryCreateRemoteDatabase(harness.plugin.settings); + ?.tryCreateRemoteDatabase(harness.plugin.core.settings); } - await harness.plugin.services.replicator.getActiveReplicator()?.markRemoteResolved(harness.plugin.settings); - // No exceptions should be thrown - const status = await harness.plugin.services.replicator + await harness.plugin.core.services.replicator .getActiveReplicator() - ?.getRemoteStatus(harness.plugin.settings); + ?.markRemoteResolved(harness.plugin.core.settings); + // No exceptions should be thrown + const status = await harness.plugin.core.services.replicator + .getActiveReplicator() + ?.getRemoteStatus(harness.plugin.core.settings); console.log("Remote status:", status); expect(status).not.toBeFalsy(); } diff --git a/test/unit/dialog.test.ts b/test/unit/dialog.test.ts index 4554263..86c424c 100644 --- a/test/unit/dialog.test.ts +++ b/test/unit/dialog.test.ts @@ -69,7 +69,7 @@ describe("Dialog Tests", async () => { it("should show copy to clipboard dialog and confirm", async () => { const testString = "This is a test string to copy to clipboard."; const title = "Copy Test"; - const result = harness.plugin.services.UI.promptCopyToClipboard(title, testString); + const result = harness.plugin.core.services.UI.promptCopyToClipboard(title, testString); const isDialogShown = await waitForDialogShown(title, 500); expect(isDialogShown).toBe(true); const copyButton = page.getByText("📋"); diff --git a/updates.md b/updates.md index 53a4a9f..17fabc4 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,29 @@ 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 -- + +11th March, 2026 + +Now, Self-hosted LiveSync has finally begun to be split into the Self-hosted LiveSync plugin for Obsidian, and a properly abstracted version of it. +This may not offer much benefit to Obsidian plugin users, or might even cause a slight inconvenience, but I believe it will certainly help improve testability and make the ecosystem better. +However, I do not see the point in putting something with little benefit into beta, so I am handling this on the alpha branch. I would actually preferred to create an R&D branch, but I was not keen on the ampersand, and I feel it will eventually become a proper beta anyway. + +### Refactored + +- Separated `ObsidianLiveSyncPlugin` into `ObsidianLiveSyncPlugin` and `LiveSyncBaseCore`. +- Now `LiveSyncCore` indicates the type specified version of `LiveSyncBaseCore`. +- Referencing `plugin.xxx` has been rewritten to referencing the corresponding service or `core.xxx`. + +### Internal API changes + +- Storage Access APIs are now yielding Promises. This is to allow more limited storage platforms to be supported. + +### R&D + +- Browser-version of Self-hosted LiveSync is now in development. This is not intended for public use now, but I will eventually make it available for testing. +- We can see the code in `src/apps/webapp` for the browser version. + ## 0.25.52 9th March, 2026 @@ -14,11 +37,12 @@ I would like to devise a mechanism for running simple test scenarios. Now that w To improve the bus factor, we really need to organise the source code more thoroughly. Your cooperation and contributions would be greatly appreciated. ### Fixed + - No longer unexpected deletion-propagation occurs when the parent directory is not empty (#813). ### Revert reversions -- Reverted the reversion of ModuleCheckRemoteSize. Now it is back to the service feature. +- Reverted the reversion of ModuleCheckRemoteSize. Now it is back to the service feature. ## 0.25.51 @@ -27,7 +51,7 @@ To improve the bus factor, we really need to organise the source code more thoro ### Reverted - Reverted to ModuleRedFlag and ModuleInitializerFile to the previous version because of some unexpected issues. (#813) - - I will re-implement them in the future with better design and tests. + - I will re-implement them in the future with better design and tests. ## 0.25.50 From 2f8bc4fef297e09f06060c7e47c4064dad1c50d3 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 11 Mar 2026 14:44:37 +0100 Subject: [PATCH 058/339] - Now `useOfflineScanner`, `useCheckRemoteSize`, and `useRedFlagFeatures` are set from `main.ts`, instead of `LiveSyncBaseCore`. --- src/LiveSyncBaseCore.ts | 11 +++-------- src/lib | 2 +- src/main.ts | 6 ++++++ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/LiveSyncBaseCore.ts b/src/LiveSyncBaseCore.ts index 1f822bd..87eb268 100644 --- a/src/LiveSyncBaseCore.ts +++ b/src/LiveSyncBaseCore.ts @@ -12,8 +12,6 @@ import type { LiveSyncCouchDBReplicatorEnv } from "./lib/src/replication/couchdb import type { CheckPointInfo } from "./lib/src/replication/journal/JournalSyncTypes"; import type { LiveSyncJournalReplicatorEnv } from "./lib/src/replication/journal/LiveSyncJournalReplicatorEnv"; import type { LiveSyncReplicatorEnv } from "./lib/src/replication/LiveSyncAbstractReplicator"; -import { useCheckRemoteSize } from "./lib/src/serviceFeatures/checkRemoteSize"; -import { useOfflineScanner } from "./lib/src/serviceFeatures/offlineScanner"; import { useTargetFilters } from "./lib/src/serviceFeatures/targetFilter"; import type { ServiceContext } from "./lib/src/services/base/ServiceBase"; import type { InjectableServiceHub } from "./lib/src/services/InjectableServices"; @@ -27,8 +25,8 @@ import { ModuleConflictResolver } from "./modules/coreFeatures/ModuleConflictRes import { ModuleResolvingMismatchedTweaks } from "./modules/coreFeatures/ModuleResolveMismatchedTweaks"; import { ModuleLiveSyncMain } from "./modules/main/ModuleLiveSyncMain"; import type { ServiceModules } from "./lib/src/interfaces/ServiceModule"; -import { useRedFlagFeatures } from "./serviceFeatures/redFlag"; import { ModuleBasicMenu } from "./modules/essential/ModuleBasicMenu"; +import { usePrepareDatabaseForUse } from "./lib/src/serviceFeatures/prepareDatabaseForUse"; export class LiveSyncBaseCore< T extends ServiceContext = ServiceContext, @@ -271,12 +269,9 @@ export class LiveSyncBaseCore< * (Please refer `serviceFeatures` for more details) */ initialiseServiceFeatures() { - useRedFlagFeatures(this); - useOfflineScanner(this); - - // enable target filter feature. useTargetFilters(this); - useCheckRemoteSize(this); + // enable target filter feature. + usePrepareDatabaseForUse(this); } } diff --git a/src/lib b/src/lib index 7989f57..83e2704 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 7989f57e06c6858e3a99ebde02ec71d6a7811dbf +Subproject commit 83e2704c818c9563c4649ce3d9c13ed11a774d37 diff --git a/src/main.ts b/src/main.ts index 807ec55..bef08a0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -34,6 +34,9 @@ import { ModuleObsidianSettingsAsMarkdown } from "./modules/features/ModuleObsid import { SetupManager } from "./modules/features/SetupManager.ts"; import { ModuleMigration } from "./modules/essential/ModuleMigration.ts"; import { enableI18nFeature } from "./serviceFeatures/onLayoutReady/enablei18n.ts"; +import { useOfflineScanner } from "./lib/src/serviceFeatures/offlineScanner.ts"; +import { useCheckRemoteSize } from "./lib/src/serviceFeatures/checkRemoteSize.ts"; +import { useRedFlagFeatures } from "./serviceFeatures/redFlag.ts"; export type LiveSyncCore = LiveSyncBaseCore; export default class ObsidianLiveSyncPlugin extends Plugin { core: LiveSyncCore; @@ -167,6 +170,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin { const featuresInitialiser = enableI18nFeature; const curriedFeature = () => featuresInitialiser(core); core.services.appLifecycle.onLayoutReady.addHandler(curriedFeature); + useOfflineScanner(core); + useRedFlagFeatures(core); + useCheckRemoteSize(core); } ); } From 0742773e1eacdc675add16fa5ac479aca02aa6d0 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 11 Mar 2026 14:51:01 +0100 Subject: [PATCH 059/339] Add `self-hosted-livesync-cli` to `src/apps/cli` as a headless, and a dedicated version. --- package-lock.json | 788 ++++++++++++++---- package.json | 2 + src/apps/cli/.gitignore | 4 + src/apps/cli/README.md | 162 ++++ .../cli/adapters/NodeConversionAdapter.ts | 28 + .../cli/adapters/NodeFileSystemAdapter.ts | 153 ++++ src/apps/cli/adapters/NodePathAdapter.ts | 18 + src/apps/cli/adapters/NodeStorageAdapter.ts | 124 +++ src/apps/cli/adapters/NodeTypeGuardAdapter.ts | 15 + src/apps/cli/adapters/NodeTypes.ts | 22 + src/apps/cli/adapters/NodeVaultAdapter.ts | 118 +++ src/apps/cli/lib/pouchdb-node.ts | 134 +++ src/apps/cli/main.ts | 452 ++++++++++ .../managers/CLIStorageEventManagerAdapter.ts | 133 +++ .../cli/managers/StorageEventManagerCLI.ts | 28 + src/apps/cli/package.json | 16 + .../cli/serviceModules/CLIServiceModules.ts | 104 +++ .../cli/serviceModules/DatabaseFileAccess.ts | 15 + src/apps/cli/serviceModules/FileAccessCLI.ts | 20 + .../serviceModules/ServiceFileAccessImpl.ts | 12 + .../cli/services/NodeKeyValueDBService.ts | 211 +++++ src/apps/cli/services/NodeServiceHub.ts | 206 +++++ src/apps/cli/services/NodeSettingService.ts | 61 ++ src/apps/cli/test/test-push-pull-linux.sh | 69 ++ src/apps/cli/tsconfig.json | 32 + src/apps/cli/vite.config.ts | 55 ++ 26 files changed, 2839 insertions(+), 143 deletions(-) create mode 100644 src/apps/cli/.gitignore create mode 100644 src/apps/cli/README.md create mode 100644 src/apps/cli/adapters/NodeConversionAdapter.ts create mode 100644 src/apps/cli/adapters/NodeFileSystemAdapter.ts create mode 100644 src/apps/cli/adapters/NodePathAdapter.ts create mode 100644 src/apps/cli/adapters/NodeStorageAdapter.ts create mode 100644 src/apps/cli/adapters/NodeTypeGuardAdapter.ts create mode 100644 src/apps/cli/adapters/NodeTypes.ts create mode 100644 src/apps/cli/adapters/NodeVaultAdapter.ts create mode 100644 src/apps/cli/lib/pouchdb-node.ts create mode 100644 src/apps/cli/main.ts create mode 100644 src/apps/cli/managers/CLIStorageEventManagerAdapter.ts create mode 100644 src/apps/cli/managers/StorageEventManagerCLI.ts create mode 100644 src/apps/cli/package.json create mode 100644 src/apps/cli/serviceModules/CLIServiceModules.ts create mode 100644 src/apps/cli/serviceModules/DatabaseFileAccess.ts create mode 100644 src/apps/cli/serviceModules/FileAccessCLI.ts create mode 100644 src/apps/cli/serviceModules/ServiceFileAccessImpl.ts create mode 100644 src/apps/cli/services/NodeKeyValueDBService.ts create mode 100644 src/apps/cli/services/NodeServiceHub.ts create mode 100644 src/apps/cli/services/NodeSettingService.ts create mode 100644 src/apps/cli/test/test-push-pull-linux.sh create mode 100644 src/apps/cli/tsconfig.json create mode 100644 src/apps/cli/vite.config.ts diff --git a/package-lock.json b/package-lock.json index f2d5fff..d89d159 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,11 +15,13 @@ "@smithy/middleware-apply-body-checksum": "^4.3.9", "@smithy/protocol-http": "^5.3.9", "@smithy/querystring-builder": "^4.2.9", + "commander": "^14.0.3", "diff-match-patch": "^1.0.5", "fflate": "^0.8.2", "idb": "^8.0.3", "minimatch": "^10.2.2", "octagonal-wheels": "^0.1.45", + "pouchdb-adapter-leveldb": "^9.0.0", "qrcode-generator": "^1.4.4", "trystero": "^0.22.0", "xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2" @@ -7134,6 +7136,15 @@ "node": ">=6" } }, + "node_modules/catering": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", + "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/chai": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", @@ -7289,10 +7300,13 @@ "license": "MIT" }, "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } }, "node_modules/commist": { "version": "3.2.0", @@ -7362,7 +7376,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, "license": "MIT" }, "node_modules/crc-32": { @@ -7622,7 +7635,6 @@ "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz", "integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==", "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", - "dev": true, "license": "MIT", "dependencies": { "abstract-leveldown": "~6.2.1", @@ -7637,7 +7649,6 @@ "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", - "dev": true, "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -7654,7 +7665,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "funding": [ { "type": "github", @@ -7910,7 +7920,6 @@ "version": "2.1.0-0", "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", "integrity": "sha512-+BNfZ+deCo8hMNpDqDnvT+c0XpJ5cUa6mqYq89bho2Ifze4URTqRkcwR399hWoTrTkbZ/XJYDgP6rc7pRgffEQ==", - "dev": true, "license": "MIT" }, "node_modules/dunder-proto": { @@ -8008,6 +8017,63 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, + "node_modules/encoding-down": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", + "integrity": "sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==", + "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", + "license": "MIT", + "dependencies": { + "abstract-leveldown": "^6.2.1", + "inherits": "^2.0.3", + "level-codec": "^9.0.0", + "level-errors": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/encoding-down/node_modules/abstract-leveldown": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.3.0.tgz", + "integrity": "sha512-TU5nlYgta8YrBMNpc9FwQzRbiXsj49gsALsXadbGHt9CROPzX5fB0rWDR5mtdpOOKa5XqRFpbj1QroPAoPzVjQ==", + "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "immediate": "^3.2.3", + "level-concat-iterator": "~2.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/encoding-down/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/encoding-sniffer": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", @@ -8032,6 +8098,14 @@ "once": "^1.4.0" } }, + "node_modules/end-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/end-stream/-/end-stream-0.1.0.tgz", + "integrity": "sha512-Brl10T8kYnc75IepKizW6Y9liyW8ikz1B7n/xoHrJxoVSSjoqPn30sb7XVFfQERK4QfUMYRGs9dhWwtt2eu6uA==", + "dependencies": { + "write-stream": "~0.4.3" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -8049,7 +8123,6 @@ "version": "0.1.8", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, "license": "MIT", "dependencies": { "prr": "~1.0.1" @@ -8972,7 +9045,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-2.2.0.tgz", "integrity": "sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==", - "dev": true, "dependencies": { "set-cookie-parser": "^2.4.8", "tough-cookie": "^4.0.0" @@ -9781,7 +9853,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==", - "dev": true, "license": "MIT" }, "node_modules/import-fresh": { @@ -9949,6 +10020,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -10882,12 +10976,29 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/level": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/level/-/level-6.0.1.tgz", + "integrity": "sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw==", + "license": "MIT", + "dependencies": { + "level-js": "^5.0.0", + "level-packager": "^5.1.0", + "leveldown": "^5.4.0" + }, + "engines": { + "node": ">=8.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/level" + } + }, "node_modules/level-codec": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.2.tgz", "integrity": "sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==", "deprecated": "Superseded by level-transcoder (https://github.com/Level/community#faq)", - "dev": true, "license": "MIT", "dependencies": { "buffer": "^5.6.0" @@ -10900,7 +11011,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "funding": [ { "type": "github", @@ -10926,7 +11036,6 @@ "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==", "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -10937,7 +11046,6 @@ "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", - "dev": true, "license": "MIT", "dependencies": { "errno": "~0.1.1" @@ -10950,7 +11058,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz", "integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==", - "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.4", @@ -10965,7 +11072,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -10976,11 +11082,78 @@ "node": ">= 6" } }, + "node_modules/level-js": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/level-js/-/level-js-5.0.2.tgz", + "integrity": "sha512-SnBIDo2pdO5VXh02ZmtAyPP6/+6YTJg2ibLtl9C34pWvmtMEmRTWpra+qO/hifkUtBTOtfx6S9vLDjBsBK4gRg==", + "deprecated": "Superseded by browser-level (https://github.com/Level/community#faq)", + "license": "MIT", + "dependencies": { + "abstract-leveldown": "~6.2.3", + "buffer": "^5.5.0", + "inherits": "^2.0.3", + "ltgt": "^2.1.2" + } + }, + "node_modules/level-js/node_modules/abstract-leveldown": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", + "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", + "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "immediate": "^3.2.3", + "level-concat-iterator": "~2.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/level-js/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/level-packager": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.1.1.tgz", + "integrity": "sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==", + "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", + "license": "MIT", + "dependencies": { + "encoding-down": "^6.3.0", + "levelup": "^4.3.2" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/level-supports": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", - "dev": true, "license": "MIT", "dependencies": { "xtend": "^4.0.2" @@ -10989,12 +11162,143 @@ "node": ">=6" } }, + "node_modules/level-write-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/level-write-stream/-/level-write-stream-1.0.0.tgz", + "integrity": "sha512-bBNKOEOMl8msO+uIM9YX/gUO6ckokZ/4pCwTm/lwvs46x6Xs8Zy0sn3Vh37eDqse4mhy4fOMIb/JsSM2nyQFtw==", + "dependencies": { + "end-stream": "~0.1.0" + } + }, + "node_modules/level/node_modules/abstract-leveldown": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", + "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", + "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "immediate": "^3.2.3", + "level-concat-iterator": "~2.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/level/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/level/node_modules/leveldown": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.6.0.tgz", + "integrity": "sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==", + "deprecated": "Superseded by classic-level (https://github.com/Level/community#faq)", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "abstract-leveldown": "~6.2.1", + "napi-macros": "~2.0.0", + "node-gyp-build": "~4.1.0" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/level/node_modules/node-gyp-build": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz", + "integrity": "sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/leveldown": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-6.1.1.tgz", + "integrity": "sha512-88c+E+Eizn4CkQOBHwqlCJaTNEjGpaEIikn1S+cINc5E9HEvJ77bqY4JY/HxT5u0caWqsc3P3DcFIKBI1vHt+A==", + "deprecated": "Superseded by classic-level (https://github.com/Level/community#faq)", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "abstract-leveldown": "^7.2.0", + "napi-macros": "~2.0.0", + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/leveldown/node_modules/abstract-leveldown": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-7.2.0.tgz", + "integrity": "sha512-DnhQwcFEaYsvYDnACLZhMmCWd3rkOeEvglpa4q5i/5Jlm3UIsWaxVzuXvDLFCSCWRO3yy2/+V/G7FusFgejnfQ==", + "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "catering": "^2.0.0", + "is-buffer": "^2.0.5", + "level-concat-iterator": "^3.0.0", + "level-supports": "^2.0.1", + "queue-microtask": "^1.2.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/leveldown/node_modules/level-concat-iterator": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-3.1.0.tgz", + "integrity": "sha512-BWRCMHBxbIqPxJ8vHOvKUsaO0v1sLYZtjN3K2iZJsRBYtp+ONsY6Jfi6hy9K3+zolgQRryhIn2NRZjZnWJ9NmQ==", + "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", + "license": "MIT", + "dependencies": { + "catering": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/leveldown/node_modules/level-supports": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-2.1.0.tgz", + "integrity": "sha512-E486g1NCjW5cF78KGPrMDRBYzPuueMZ6VBXHT6gC7A8UYWGiM14fGgp+s/L1oFfDWSPV/+SFkYCmZ0SiESkRKA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/levelup": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.4.0.tgz", "integrity": "sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==", "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", - "dev": true, "license": "MIT", "dependencies": { "deferred-leveldown": "~5.3.0", @@ -11211,7 +11515,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==", - "dev": true, "license": "MIT" }, "node_modules/magic-string": { @@ -11531,6 +11834,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-macros": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", + "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==", + "license": "MIT" + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -11550,7 +11859,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", - "dev": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -11566,6 +11874,17 @@ } } }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -12487,11 +12806,24 @@ "pouchdb-utils": "9.0.0" } }, + "node_modules/pouchdb-adapter-leveldb": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/pouchdb-adapter-leveldb/-/pouchdb-adapter-leveldb-9.0.0.tgz", + "integrity": "sha512-kF5OAN8io3j9HWP7SY0ycJrhpXxklGpO0A6On0TZXZzji2wwjLNMBxyaPGVbVni95+/t1u7Xdo3r0cAjfm+mww==", + "license": "Apache-2.0", + "dependencies": { + "level": "6.0.1", + "level-write-stream": "1.0.0", + "leveldown": "6.1.1", + "pouchdb-adapter-leveldb-core": "9.0.0", + "pouchdb-merge": "9.0.0", + "through2": "3.0.2" + } + }, "node_modules/pouchdb-adapter-leveldb-core": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-adapter-leveldb-core/-/pouchdb-adapter-leveldb-core-9.0.0.tgz", "integrity": "sha512-b3ZGPtVXyivGL5SK3AIDG7PrNsZdoDpGFkmTytDTtctkVhxOg71gnXXP+CrupENPqSNG/eGbKW4w+bbMpxy6aA==", - "dev": true, "license": "Apache-2.0", "dependencies": { "double-ended-queue": "2.1.0-0", @@ -12523,7 +12855,6 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-adapter-utils/-/pouchdb-adapter-utils-9.0.0.tgz", "integrity": "sha512-hmbm4ey0HL0vtoY1tRTPIt2FfYjvMh3DWoGGSxXDTS73qTFQ+Fhhi5I0AnN9PcD2omfKQAVXiYks4kkMvlAHqA==", - "dev": true, "dependencies": { "pouchdb-binary-utils": "9.0.0", "pouchdb-errors": "9.0.0", @@ -12535,14 +12866,12 @@ "node_modules/pouchdb-binary-utils": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-binary-utils/-/pouchdb-binary-utils-9.0.0.tgz", - "integrity": "sha512-2OMtgDZi82vqs+zNDE0YiYjOaWkYCUcZJZKK3WkRr+XYRu+2B7umJrnygJFhUwoGedBbHSrlQBLhdNV3F1AX1A==", - "dev": true + "integrity": "sha512-2OMtgDZi82vqs+zNDE0YiYjOaWkYCUcZJZKK3WkRr+XYRu+2B7umJrnygJFhUwoGedBbHSrlQBLhdNV3F1AX1A==" }, "node_modules/pouchdb-changes-filter": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-changes-filter/-/pouchdb-changes-filter-9.0.0.tgz", "integrity": "sha512-ig0fo0WLgIjAniFJ19Uw1Y+oxiypqC+Skhd8BCETRVXOhLBzueRwEQR4thffyo0UayYVqldJfSR5wHSDvEVk/A==", - "dev": true, "dependencies": { "pouchdb-errors": "9.0.0", "pouchdb-selector-core": "9.0.0", @@ -12562,14 +12891,12 @@ "node_modules/pouchdb-collate": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-collate/-/pouchdb-collate-9.0.0.tgz", - "integrity": "sha512-TrnEDNZEmIIl+W3xKUO8h+geqVLQ90oZe5ujPkl8myUzpREULWXWQBnV5EzPXVEKDBpJlb8T3I6oy/zdWGQpdA==", - "dev": true + "integrity": "sha512-TrnEDNZEmIIl+W3xKUO8h+geqVLQ90oZe5ujPkl8myUzpREULWXWQBnV5EzPXVEKDBpJlb8T3I6oy/zdWGQpdA==" }, "node_modules/pouchdb-core": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-core/-/pouchdb-core-9.0.0.tgz", "integrity": "sha512-98SJgs8bqXhr4gMGuOTR8yVeLlMYy797zlOtdlvlXIxIicvocyA8ColhVVhdBXPNOGxT2HwReIMywdIVAgibpg==", - "dev": true, "dependencies": { "pouchdb-changes-filter": "9.0.0", "pouchdb-errors": "9.0.0", @@ -12582,14 +12909,12 @@ "node_modules/pouchdb-errors": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-errors/-/pouchdb-errors-9.0.0.tgz", - "integrity": "sha512-961PSMLhW0UqqdJ566g+CdLZ5pkBJRd6l4WWpCDdD0USvE4xYfYGzv43w7nZZBw1k3Xdy092yqPge7yX/tfnyw==", - "dev": true + "integrity": "sha512-961PSMLhW0UqqdJ566g+CdLZ5pkBJRd6l4WWpCDdD0USvE4xYfYGzv43w7nZZBw1k3Xdy092yqPge7yX/tfnyw==" }, "node_modules/pouchdb-fetch": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-fetch/-/pouchdb-fetch-9.0.0.tgz", "integrity": "sha512-TbE3cUcAJQrwb9kr44tDP0X+NAbcqgjsTvcL30L4xzBNJeCPTIRjukYX80s154SHJUXBxcWRiPsMmNqpXsjfCA==", - "dev": true, "dependencies": { "fetch-cookie": "2.2.0", "node-fetch": "2.6.9" @@ -12624,7 +12949,6 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-json/-/pouchdb-json-9.0.0.tgz", "integrity": "sha512-aI41mYVyI195GXuT1Ys7mLIB/Mvrz11ihoTP6km6hYqVgSuaUxuZcFUozlyTJiZXr7H5kdhNgclhlVnjir4JAA==", - "dev": true, "dependencies": { "vuvuzela": "1.0.3" } @@ -12653,7 +12977,6 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-md5/-/pouchdb-md5-9.0.0.tgz", "integrity": "sha512-58xUYBvW3/s+aH0j4uOhhN8yCk0LQ254cxBzI/gbKA9PrfwHpe4zrr0L/ia5ml3A30oH1f8aTnuVMwWDkFcuww==", - "dev": true, "license": "Apache-2.0", "dependencies": { "pouchdb-binary-utils": "9.0.0", @@ -12664,7 +12987,6 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-merge/-/pouchdb-merge-9.0.0.tgz", "integrity": "sha512-Xh+TgOZCkGoZpI589btKf/cTiuQ5CsnPl9YpdW4h0cAPusniN6XNsR62F+/HbL9wirI6XTEPHUrk7MsQbk3S3A==", - "dev": true, "dependencies": { "pouchdb-utils": "9.0.0" } @@ -12685,7 +13007,6 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-selector-core/-/pouchdb-selector-core-9.0.0.tgz", "integrity": "sha512-ZYHYsdoedwm8j5tYofz+3+uUSK8i+7tRCBb01T0OuqDQb17+w5mzjHF8Ppi160xdPUPaWCo1Un+nLWGJzkmA3g==", - "dev": true, "dependencies": { "pouchdb-collate": "9.0.0", "pouchdb-utils": "9.0.0" @@ -12695,7 +13016,6 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-utils/-/pouchdb-utils-9.0.0.tgz", "integrity": "sha512-xWZE5c+nAslgmLC8JBZbky8AYgdz7pKtv7KTSi6CD2tuQD0WyNKib0YnhZndeE84dksTeZlqlg56RQHsHoB2LQ==", - "dev": true, "dependencies": { "pouchdb-errors": "9.0.0", "pouchdb-md5": "9.0.0", @@ -12840,14 +13160,12 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "dev": true, "license": "MIT" }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, "node_modules/pump": { "version": "3.0.3", @@ -12864,7 +13182,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, "engines": { "node": ">=6" } @@ -12885,14 +13202,12 @@ "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -13054,8 +13369,7 @@ "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "node_modules/resolve": { "version": "1.22.8", @@ -13409,8 +13723,7 @@ "node_modules/set-cookie-parser": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", - "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", - "dev": true + "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==" }, "node_modules/set-function-length": { "version": "1.2.2", @@ -13699,8 +14012,7 @@ "node_modules/spark-md5": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.2.tgz", - "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==", - "dev": true + "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==" }, "node_modules/split2": { "version": "4.2.0", @@ -13920,7 +14232,6 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/sublevel-pouchdb/-/sublevel-pouchdb-9.0.0.tgz", "integrity": "sha512-pX4r8+F7wuts0C81kUJ341h4bl2aRe7qV572FE8X1FMz9VkKlmi2nPD1vfeiOJXz5Y09I4MHjGULAbqvTfQZEQ==", - "dev": true, "license": "Apache-2.0", "dependencies": { "level-codec": "9.0.2", @@ -13932,14 +14243,12 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true, "license": "MIT" }, "node_modules/sublevel-pouchdb/node_modules/readable-stream": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "dev": true, "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", @@ -13952,7 +14261,6 @@ "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", - "dev": true, "license": "MIT" }, "node_modules/supports-color": { @@ -14191,7 +14499,6 @@ "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -14205,6 +14512,13 @@ "node": ">=10" } }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, "node_modules/text-decoder": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", @@ -14219,7 +14533,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.4", @@ -14230,7 +14543,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -14344,7 +14656,6 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "dev": true, "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -15111,7 +15422,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, "engines": { "node": ">= 4.0.0" } @@ -15130,7 +15440,6 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -15163,7 +15472,6 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, "bin": { "uuid": "dist/bin/uuid" } @@ -15858,8 +16166,7 @@ "node_modules/vuvuzela": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/vuvuzela/-/vuvuzela-1.0.3.tgz", - "integrity": "sha512-Tm7jR1xTzBbPW+6y1tknKiEhz04Wf/1iZkcTJjSFcpNko43+dFW6+OOeQe9taJIug3NdfUAjFKgUSyQrIKaDvQ==", - "dev": true + "integrity": "sha512-Tm7jR1xTzBbPW+6y1tknKiEhz04Wf/1iZkcTJjSFcpNko43+dFW6+OOeQe9taJIug3NdfUAjFKgUSyQrIKaDvQ==" }, "node_modules/w3c-keyname": { "version": "2.2.8", @@ -16317,6 +16624,20 @@ "dev": true, "license": "ISC" }, + "node_modules/write-stream": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/write-stream/-/write-stream-0.4.3.tgz", + "integrity": "sha512-IJrvkhbAnj89W/GAVdVgbnPiVw5Ntg/B4tc/MUCIEwj/g6JIww1DWJyB/yBMT3yw2/TkT6IUZ0+IYef3flEw8A==", + "dependencies": { + "readable-stream": "~0.0.2" + } + }, + "node_modules/write-stream/node_modules/readable-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-0.0.4.tgz", + "integrity": "sha512-azrivNydKRYt7zwLV5wWUK7YzKTWs3q87xSmY6DlHapPrCvaT6ZrukvM5erV+yCSSPmZT8zkSdttOHQpWWm9zw==", + "license": "BSD" + }, "node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", @@ -16342,7 +16663,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4" @@ -21322,6 +21642,11 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, + "catering": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", + "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==" + }, "chai": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", @@ -21433,10 +21758,9 @@ "dev": true }, "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==" }, "commist": { "version": "3.2.0", @@ -21494,8 +21818,7 @@ "core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "crc-32": { "version": "1.2.2", @@ -21664,7 +21987,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz", "integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==", - "dev": true, "requires": { "abstract-leveldown": "~6.2.1", "inherits": "^2.0.3" @@ -21674,7 +21996,6 @@ "version": "6.2.3", "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", - "dev": true, "requires": { "buffer": "^5.5.0", "immediate": "^3.2.3", @@ -21687,7 +22008,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "requires": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -21845,8 +22165,7 @@ "double-ended-queue": { "version": "2.1.0-0", "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", - "integrity": "sha512-+BNfZ+deCo8hMNpDqDnvT+c0XpJ5cUa6mqYq89bho2Ifze4URTqRkcwR399hWoTrTkbZ/XJYDgP6rc7pRgffEQ==", - "dev": true + "integrity": "sha512-+BNfZ+deCo8hMNpDqDnvT+c0XpJ5cUa6mqYq89bho2Ifze4URTqRkcwR399hWoTrTkbZ/XJYDgP6rc7pRgffEQ==" }, "dunder-proto": { "version": "1.0.1", @@ -21913,6 +22232,40 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "encoding-down": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", + "integrity": "sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==", + "requires": { + "abstract-leveldown": "^6.2.1", + "inherits": "^2.0.3", + "level-codec": "^9.0.0", + "level-errors": "^2.0.0" + }, + "dependencies": { + "abstract-leveldown": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.3.0.tgz", + "integrity": "sha512-TU5nlYgta8YrBMNpc9FwQzRbiXsj49gsALsXadbGHt9CROPzX5fB0rWDR5mtdpOOKa5XqRFpbj1QroPAoPzVjQ==", + "requires": { + "buffer": "^5.5.0", + "immediate": "^3.2.3", + "level-concat-iterator": "~2.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + } + } + }, "encoding-sniffer": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", @@ -21932,6 +22285,14 @@ "once": "^1.4.0" } }, + "end-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/end-stream/-/end-stream-0.1.0.tgz", + "integrity": "sha512-Brl10T8kYnc75IepKizW6Y9liyW8ikz1B7n/xoHrJxoVSSjoqPn30sb7XVFfQERK4QfUMYRGs9dhWwtt2eu6uA==", + "requires": { + "write-stream": "~0.4.3" + } + }, "entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -21942,7 +22303,6 @@ "version": "0.1.8", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, "requires": { "prr": "~1.0.1" } @@ -22593,7 +22953,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-2.2.0.tgz", "integrity": "sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==", - "dev": true, "requires": { "set-cookie-parser": "^2.4.8", "tough-cookie": "^4.0.0" @@ -23135,8 +23494,7 @@ "immediate": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", - "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==", - "dev": true + "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==" }, "import-fresh": { "version": "3.3.1", @@ -23248,6 +23606,11 @@ "has-tostringtag": "^1.0.2" } }, + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" + }, "is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -23912,11 +24275,58 @@ } } }, + "level": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/level/-/level-6.0.1.tgz", + "integrity": "sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw==", + "requires": { + "level-js": "^5.0.0", + "level-packager": "^5.1.0", + "leveldown": "^5.4.0" + }, + "dependencies": { + "abstract-leveldown": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", + "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", + "requires": { + "buffer": "^5.5.0", + "immediate": "^3.2.3", + "level-concat-iterator": "~2.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "leveldown": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.6.0.tgz", + "integrity": "sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==", + "requires": { + "abstract-leveldown": "~6.2.1", + "napi-macros": "~2.0.0", + "node-gyp-build": "~4.1.0" + } + }, + "node-gyp-build": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz", + "integrity": "sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==" + } + } + }, "level-codec": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.2.tgz", "integrity": "sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==", - "dev": true, "requires": { "buffer": "^5.6.0" }, @@ -23925,7 +24335,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "requires": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -23936,14 +24345,12 @@ "level-concat-iterator": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", - "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==", - "dev": true + "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==" }, "level-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", - "dev": true, "requires": { "errno": "~0.1.1" } @@ -23952,7 +24359,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz", "integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==", - "dev": true, "requires": { "inherits": "^2.0.4", "readable-stream": "^3.4.0", @@ -23963,7 +24369,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -23972,20 +24377,107 @@ } } }, + "level-js": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/level-js/-/level-js-5.0.2.tgz", + "integrity": "sha512-SnBIDo2pdO5VXh02ZmtAyPP6/+6YTJg2ibLtl9C34pWvmtMEmRTWpra+qO/hifkUtBTOtfx6S9vLDjBsBK4gRg==", + "requires": { + "abstract-leveldown": "~6.2.3", + "buffer": "^5.5.0", + "inherits": "^2.0.3", + "ltgt": "^2.1.2" + }, + "dependencies": { + "abstract-leveldown": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", + "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", + "requires": { + "buffer": "^5.5.0", + "immediate": "^3.2.3", + "level-concat-iterator": "~2.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + } + } + }, + "level-packager": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.1.1.tgz", + "integrity": "sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==", + "requires": { + "encoding-down": "^6.3.0", + "levelup": "^4.3.2" + } + }, "level-supports": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", - "dev": true, "requires": { "xtend": "^4.0.2" } }, + "level-write-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/level-write-stream/-/level-write-stream-1.0.0.tgz", + "integrity": "sha512-bBNKOEOMl8msO+uIM9YX/gUO6ckokZ/4pCwTm/lwvs46x6Xs8Zy0sn3Vh37eDqse4mhy4fOMIb/JsSM2nyQFtw==", + "requires": { + "end-stream": "~0.1.0" + } + }, + "leveldown": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-6.1.1.tgz", + "integrity": "sha512-88c+E+Eizn4CkQOBHwqlCJaTNEjGpaEIikn1S+cINc5E9HEvJ77bqY4JY/HxT5u0caWqsc3P3DcFIKBI1vHt+A==", + "requires": { + "abstract-leveldown": "^7.2.0", + "napi-macros": "~2.0.0", + "node-gyp-build": "^4.3.0" + }, + "dependencies": { + "abstract-leveldown": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-7.2.0.tgz", + "integrity": "sha512-DnhQwcFEaYsvYDnACLZhMmCWd3rkOeEvglpa4q5i/5Jlm3UIsWaxVzuXvDLFCSCWRO3yy2/+V/G7FusFgejnfQ==", + "requires": { + "buffer": "^6.0.3", + "catering": "^2.0.0", + "is-buffer": "^2.0.5", + "level-concat-iterator": "^3.0.0", + "level-supports": "^2.0.1", + "queue-microtask": "^1.2.3" + } + }, + "level-concat-iterator": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-3.1.0.tgz", + "integrity": "sha512-BWRCMHBxbIqPxJ8vHOvKUsaO0v1sLYZtjN3K2iZJsRBYtp+ONsY6Jfi6hy9K3+zolgQRryhIn2NRZjZnWJ9NmQ==", + "requires": { + "catering": "^2.1.0" + } + }, + "level-supports": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-2.1.0.tgz", + "integrity": "sha512-E486g1NCjW5cF78KGPrMDRBYzPuueMZ6VBXHT6gC7A8UYWGiM14fGgp+s/L1oFfDWSPV/+SFkYCmZ0SiESkRKA==" + } + } + }, "levelup": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.4.0.tgz", "integrity": "sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==", - "dev": true, "requires": { "deferred-leveldown": "~5.3.0", "level-errors": "~2.0.0", @@ -24152,8 +24644,7 @@ "ltgt": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", - "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==", - "dev": true + "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==" }, "magic-string": { "version": "0.30.21", @@ -24379,6 +24870,11 @@ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true }, + "napi-macros": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", + "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==" + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -24394,11 +24890,15 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", - "dev": true, "requires": { "whatwg-url": "^5.0.0" } }, + "node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==" + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -24981,11 +25481,23 @@ "pouchdb-utils": "9.0.0" } }, + "pouchdb-adapter-leveldb": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/pouchdb-adapter-leveldb/-/pouchdb-adapter-leveldb-9.0.0.tgz", + "integrity": "sha512-kF5OAN8io3j9HWP7SY0ycJrhpXxklGpO0A6On0TZXZzji2wwjLNMBxyaPGVbVni95+/t1u7Xdo3r0cAjfm+mww==", + "requires": { + "level": "6.0.1", + "level-write-stream": "1.0.0", + "leveldown": "6.1.1", + "pouchdb-adapter-leveldb-core": "9.0.0", + "pouchdb-merge": "9.0.0", + "through2": "3.0.2" + } + }, "pouchdb-adapter-leveldb-core": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-adapter-leveldb-core/-/pouchdb-adapter-leveldb-core-9.0.0.tgz", "integrity": "sha512-b3ZGPtVXyivGL5SK3AIDG7PrNsZdoDpGFkmTytDTtctkVhxOg71gnXXP+CrupENPqSNG/eGbKW4w+bbMpxy6aA==", - "dev": true, "requires": { "double-ended-queue": "2.1.0-0", "levelup": "4.4.0", @@ -25015,7 +25527,6 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-adapter-utils/-/pouchdb-adapter-utils-9.0.0.tgz", "integrity": "sha512-hmbm4ey0HL0vtoY1tRTPIt2FfYjvMh3DWoGGSxXDTS73qTFQ+Fhhi5I0AnN9PcD2omfKQAVXiYks4kkMvlAHqA==", - "dev": true, "requires": { "pouchdb-binary-utils": "9.0.0", "pouchdb-errors": "9.0.0", @@ -25027,14 +25538,12 @@ "pouchdb-binary-utils": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-binary-utils/-/pouchdb-binary-utils-9.0.0.tgz", - "integrity": "sha512-2OMtgDZi82vqs+zNDE0YiYjOaWkYCUcZJZKK3WkRr+XYRu+2B7umJrnygJFhUwoGedBbHSrlQBLhdNV3F1AX1A==", - "dev": true + "integrity": "sha512-2OMtgDZi82vqs+zNDE0YiYjOaWkYCUcZJZKK3WkRr+XYRu+2B7umJrnygJFhUwoGedBbHSrlQBLhdNV3F1AX1A==" }, "pouchdb-changes-filter": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-changes-filter/-/pouchdb-changes-filter-9.0.0.tgz", "integrity": "sha512-ig0fo0WLgIjAniFJ19Uw1Y+oxiypqC+Skhd8BCETRVXOhLBzueRwEQR4thffyo0UayYVqldJfSR5wHSDvEVk/A==", - "dev": true, "requires": { "pouchdb-errors": "9.0.0", "pouchdb-selector-core": "9.0.0", @@ -25054,14 +25563,12 @@ "pouchdb-collate": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-collate/-/pouchdb-collate-9.0.0.tgz", - "integrity": "sha512-TrnEDNZEmIIl+W3xKUO8h+geqVLQ90oZe5ujPkl8myUzpREULWXWQBnV5EzPXVEKDBpJlb8T3I6oy/zdWGQpdA==", - "dev": true + "integrity": "sha512-TrnEDNZEmIIl+W3xKUO8h+geqVLQ90oZe5ujPkl8myUzpREULWXWQBnV5EzPXVEKDBpJlb8T3I6oy/zdWGQpdA==" }, "pouchdb-core": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-core/-/pouchdb-core-9.0.0.tgz", "integrity": "sha512-98SJgs8bqXhr4gMGuOTR8yVeLlMYy797zlOtdlvlXIxIicvocyA8ColhVVhdBXPNOGxT2HwReIMywdIVAgibpg==", - "dev": true, "requires": { "pouchdb-changes-filter": "9.0.0", "pouchdb-errors": "9.0.0", @@ -25074,14 +25581,12 @@ "pouchdb-errors": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-errors/-/pouchdb-errors-9.0.0.tgz", - "integrity": "sha512-961PSMLhW0UqqdJ566g+CdLZ5pkBJRd6l4WWpCDdD0USvE4xYfYGzv43w7nZZBw1k3Xdy092yqPge7yX/tfnyw==", - "dev": true + "integrity": "sha512-961PSMLhW0UqqdJ566g+CdLZ5pkBJRd6l4WWpCDdD0USvE4xYfYGzv43w7nZZBw1k3Xdy092yqPge7yX/tfnyw==" }, "pouchdb-fetch": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-fetch/-/pouchdb-fetch-9.0.0.tgz", "integrity": "sha512-TbE3cUcAJQrwb9kr44tDP0X+NAbcqgjsTvcL30L4xzBNJeCPTIRjukYX80s154SHJUXBxcWRiPsMmNqpXsjfCA==", - "dev": true, "requires": { "fetch-cookie": "2.2.0", "node-fetch": "2.6.9" @@ -25116,7 +25621,6 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-json/-/pouchdb-json-9.0.0.tgz", "integrity": "sha512-aI41mYVyI195GXuT1Ys7mLIB/Mvrz11ihoTP6km6hYqVgSuaUxuZcFUozlyTJiZXr7H5kdhNgclhlVnjir4JAA==", - "dev": true, "requires": { "vuvuzela": "1.0.3" } @@ -25145,7 +25649,6 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-md5/-/pouchdb-md5-9.0.0.tgz", "integrity": "sha512-58xUYBvW3/s+aH0j4uOhhN8yCk0LQ254cxBzI/gbKA9PrfwHpe4zrr0L/ia5ml3A30oH1f8aTnuVMwWDkFcuww==", - "dev": true, "requires": { "pouchdb-binary-utils": "9.0.0", "spark-md5": "3.0.2" @@ -25155,7 +25658,6 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-merge/-/pouchdb-merge-9.0.0.tgz", "integrity": "sha512-Xh+TgOZCkGoZpI589btKf/cTiuQ5CsnPl9YpdW4h0cAPusniN6XNsR62F+/HbL9wirI6XTEPHUrk7MsQbk3S3A==", - "dev": true, "requires": { "pouchdb-utils": "9.0.0" } @@ -25176,7 +25678,6 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-selector-core/-/pouchdb-selector-core-9.0.0.tgz", "integrity": "sha512-ZYHYsdoedwm8j5tYofz+3+uUSK8i+7tRCBb01T0OuqDQb17+w5mzjHF8Ppi160xdPUPaWCo1Un+nLWGJzkmA3g==", - "dev": true, "requires": { "pouchdb-collate": "9.0.0", "pouchdb-utils": "9.0.0" @@ -25186,7 +25687,6 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-utils/-/pouchdb-utils-9.0.0.tgz", "integrity": "sha512-xWZE5c+nAslgmLC8JBZbky8AYgdz7pKtv7KTSi6CD2tuQD0WyNKib0YnhZndeE84dksTeZlqlg56RQHsHoB2LQ==", - "dev": true, "requires": { "pouchdb-errors": "9.0.0", "pouchdb-md5": "9.0.0", @@ -25294,14 +25794,12 @@ "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "dev": true + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==" }, "psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, "pump": { "version": "3.0.3", @@ -25316,8 +25814,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qrcode-generator": { "version": "1.5.2", @@ -25333,14 +25830,12 @@ "querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, "quick-lru": { "version": "7.3.0", @@ -25445,8 +25940,7 @@ "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "resolve": { "version": "1.22.8", @@ -25668,8 +26162,7 @@ "set-cookie-parser": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", - "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", - "dev": true + "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==" }, "set-function-length": { "version": "1.2.2", @@ -25862,8 +26355,7 @@ "spark-md5": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.2.tgz", - "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==", - "dev": true + "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==" }, "split2": { "version": "4.2.0", @@ -26014,7 +26506,6 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/sublevel-pouchdb/-/sublevel-pouchdb-9.0.0.tgz", "integrity": "sha512-pX4r8+F7wuts0C81kUJ341h4bl2aRe7qV572FE8X1FMz9VkKlmi2nPD1vfeiOJXz5Y09I4MHjGULAbqvTfQZEQ==", - "dev": true, "requires": { "level-codec": "9.0.2", "ltgt": "2.2.1", @@ -26024,14 +26515,12 @@ "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" }, "readable-stream": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -26042,8 +26531,7 @@ "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", - "dev": true + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" } } }, @@ -26174,12 +26662,19 @@ "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", "dev": true, - "peer": true, "requires": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } } }, "text-decoder": { @@ -26195,7 +26690,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "dev": true, "requires": { "inherits": "^2.0.4", "readable-stream": "2 || 3" @@ -26205,7 +26699,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -26277,7 +26770,6 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "dev": true, "requires": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -26702,8 +27194,7 @@ "universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" }, "uri-js": { "version": "4.4.1", @@ -26718,7 +27209,6 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, "requires": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -26744,8 +27234,7 @@ "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "vite": { "version": "7.3.1", @@ -27035,8 +27524,7 @@ "vuvuzela": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/vuvuzela/-/vuvuzela-1.0.3.tgz", - "integrity": "sha512-Tm7jR1xTzBbPW+6y1tknKiEhz04Wf/1iZkcTJjSFcpNko43+dFW6+OOeQe9taJIug3NdfUAjFKgUSyQrIKaDvQ==", - "dev": true + "integrity": "sha512-Tm7jR1xTzBbPW+6y1tknKiEhz04Wf/1iZkcTJjSFcpNko43+dFW6+OOeQe9taJIug3NdfUAjFKgUSyQrIKaDvQ==" }, "w3c-keyname": { "version": "2.2.8", @@ -27371,6 +27859,21 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "write-stream": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/write-stream/-/write-stream-0.4.3.tgz", + "integrity": "sha512-IJrvkhbAnj89W/GAVdVgbnPiVw5Ntg/B4tc/MUCIEwj/g6JIww1DWJyB/yBMT3yw2/TkT6IUZ0+IYef3flEw8A==", + "requires": { + "readable-stream": "~0.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-0.0.4.tgz", + "integrity": "sha512-azrivNydKRYt7zwLV5wWUK7YzKTWs3q87xSmY6DlHapPrCvaT6ZrukvM5erV+yCSSPmZT8zkSdttOHQpWWm9zw==" + } + } + }, "ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", @@ -27380,8 +27883,7 @@ "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "xxhash-wasm-102": { "version": "npm:xxhash-wasm@1.0.2", diff --git a/package.json b/package.json index ee4ea56..4b8eb28 100644 --- a/package.json +++ b/package.json @@ -129,11 +129,13 @@ "@smithy/middleware-apply-body-checksum": "^4.3.9", "@smithy/protocol-http": "^5.3.9", "@smithy/querystring-builder": "^4.2.9", + "commander": "^14.0.3", "diff-match-patch": "^1.0.5", "fflate": "^0.8.2", "idb": "^8.0.3", "minimatch": "^10.2.2", "octagonal-wheels": "^0.1.45", + "pouchdb-adapter-leveldb": "^9.0.0", "qrcode-generator": "^1.4.4", "trystero": "^0.22.0", "xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2" diff --git a/src/apps/cli/.gitignore b/src/apps/cli/.gitignore new file mode 100644 index 0000000..630bad4 --- /dev/null +++ b/src/apps/cli/.gitignore @@ -0,0 +1,4 @@ +.livesync +test/* +!test/*.sh +node_modules \ No newline at end of file diff --git a/src/apps/cli/README.md b/src/apps/cli/README.md new file mode 100644 index 0000000..b6dec8c --- /dev/null +++ b/src/apps/cli/README.md @@ -0,0 +1,162 @@ +# Self-hosted LiveSync CLI +Command-line version of Obsidian LiveSync plugin for syncing vaults without Obsidian. + +## Features + +- ✅ Sync Obsidian vaults using CouchDB without running Obsidian +- ✅ Compatible with Obsidian LiveSync plugin settings +- ✅ Supports all core sync features (encryption, conflict resolution, etc.) +- ✅ Lightweight and headless operation +- ✅ Cross-platform (Windows, macOS, Linux) + +## Architecture + +This CLI version is built using the same core as the Obsidian plugin: + +``` +CLI Main + └─ LiveSyncBaseCore + ├─ HeadlessServiceHub (All services without Obsidian dependencies) + └─ ServiceModules (Ported from main.ts) + ├─ FileAccessCLI (Node.js FileSystemAdapter) + ├─ StorageEventManagerCLI + ├─ ServiceFileAccessCLI + ├─ ServiceDatabaseFileAccess + ├─ ServiceFileHandler + └─ ServiceRebuilder +``` + +### Key Components + +1. **Node.js FileSystem Adapter** (`adapters/`) + - Platform-agnostic file operations using Node.js `fs/promises` + - Implements same interface as Obsidian's file system + +2. **CLI Storage Event Manager** (`managers/`) + - File-based snapshot persistence (JSON) + - Console-based status updates + - Optional file watching (can be extended with chokidar) + +3. **Service Modules** (`serviceModules/`) + - Direct port from `main.ts` `initialiseServiceModules` + - All core sync functionality preserved + +4. **Main Entry Point** (`main.ts`) + - Command-line interface + - Settings management (JSON file) + - Graceful shutdown handling + +## Installation + +```bash +# Install dependencies (ensure you are in repository root directory, not src/apps/cli) +# due to shared dependencies with webapp and main library +npm install +# Build the project (ensure you are in `src/apps/cli` directory) +npm run build +``` + +## Usage + +### Basic Usage + +As you know, the CLI is designed to be used in a headless environment. Hence all operations are performed against a local vault directory and a settings file. Here are some example commands: + +```bash +# Sync local database with CouchDB (no files will be changed). +node dist/cli/index.js /path/to/your-local-database --settings /path/to/settings.json sync + +# Push files to local database +node dist/cli/index.js /path/to/your-local-database --settings /path/to/settings.json push /your/storage/file.md /vault/path/file.md + +# Pull files from local database +node dist/cli/index.js /path/to/your-local-database --settings /path/to/settings.json pull /vault/path/file.md /your/storage/file.md + +# Verbose logging +node dist/cli/index.js /path/to/your-local-database --settings /path/to/settings.json --verbose +``` + +### Configuration + +The CLI uses the same settings format as the Obsidian plugin. Create a `.livesync/settings.json` file in your vault directory: + +```json +{ + "couchDB_URI": "http://localhost:5984", + "couchDB_USER": "admin", + "couchDB_PASSWORD": "password", + "couchDB_DBNAME": "obsidian-livesync", + "liveSync": true, + "syncOnSave": true, + "syncOnStart": true, + "encrypt": true, + "passphrase": "your-encryption-passphrase", + "usePluginSync": false, + "isConfigured": true +} +``` + +**Minimum required settings:** + +- `couchDB_URI`: CouchDB server URL +- `couchDB_USER`: CouchDB username +- `couchDB_PASSWORD`: CouchDB password +- `couchDB_DBNAME`: Database name +- `isConfigured`: Set to `true` after configuration + +### Command-line Options + +``` +Usage: + livesync-cli [database-path] [options] + +Arguments: + database-path Path to the local database directory (required) + +Options: + --settings, -s Path to settings file (default: .livesync/settings.json in local database directory) + --verbose, -v Enable verbose logging + --help, -h Show this help message + sync Sync local database with CouchDB or Bucket + push Push file to local database + pull Pull file from local database +``` + +### Planned options: + +- `put `: Add/update file in local database from standard input +- `cat `: Output file content to standard output +- `info `: Show file metadata, conflicts, and, other information +- `ls `: List files in local database with optional prefix filter +- `resolve `: Resolve conflict for a file by choosing a specific revision +- `rm `: Remove file from local database. +- `--immediate`: Perform sync after the command (e.g. `push`, `pull`, `put`, `rm`). +- `serve`: Start CLI in server mode, exposing REST APIs for remote, and batch operations. + +## Use Cases + +## Development + +### Project Structure + +``` +src/apps/cli/ +├── adapters/ # Node.js FileSystem Adapter +│ ├── NodeFileSystemAdapter.ts +│ ├── NodePathAdapter.ts +│ ├── NodeTypeGuardAdapter.ts +│ ├── NodeConversionAdapter.ts +│ ├── NodeStorageAdapter.ts +│ ├── NodeVaultAdapter.ts +│ └── NodeTypes.ts +├── managers/ # CLI-specific managers +│ ├── CLIStorageEventManagerAdapter.ts +│ └── StorageEventManagerCLI.ts +├── serviceModules/ # Service modules (ported from main.ts) +│ ├── CLIServiceModules.ts +│ ├── FileAccessCLI.ts +│ ├── ServiceFileAccessImpl.ts +│ └── DatabaseFileAccess.ts +├── main.ts # CLI entry point +└── README.md # This file +``` diff --git a/src/apps/cli/adapters/NodeConversionAdapter.ts b/src/apps/cli/adapters/NodeConversionAdapter.ts new file mode 100644 index 0000000..044abe2 --- /dev/null +++ b/src/apps/cli/adapters/NodeConversionAdapter.ts @@ -0,0 +1,28 @@ +import * as path from "path"; +import type { UXFileInfoStub, UXFolderInfo } from "@lib/common/types"; +import type { IConversionAdapter } from "@lib/serviceModules/adapters"; +import type { NodeFile, NodeFolder } from "./NodeTypes"; + +/** + * Conversion adapter implementation for Node.js + */ +export class NodeConversionAdapter implements IConversionAdapter { + nativeFileToUXFileInfoStub(file: NodeFile): UXFileInfoStub { + return { + name: path.basename(file.path), + path: file.path, + stat: file.stat, + isFolder: false, + }; + } + + nativeFolderToUXFolder(folder: NodeFolder): UXFolderInfo { + return { + name: path.basename(folder.path), + path: folder.path, + isFolder: true, + children: [], + parent: path.dirname(folder.path) as any, + }; + } +} diff --git a/src/apps/cli/adapters/NodeFileSystemAdapter.ts b/src/apps/cli/adapters/NodeFileSystemAdapter.ts new file mode 100644 index 0000000..b90ad73 --- /dev/null +++ b/src/apps/cli/adapters/NodeFileSystemAdapter.ts @@ -0,0 +1,153 @@ +import * as fs from "fs/promises"; +import * as path from "path"; +import type { FilePath, UXStat } from "@lib/common/types"; +import type { IFileSystemAdapter } from "@lib/serviceModules/adapters"; +import { NodePathAdapter } from "./NodePathAdapter"; +import { NodeTypeGuardAdapter } from "./NodeTypeGuardAdapter"; +import { NodeConversionAdapter } from "./NodeConversionAdapter"; +import { NodeStorageAdapter } from "./NodeStorageAdapter"; +import { NodeVaultAdapter } from "./NodeVaultAdapter"; +import type { NodeFile, NodeFolder, NodeStat } from "./NodeTypes"; + +/** + * Complete file system adapter implementation for Node.js + */ +export class NodeFileSystemAdapter implements IFileSystemAdapter { + readonly path: NodePathAdapter; + readonly typeGuard: NodeTypeGuardAdapter; + readonly conversion: NodeConversionAdapter; + readonly storage: NodeStorageAdapter; + readonly vault: NodeVaultAdapter; + + private fileCache = new Map(); + + constructor(private basePath: string) { + this.path = new NodePathAdapter(); + this.typeGuard = new NodeTypeGuardAdapter(); + this.conversion = new NodeConversionAdapter(); + this.storage = new NodeStorageAdapter(basePath); + this.vault = new NodeVaultAdapter(basePath); + } + + private resolvePath(p: FilePath | string): string { + return path.join(this.basePath, p); + } + + private normalisePath(p: FilePath | string): string { + return this.path.normalisePath(p as string); + } + + async getAbstractFileByPath(p: FilePath | string): Promise { + const pathStr = this.normalisePath(p); + + const cached = this.fileCache.get(pathStr); + if (cached) { + return cached; + } + + return await this.refreshFile(pathStr); + } + + async getAbstractFileByPathInsensitive(p: FilePath | string): Promise { + const pathStr = this.normalisePath(p); + + const exact = await this.getAbstractFileByPath(pathStr); + if (exact) { + return exact; + } + + const lowerPath = pathStr.toLowerCase(); + for (const [cachedPath, cachedFile] of this.fileCache.entries()) { + if (cachedPath.toLowerCase() === lowerPath) { + return cachedFile; + } + } + + await this.scanDirectory(); + + for (const [cachedPath, cachedFile] of this.fileCache.entries()) { + if (cachedPath.toLowerCase() === lowerPath) { + return cachedFile; + } + } + + return null; + } + + async getFiles(): Promise { + if (this.fileCache.size === 0) { + await this.scanDirectory(); + } + return Array.from(this.fileCache.values()); + } + + async statFromNative(file: NodeFile): Promise { + return file.stat; + } + + async reconcileInternalFile(p: string): Promise { + // No-op in Node.js version + // This is used by Obsidian to sync internal file metadata + } + + async refreshFile(p: string): Promise { + const pathStr = this.normalisePath(p); + try { + const fullPath = this.resolvePath(pathStr); + const stat = await fs.stat(fullPath); + if (!stat.isFile()) { + this.fileCache.delete(pathStr); + return null; + } + + const file: NodeFile = { + path: pathStr as FilePath, + stat: { + size: stat.size, + mtime: stat.mtimeMs, + ctime: stat.ctimeMs, + type: "file", + }, + }; + this.fileCache.set(pathStr, file); + return file; + } catch { + this.fileCache.delete(pathStr); + return null; + } + } + + /** + * Helper method to recursively scan directory and populate file cache + */ + async scanDirectory(relativePath: string = ""): Promise { + const fullPath = this.resolvePath(relativePath); + try { + const entries = await fs.readdir(fullPath, { withFileTypes: true }); + + for (const entry of entries) { + const entryRelativePath = path.join(relativePath, entry.name).replace(/\\/g, "/"); + + if (entry.isDirectory()) { + await this.scanDirectory(entryRelativePath); + } else if (entry.isFile()) { + const entryFullPath = this.resolvePath(entryRelativePath); + const stat = await fs.stat(entryFullPath); + const file: NodeFile = { + path: entryRelativePath as FilePath, + stat: { + size: stat.size, + mtime: stat.mtimeMs, + ctime: stat.ctimeMs, + type: "file", + }, + }; + this.fileCache.set(entryRelativePath, file); + } + } + } catch (error) { + // Directory doesn't exist or is not readable + console.error(`Error scanning directory ${fullPath}:`, error); + } + } +} diff --git a/src/apps/cli/adapters/NodePathAdapter.ts b/src/apps/cli/adapters/NodePathAdapter.ts new file mode 100644 index 0000000..fdad06a --- /dev/null +++ b/src/apps/cli/adapters/NodePathAdapter.ts @@ -0,0 +1,18 @@ +import * as path from "path"; +import type { FilePath } from "@lib/common/types"; +import type { IPathAdapter } from "@lib/serviceModules/adapters"; +import type { NodeFile } from "./NodeTypes"; + +/** + * Path adapter implementation for Node.js + */ +export class NodePathAdapter implements IPathAdapter { + getPath(file: string | NodeFile): FilePath { + return (typeof file === "string" ? file : file.path) as FilePath; + } + + normalisePath(p: string): string { + // Normalize path separators to forward slashes (like Obsidian) + return path.normalize(p).replace(/\\/g, "/"); + } +} diff --git a/src/apps/cli/adapters/NodeStorageAdapter.ts b/src/apps/cli/adapters/NodeStorageAdapter.ts new file mode 100644 index 0000000..d98b1c7 --- /dev/null +++ b/src/apps/cli/adapters/NodeStorageAdapter.ts @@ -0,0 +1,124 @@ +import * as fs from "fs/promises"; +import * as path from "path"; +import type { UXDataWriteOptions } from "@lib/common/types"; +import type { IStorageAdapter } from "@lib/serviceModules/adapters"; +import type { NodeStat } from "./NodeTypes"; + +/** + * Storage adapter implementation for Node.js + */ +export class NodeStorageAdapter implements IStorageAdapter { + constructor(private basePath: string) {} + + private resolvePath(p: string): string { + return path.join(this.basePath, p); + } + + async exists(p: string): Promise { + try { + await fs.access(this.resolvePath(p)); + return true; + } catch { + return false; + } + } + + async trystat(p: string): Promise { + try { + const stat = await fs.stat(this.resolvePath(p)); + return { + size: stat.size, + mtime: stat.mtimeMs, + ctime: stat.ctimeMs, + type: stat.isDirectory() ? "folder" : "file", + }; + } catch { + return null; + } + } + + async stat(p: string): Promise { + return await this.trystat(p); + } + + async mkdir(p: string): Promise { + await fs.mkdir(this.resolvePath(p), { recursive: true }); + } + + async remove(p: string): Promise { + const fullPath = this.resolvePath(p); + const stat = await fs.stat(fullPath); + if (stat.isDirectory()) { + await fs.rm(fullPath, { recursive: true, force: true }); + } else { + await fs.unlink(fullPath); + } + } + + async read(p: string): Promise { + return await fs.readFile(this.resolvePath(p), "utf-8"); + } + + async readBinary(p: string): Promise { + const buffer = await fs.readFile(this.resolvePath(p)); + return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) as ArrayBuffer; + } + + async write(p: string, data: string, options?: UXDataWriteOptions): Promise { + const fullPath = this.resolvePath(p); + await fs.mkdir(path.dirname(fullPath), { recursive: true }); + await fs.writeFile(fullPath, data, "utf-8"); + + if (options?.mtime || options?.ctime) { + const atime = options.mtime ? new Date(options.mtime) : new Date(); + const mtime = options.mtime ? new Date(options.mtime) : new Date(); + await fs.utimes(fullPath, atime, mtime); + } + } + + async writeBinary(p: string, data: ArrayBuffer, options?: UXDataWriteOptions): Promise { + const fullPath = this.resolvePath(p); + await fs.mkdir(path.dirname(fullPath), { recursive: true }); + await fs.writeFile(fullPath, new Uint8Array(data)); + + if (options?.mtime || options?.ctime) { + const atime = options.mtime ? new Date(options.mtime) : new Date(); + const mtime = options.mtime ? new Date(options.mtime) : new Date(); + await fs.utimes(fullPath, atime, mtime); + } + } + + async append(p: string, data: string, options?: UXDataWriteOptions): Promise { + const fullPath = this.resolvePath(p); + await fs.mkdir(path.dirname(fullPath), { recursive: true }); + await fs.appendFile(fullPath, data, "utf-8"); + + if (options?.mtime || options?.ctime) { + const atime = options.mtime ? new Date(options.mtime) : new Date(); + const mtime = options.mtime ? new Date(options.mtime) : new Date(); + await fs.utimes(fullPath, atime, mtime); + } + } + + async list(basePath: string): Promise<{ files: string[]; folders: string[] }> { + const fullPath = this.resolvePath(basePath); + try { + const entries = await fs.readdir(fullPath, { withFileTypes: true }); + const files: string[] = []; + const folders: string[] = []; + + for (const entry of entries) { + const entryPath = path.join(basePath, entry.name).replace(/\\/g, "/"); + if (entry.isDirectory()) { + folders.push(entryPath); + } else if (entry.isFile()) { + files.push(entryPath); + } + } + + return { files, folders }; + } catch { + return { files: [], folders: [] }; + } + } +} diff --git a/src/apps/cli/adapters/NodeTypeGuardAdapter.ts b/src/apps/cli/adapters/NodeTypeGuardAdapter.ts new file mode 100644 index 0000000..8f1e970 --- /dev/null +++ b/src/apps/cli/adapters/NodeTypeGuardAdapter.ts @@ -0,0 +1,15 @@ +import type { ITypeGuardAdapter } from "@lib/serviceModules/adapters"; +import type { NodeFile, NodeFolder } from "./NodeTypes"; + +/** + * Type guard adapter implementation for Node.js + */ +export class NodeTypeGuardAdapter implements ITypeGuardAdapter { + isFile(file: any): file is NodeFile { + return file && typeof file === "object" && "path" in file && "stat" in file && !file.isFolder; + } + + isFolder(item: any): item is NodeFolder { + return item && typeof item === "object" && "path" in item && item.isFolder === true; + } +} diff --git a/src/apps/cli/adapters/NodeTypes.ts b/src/apps/cli/adapters/NodeTypes.ts new file mode 100644 index 0000000..ca083c5 --- /dev/null +++ b/src/apps/cli/adapters/NodeTypes.ts @@ -0,0 +1,22 @@ +import type { FilePath, UXStat } from "@lib/common/types"; + +/** + * Node.js file representation + */ +export type NodeFile = { + path: FilePath; + stat: UXStat; +}; + +/** + * Node.js folder representation + */ +export type NodeFolder = { + path: FilePath; + isFolder: true; +}; + +/** + * Node.js stat type (compatible with UXStat) + */ +export type NodeStat = UXStat; diff --git a/src/apps/cli/adapters/NodeVaultAdapter.ts b/src/apps/cli/adapters/NodeVaultAdapter.ts new file mode 100644 index 0000000..947ad01 --- /dev/null +++ b/src/apps/cli/adapters/NodeVaultAdapter.ts @@ -0,0 +1,118 @@ +import * as fs from "fs/promises"; +import * as path from "path"; +import type { UXDataWriteOptions } from "@lib/common/types"; +import type { IVaultAdapter } from "@lib/serviceModules/adapters"; +import type { NodeFile, NodeFolder, NodeStat } from "./NodeTypes"; + +/** + * Vault adapter implementation for Node.js + */ +export class NodeVaultAdapter implements IVaultAdapter { + constructor(private basePath: string) {} + + private resolvePath(p: string): string { + return path.join(this.basePath, p); + } + + async read(file: NodeFile): Promise { + return await fs.readFile(this.resolvePath(file.path), "utf-8"); + } + + async cachedRead(file: NodeFile): Promise { + // No caching in CLI version, just read directly + return await this.read(file); + } + + async readBinary(file: NodeFile): Promise { + const buffer = await fs.readFile(this.resolvePath(file.path)); + return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) as ArrayBuffer; + } + + async modify(file: NodeFile, data: string, options?: UXDataWriteOptions): Promise { + const fullPath = this.resolvePath(file.path); + await fs.writeFile(fullPath, data, "utf-8"); + + if (options?.mtime || options?.ctime) { + const atime = options.mtime ? new Date(options.mtime) : new Date(); + const mtime = options.mtime ? new Date(options.mtime) : new Date(); + await fs.utimes(fullPath, atime, mtime); + } + } + + async modifyBinary(file: NodeFile, data: ArrayBuffer, options?: UXDataWriteOptions): Promise { + const fullPath = this.resolvePath(file.path); + await fs.writeFile(fullPath, new Uint8Array(data)); + + if (options?.mtime || options?.ctime) { + const atime = options.mtime ? new Date(options.mtime) : new Date(); + const mtime = options.mtime ? new Date(options.mtime) : new Date(); + await fs.utimes(fullPath, atime, mtime); + } + } + + async create(p: string, data: string, options?: UXDataWriteOptions): Promise { + const fullPath = this.resolvePath(p); + await fs.mkdir(path.dirname(fullPath), { recursive: true }); + await fs.writeFile(fullPath, data, "utf-8"); + + if (options?.mtime || options?.ctime) { + const atime = options.mtime ? new Date(options.mtime) : new Date(); + const mtime = options.mtime ? new Date(options.mtime) : new Date(); + await fs.utimes(fullPath, atime, mtime); + } + + const stat = await fs.stat(fullPath); + return { + path: p as any, + stat: { + size: stat.size, + mtime: stat.mtimeMs, + ctime: stat.ctimeMs, + type: "file", + }, + }; + } + + async createBinary(p: string, data: ArrayBuffer, options?: UXDataWriteOptions): Promise { + const fullPath = this.resolvePath(p); + await fs.mkdir(path.dirname(fullPath), { recursive: true }); + await fs.writeFile(fullPath, new Uint8Array(data)); + + if (options?.mtime || options?.ctime) { + const atime = options.mtime ? new Date(options.mtime) : new Date(); + const mtime = options.mtime ? new Date(options.mtime) : new Date(); + await fs.utimes(fullPath, atime, mtime); + } + + const stat = await fs.stat(fullPath); + return { + path: p as any, + stat: { + size: stat.size, + mtime: stat.mtimeMs, + ctime: stat.ctimeMs, + type: "file", + }, + }; + } + + async delete(file: NodeFile | NodeFolder, force = false): Promise { + const fullPath = this.resolvePath(file.path); + const stat = await fs.stat(fullPath); + if (stat.isDirectory()) { + await fs.rm(fullPath, { recursive: true, force }); + } else { + await fs.unlink(fullPath); + } + } + + async trash(file: NodeFile | NodeFolder, force = false): Promise { + // In CLI, trash is the same as delete (no recycle bin) + await this.delete(file, force); + } + + trigger(name: string, ...data: any[]): any { + // No-op in CLI version (no event system) + return undefined; + } +} diff --git a/src/apps/cli/lib/pouchdb-node.ts b/src/apps/cli/lib/pouchdb-node.ts new file mode 100644 index 0000000..137644e --- /dev/null +++ b/src/apps/cli/lib/pouchdb-node.ts @@ -0,0 +1,134 @@ +import PouchDB from "pouchdb-core"; + +import HttpPouch from "pouchdb-adapter-http"; +import mapreduce from "pouchdb-mapreduce"; +import replication from "pouchdb-replication"; + +import LevelDBAdapter from "pouchdb-adapter-leveldb"; + +import find from "pouchdb-find"; +import transform from "transform-pouch"; +//@ts-ignore +import { findPathToLeaf } from "pouchdb-merge"; +//@ts-ignore +import { adapterFun } from "pouchdb-utils"; +//@ts-ignore +import { createError, MISSING_DOC, UNKNOWN_ERROR } from "pouchdb-errors"; +import { mapAllTasksWithConcurrencyLimit, unwrapTaskResult } from "octagonal-wheels/concurrency/task"; + +PouchDB.plugin(LevelDBAdapter).plugin(HttpPouch).plugin(mapreduce).plugin(replication).plugin(find).plugin(transform); + +type PurgeMultiResult = { + ok: true; + deletedRevs: string[]; + documentWasRemovedCompletely: boolean; +}; +type PurgeMultiParam = [docId: string, rev$$1: string]; +function appendPurgeSeqs(db: PouchDB.Database, docs: PurgeMultiParam[]) { + return db + .get("_local/purges") + .then(function (doc: any) { + for (const [docId, rev$$1] of docs) { + const purgeSeq = doc.purgeSeq + 1; + doc.purges.push({ + docId, + rev: rev$$1, + purgeSeq, + }); + //@ts-ignore : missing type def + if (doc.purges.length > db.purged_infos_limit) { + //@ts-ignore : missing type def + doc.purges.splice(0, doc.purges.length - db.purged_infos_limit); + } + doc.purgeSeq = purgeSeq; + } + return doc; + }) + .catch(function (err) { + if (err.status !== 404) { + throw err; + } + return { + _id: "_local/purges", + purges: docs.map(([docId, rev$$1], idx) => ({ + docId, + rev: rev$$1, + purgeSeq: idx, + })), + purgeSeq: docs.length, + }; + }) + .then(function (doc) { + return db.put(doc); + }); +} + +/** + * purge multiple documents at once. + */ +PouchDB.prototype.purgeMulti = adapterFun( + "_purgeMulti", + function ( + docs: PurgeMultiParam[], + callback: ( + error: Error, + result?: { + [x: string]: PurgeMultiResult | Error; + } + ) => void + ) { + //@ts-ignore + if (typeof this._purge === "undefined") { + return callback( + //@ts-ignore: this ts-ignore might be hiding a `this` bug where we don't have "this" conext. + createError(UNKNOWN_ERROR, "Purge is not implemented in the " + this.adapter + " adapter.") + ); + } + //@ts-ignore + // eslint-disable-next-line @typescript-eslint/no-this-alias + const self = this; + const tasks = docs.map( + (param) => () => + new Promise<[PurgeMultiParam, PurgeMultiResult | Error]>((res, rej) => { + const [docId, rev$$1] = param; + self._getRevisionTree(docId, (error: Error, revs: string[]) => { + if (error) { + return res([param, error]); + } + if (!revs) { + return res([param, createError(MISSING_DOC)]); + } + let path; + try { + path = findPathToLeaf(revs, rev$$1); + } catch (error) { + //@ts-ignore + return res([param, error.message || error]); + } + self._purge(docId, path, (error: Error, result: PurgeMultiResult) => { + if (error) { + return res([param, error]); + } else { + return res([param, result]); + } + }); + }); + }) + ); + (async () => { + const ret = await mapAllTasksWithConcurrencyLimit(1, tasks); + const retAll = ret.map((e) => unwrapTaskResult(e)) as [PurgeMultiParam, PurgeMultiResult | Error][]; + await appendPurgeSeqs( + self, + retAll.filter((e) => "ok" in e[1]).map((e) => e[0]) + ); + const result = Object.fromEntries(retAll.map((e) => [e[0][0], e[1]])); + return result; + })() + //@ts-ignore + .then((result) => callback(undefined, result)) + .catch((error) => callback(error)); + } +); + +export { PouchDB }; diff --git a/src/apps/cli/main.ts b/src/apps/cli/main.ts new file mode 100644 index 0000000..649f245 --- /dev/null +++ b/src/apps/cli/main.ts @@ -0,0 +1,452 @@ +#!/usr/bin/env node +/** + * Self-hosted LiveSync CLI + * Command-line version of Obsidian LiveSync plugin for syncing vaults without Obsidian + */ + +if (!("localStorage" in globalThis)) { + const store = new Map(); + (globalThis as any).localStorage = { + getItem: (key: string) => (store.has(key) ? store.get(key)! : null), + setItem: (key: string, value: string) => { + store.set(key, value); + }, + removeItem: (key: string) => { + store.delete(key); + }, + clear: () => { + store.clear(); + }, + }; +} + +import * as fs from "fs/promises"; +import * as path from "path"; +import { NodeServiceContext, NodeServiceHub } from "./services/NodeServiceHub"; +import { LiveSyncBaseCore } from "../../LiveSyncBaseCore"; +import { ServiceContext } from "@lib/services/base/ServiceBase"; +import { initialiseServiceModulesCLI } from "./serviceModules/CLIServiceModules"; +import { + DEFAULT_SETTINGS, + LOG_LEVEL_VERBOSE, + type LOG_LEVEL, + type ObsidianLiveSyncSettings, + type FilePathWithPrefix, +} from "@lib/common/types"; +import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub"; +import type { InjectableSettingService } from "@/lib/src/services/implements/injectable/InjectableSettingService"; +import { LOG_LEVEL_DEBUG, setGlobalLogFunction, defaultLoggerEnv } from "octagonal-wheels/common/logger"; +import PouchDb from "pouchdb-core"; + +const SETTINGS_FILE = ".livesync/settings.json"; +const VALID_COMMANDS = new Set(["sync", "push", "pull", "init-settings"] as const); + +type CLICommand = "daemon" | "sync" | "push" | "pull" | "init-settings"; +defaultLoggerEnv.minLogLevel = LOG_LEVEL_DEBUG; +// DI the log again. +// const recentLogEntries = reactiveSource([]); +// const globalLogFunction = (message: any, level?: number, key?: string) => { +// const messageX = +// message instanceof Error +// ? new LiveSyncError("[Error Logged]: " + message.message, { cause: message }) +// : message; +// const entry = { message: messageX, level, key } as LogEntry; +// recentLogEntries.value = [...recentLogEntries.value, entry]; +// }; + +setGlobalLogFunction((msg, level) => { + console.log(`[${level}] ${typeof msg === "string" ? msg : JSON.stringify(msg)}`); + if (msg instanceof Error) { + console.error(msg); + } +}); +interface CLIOptions { + databasePath?: string; + settingsPath?: string; + verbose?: boolean; + force?: boolean; + command: CLICommand; + commandArgs: string[]; +} + +function printHelp(): void { + console.log(` +Self-hosted LiveSync CLI + +Usage: + livesync-cli [database-path] [options] [command] [command-args] + +Arguments: + database-path Path to the local database directory (required) + +Commands: + sync Run one replication cycle and exit + push Push local file into local database path + pull Pull file from local database into local file + init-settings [path] Create settings JSON from DEFAULT_SETTINGS + +Options: + --settings, -s Path to settings file (default: .livesync/settings.json in local database directory) + --force, -f Overwrite existing file on init-settings + --verbose, -v Enable verbose logging + --help, -h Show this help message + +Examples: + livesync-cli ./my-database sync + 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 init-settings ./data.json + livesync-cli ./my-database --verbose + `); +} + +function parseArgs(): CLIOptions { + const args = process.argv.slice(2); + + if (args.length === 0 || args.includes("--help") || args.includes("-h")) { + printHelp(); + process.exit(0); + } + + let databasePath: string | undefined; + let settingsPath: string | undefined; + let verbose = false; + let force = false; + let command: CLICommand = "daemon"; + const commandArgs: string[] = []; + + for (let i = 0; i < args.length; i++) { + const token = args[i]; + switch (token) { + case "--settings": + case "-s": { + i++; + if (!args[i]) { + console.error(`Error: Missing value for ${token}`); + process.exit(1); + } + settingsPath = args[i]; + break; + } + case "--verbose": + case "-v": + verbose = true; + break; + case "--force": + case "-f": + force = true; + break; + default: { + if (!databasePath) { + if (command === "daemon" && VALID_COMMANDS.has(token as any)) { + command = token as CLICommand; + break; + } + if (command === "init-settings") { + commandArgs.push(token); + break; + } + databasePath = token; + break; + } + if (command === "daemon" && VALID_COMMANDS.has(token as any)) { + command = token as CLICommand; + break; + } + commandArgs.push(token); + break; + } + } + } + + if (!databasePath && command !== "init-settings") { + console.error("Error: database-path is required"); + process.exit(1); + } + + return { + databasePath, + settingsPath, + verbose, + force, + command, + commandArgs, + }; +} + +async function createDefaultSettingsFile(options: CLIOptions) { + const targetPath = options.settingsPath + ? path.resolve(options.settingsPath) + : options.commandArgs[0] + ? path.resolve(options.commandArgs[0]) + : path.resolve(process.cwd(), "data.json"); + + if (!options.force) { + try { + await fs.stat(targetPath); + throw new Error(`Settings file already exists: ${targetPath} (use --force to overwrite)`); + } catch (ex: any) { + if (!(ex && ex?.code === "ENOENT")) { + throw ex; + } + } + } + + const settings = { + ...DEFAULT_SETTINGS, + useIndexedDBAdapter: false, + } as ObsidianLiveSyncSettings; + + await fs.mkdir(path.dirname(targetPath), { recursive: true }); + await fs.writeFile(targetPath, JSON.stringify(settings, null, 2), "utf-8"); + console.log(`[Done] Created settings file: ${targetPath}`); +} + +function toArrayBuffer(data: Buffer): ArrayBuffer { + return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer; +} + +function toVaultRelativePath(inputPath: string, vaultPath: string): string { + const stripped = inputPath.replace(/^[/\\]+/, ""); + if (!path.isAbsolute(inputPath)) { + return stripped.replace(/\\/g, "/"); + } + const resolved = path.resolve(inputPath); + const rel = path.relative(vaultPath, resolved); + if (rel.startsWith("..") || path.isAbsolute(rel)) { + throw new Error(`Path ${inputPath} is outside of the local database directory`); + } + return rel.replace(/\\/g, "/"); +} + +async function runCommand( + options: CLIOptions, + vaultPath: string, + core: LiveSyncBaseCore +): Promise { + await core.services.control.activated; + if (options.command === "daemon") { + return true; + } + + if (options.command === "sync") { + console.log("[Command] sync"); + const result = await core.services.replication.replicate(true); + return !!result; + } + + if (options.command === "push") { + if (options.commandArgs.length < 2) { + throw new Error("push requires two arguments: "); + } + const sourcePath = path.resolve(options.commandArgs[0]); + const destinationVaultPath = toVaultRelativePath(options.commandArgs[1], vaultPath); + const sourceData = await fs.readFile(sourcePath); + const sourceStat = await fs.stat(sourcePath); + console.log(`[Command] push ${sourcePath} -> ${destinationVaultPath}`); + + await core.serviceModules.storageAccess.writeFileAuto(destinationVaultPath, toArrayBuffer(sourceData), { + mtime: sourceStat.mtimeMs, + ctime: sourceStat.ctimeMs, + }); + const destinationPathWithPrefix = destinationVaultPath as FilePathWithPrefix; + const stored = await core.serviceModules.fileHandler.storeFileToDB(destinationPathWithPrefix, true); + return stored; + } + + if (options.command === "pull") { + if (options.commandArgs.length < 2) { + throw new Error("pull requires two arguments: "); + } + const sourceVaultPath = toVaultRelativePath(options.commandArgs[0], vaultPath); + const destinationPath = path.resolve(options.commandArgs[1]); + console.log(`[Command] pull ${sourceVaultPath} -> ${destinationPath}`); + + const sourcePathWithPrefix = sourceVaultPath as FilePathWithPrefix; + const restored = await core.serviceModules.fileHandler.dbToStorage(sourcePathWithPrefix, null, true); + if (!restored) { + return false; + } + const data = await core.serviceModules.storageAccess.readFileAuto(sourceVaultPath); + await fs.mkdir(path.dirname(destinationPath), { recursive: true }); + if (typeof data === "string") { + await fs.writeFile(destinationPath, data, "utf-8"); + } else { + await fs.writeFile(destinationPath, new Uint8Array(data)); + } + return true; + } + + throw new Error(`Unsupported command: ${options.command}`); +} + +async function main() { + const options = parseArgs(); + + if (options.command === "init-settings") { + await createDefaultSettingsFile(options); + return; + } + + // Resolve vault path + const vaultPath = path.resolve(options.databasePath!); + // Check if vault directory exists + try { + const stat = await fs.stat(vaultPath); + if (!stat.isDirectory()) { + console.error(`Error: ${vaultPath} is not a directory`); + process.exit(1); + } + } catch (error) { + console.error(`Error: Vault directory ${vaultPath} does not exist`); + process.exit(1); + } + + // Resolve settings path + const settingsPath = options.settingsPath + ? path.resolve(options.settingsPath) + : path.join(vaultPath, SETTINGS_FILE); + + console.log(`Self-hosted LiveSync CLI`); + console.log(`Vault: ${vaultPath}`); + console.log(`Settings: ${settingsPath}`); + console.log(); + + // Create service context and hub + const context = new NodeServiceContext(vaultPath); + const serviceHubInstance = new NodeServiceHub(vaultPath, context); + serviceHubInstance.API.addLog.setHandler((message: string, level: LOG_LEVEL) => { + const prefix = `[${level}]`; + if (level <= LOG_LEVEL_VERBOSE) { + if (!options.verbose) return; + } + console.log(`${prefix} ${message}`); + }); + // Prevent replication result to be processed automatically. + serviceHubInstance.replication.processSynchroniseResult.addHandler(async () => { + console.log(`[Info] Replication result received, but not processed automatically in CLI mode.`); + return await Promise.resolve(true); + }, -100); + // Setup settings handlers + const settingService = serviceHubInstance.setting; + + (settingService as InjectableSettingService).saveData.setHandler( + async (data: ObsidianLiveSyncSettings) => { + try { + await fs.writeFile(settingsPath, JSON.stringify(data, null, 2), "utf-8"); + if (options.verbose) { + console.log(`[Settings] Saved to ${settingsPath}`); + } + } catch (error) { + console.error(`[Settings] Failed to save:`, error); + } + } + ); + + (settingService as InjectableSettingService).loadData.setHandler( + async (): Promise => { + try { + const content = await fs.readFile(settingsPath, "utf-8"); + const data = JSON.parse(content); + if (options.verbose) { + console.log(`[Settings] Loaded from ${settingsPath}`); + } + // Force disable IndexedDB adapter in CLI environment + data.useIndexedDBAdapter = false; + return data; + } catch (error) { + if (options.verbose) { + console.log(`[Settings] File not found, using defaults`); + } + return undefined; + } + } + ); + + // Create LiveSync core + const core = new LiveSyncBaseCore( + serviceHubInstance, + (core: LiveSyncBaseCore, serviceHub: InjectableServiceHub) => { + return initialiseServiceModulesCLI(vaultPath, core, serviceHub); + }, + () => [], // No extra modules + () => [], // No add-ons + () => [] // No serviceFeatures + ); + + // Setup signal handlers for graceful shutdown + const shutdown = async (signal: string) => { + console.log(); + console.log(`[Shutdown] Received ${signal}, shutting down gracefully...`); + try { + await core.services.control.onUnload(); + console.log(`[Shutdown] Complete`); + process.exit(0); + } catch (error) { + console.error(`[Shutdown] Error:`, error); + process.exit(1); + } + }; + + process.on("SIGINT", () => shutdown("SIGINT")); + process.on("SIGTERM", () => shutdown("SIGTERM")); + + // Start the core + try { + console.log(`[Starting] Initializing LiveSync...`); + + const loadResult = await core.services.control.onLoad(); + if (!loadResult) { + console.error(`[Error] Failed to initialize LiveSync`); + process.exit(1); + } + + await core.services.control.onReady(); + + console.log(`[Ready] LiveSync is running`); + console.log(`[Ready] Press Ctrl+C to stop`); + console.log(); + + // Check if configured + const settings = core.services.setting.currentSettings(); + if (!settings.isConfigured) { + console.warn(`[Warning] LiveSync is not configured yet`); + console.warn(`[Warning] Please edit ${settingsPath} to configure CouchDB connection`); + console.warn(); + console.warn(`Required settings:`); + console.warn(` - couchDB_URI: CouchDB server URL`); + console.warn(` - couchDB_USER: CouchDB username`); + console.warn(` - couchDB_PASSWORD: CouchDB password`); + console.warn(` - couchDB_DBNAME: Database name`); + console.warn(); + } else { + console.log(`[Info] LiveSync is configured and ready`); + console.log(`[Info] Database: ${settings.couchDB_URI}/${settings.couchDB_DBNAME}`); + console.log(); + } + + const result = await runCommand(options, vaultPath, core); + if (!result) { + console.error(`[Error] Command '${options.command}' failed`); + process.exitCode = 1; + } else if (options.command !== "daemon") { + console.log(`[Done] Command '${options.command}' completed`); + } + + if (options.command === "daemon") { + // Keep the process running + await new Promise(() => {}); + } else { + await core.services.control.onUnload(); + } + } catch (error) { + console.error(`[Error] Failed to start:`, error); + process.exit(1); + } +} + +// Run main +main().catch((error) => { + console.error(`[Fatal Error]`, error); + process.exit(1); +}); diff --git a/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts b/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts new file mode 100644 index 0000000..5cb8509 --- /dev/null +++ b/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts @@ -0,0 +1,133 @@ +import type { FilePath, UXFileInfoStub, UXInternalFileInfoStub } from "@lib/common/types"; +import type { FileEventItem } from "@lib/common/types"; +import type { IStorageEventManagerAdapter } from "@lib/managers/adapters"; +import type { + IStorageEventTypeGuardAdapter, + IStorageEventPersistenceAdapter, + IStorageEventWatchAdapter, + IStorageEventStatusAdapter, + IStorageEventConverterAdapter, + IStorageEventWatchHandlers, +} from "@lib/managers/adapters"; +import type { FileEventItemSentinel } from "@lib/managers/StorageEventManager"; +import type { NodeFile, NodeFolder } from "../adapters/NodeTypes"; +import * as fs from "fs/promises"; +import * as path from "path"; + +/** + * CLI-specific type guard adapter + */ +class CLITypeGuardAdapter implements IStorageEventTypeGuardAdapter { + isFile(file: any): file is NodeFile { + return file && typeof file === "object" && "path" in file && "stat" in file && !file.isFolder; + } + + isFolder(item: any): item is NodeFolder { + return item && typeof item === "object" && "path" in item && item.isFolder === true; + } +} + +/** + * CLI-specific persistence adapter (file-based snapshot) + */ +class CLIPersistenceAdapter implements IStorageEventPersistenceAdapter { + private snapshotPath: string; + + constructor(basePath: string) { + this.snapshotPath = path.join(basePath, ".livesync-snapshot.json"); + } + + async saveSnapshot(snapshot: (FileEventItem | FileEventItemSentinel)[]): Promise { + try { + await fs.writeFile(this.snapshotPath, JSON.stringify(snapshot, null, 2), "utf-8"); + } catch (error) { + console.error("Failed to save snapshot:", error); + } + } + + async loadSnapshot(): Promise<(FileEventItem | FileEventItemSentinel)[] | null> { + try { + const content = await fs.readFile(this.snapshotPath, "utf-8"); + return JSON.parse(content); + } catch { + return null; + } + } +} + +/** + * CLI-specific status adapter (console logging) + */ +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; + } + } +} + +/** + * CLI-specific converter adapter + */ +class CLIConverterAdapter implements IStorageEventConverterAdapter { + toFileInfo(file: NodeFile, deleted?: boolean): UXFileInfoStub { + return { + name: path.basename(file.path), + path: file.path, + stat: file.stat, + deleted: deleted, + isFolder: false, + }; + } + + toInternalFileInfo(p: FilePath): UXInternalFileInfoStub { + return { + name: path.basename(p), + path: p, + isInternal: true, + stat: undefined, + }; + } +} + +/** + * CLI-specific watch adapter (optional file watching with chokidar) + */ +class CLIWatchAdapter implements IStorageEventWatchAdapter { + constructor(private basePath: string) {} + + 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.log("[CLIWatchAdapter] File watching is not enabled in CLI version"); + return Promise.resolve(); + } +} + +/** + * Composite adapter for CLI StorageEventManager + */ +export class CLIStorageEventManagerAdapter implements IStorageEventManagerAdapter { + readonly typeGuard: CLITypeGuardAdapter; + readonly persistence: CLIPersistenceAdapter; + readonly watch: CLIWatchAdapter; + readonly status: CLIStatusAdapter; + readonly converter: CLIConverterAdapter; + + constructor(basePath: string) { + this.typeGuard = new CLITypeGuardAdapter(); + this.persistence = new CLIPersistenceAdapter(basePath); + this.watch = new CLIWatchAdapter(basePath); + this.status = new CLIStatusAdapter(); + this.converter = new CLIConverterAdapter(); + } +} diff --git a/src/apps/cli/managers/StorageEventManagerCLI.ts b/src/apps/cli/managers/StorageEventManagerCLI.ts new file mode 100644 index 0000000..d1f2504 --- /dev/null +++ b/src/apps/cli/managers/StorageEventManagerCLI.ts @@ -0,0 +1,28 @@ +import { StorageEventManagerBase, type StorageEventManagerBaseDependencies } from "@lib/managers/StorageEventManager"; +import { CLIStorageEventManagerAdapter } from "./CLIStorageEventManagerAdapter"; +import type { IMinimumLiveSyncCommands, LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; +import type { ServiceContext } from "@lib/services/base/ServiceBase"; +// import type { IMinimumLiveSyncCommands } from "@lib/services/base/IService"; + +export class StorageEventManagerCLI extends StorageEventManagerBase { + core: LiveSyncBaseCore; + + constructor( + basePath: string, + core: LiveSyncBaseCore, + dependencies: StorageEventManagerBaseDependencies + ) { + const adapter = new CLIStorageEventManagerAdapter(basePath); + super(adapter, dependencies); + this.core = core; + } + + /** + * Override _watchVaultRawEvents for CLI-specific logic + * In CLI, we don't have internal files like Obsidian's .obsidian folder + */ + protected override async _watchVaultRawEvents(path: string) { + // No-op in CLI version + // Internal file handling is not needed + } +} diff --git a/src/apps/cli/package.json b/src/apps/cli/package.json new file mode 100644 index 0000000..ddfe3ef --- /dev/null +++ b/src/apps/cli/package.json @@ -0,0 +1,16 @@ +{ + "name": "self-hosted-livesync-cli", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "run": "node dist/index.cjs", + "buildRun": "npm run build && npm run", + "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json" + }, + "dependencies": {}, + "devDependencies": {} +} diff --git a/src/apps/cli/serviceModules/CLIServiceModules.ts b/src/apps/cli/serviceModules/CLIServiceModules.ts new file mode 100644 index 0000000..8cf0f40 --- /dev/null +++ b/src/apps/cli/serviceModules/CLIServiceModules.ts @@ -0,0 +1,104 @@ +import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub"; +import { ServiceRebuilder } from "@lib/serviceModules/Rebuilder"; +import { ServiceFileHandler } from "../../../serviceModules/FileHandler"; +import { StorageAccessManager } from "@lib/managers/StorageProcessingManager"; +import type { LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; +import type { ServiceContext } from "@lib/services/base/ServiceBase"; +import { FileAccessCLI } from "./FileAccessCLI"; +import { ServiceFileAccessCLI } from "./ServiceFileAccessImpl"; +import { ServiceDatabaseFileAccessCLI } from "./DatabaseFileAccess"; +import { StorageEventManagerCLI } from "../managers/StorageEventManagerCLI"; +import type { ServiceModules } from "@lib/interfaces/ServiceModule"; + +/** + * Initialize service modules for CLI version + * This is the CLI equivalent of ObsidianLiveSyncPlugin.initialiseServiceModules + * + * @param basePath - The base path of the vault directory + * @param core - The LiveSyncBaseCore instance + * @param services - The service hub + * @returns ServiceModules containing all initialized service modules + */ +export function initialiseServiceModulesCLI( + basePath: string, + core: LiveSyncBaseCore, + services: InjectableServiceHub +): ServiceModules { + const storageAccessManager = new StorageAccessManager(); + + // CLI-specific file access using Node.js FileSystemAdapter + const vaultAccess = new FileAccessCLI(basePath, { + storageAccessManager: storageAccessManager, + vaultService: services.vault, + settingService: services.setting, + APIService: services.API, + pathService: services.path, + }); + + // 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, + }); + + // Storage access using CLI file system adapter + const storageAccess = new ServiceFileAccessCLI({ + API: services.API, + setting: services.setting, + fileProcessing: services.fileProcessing, + vault: services.vault, + appLifecycle: services.appLifecycle, + storageEventManager: storageEventManager, + storageAccessManager: storageAccessManager, + vaultAccess: vaultAccess, + }); + + // Database file access (platform-independent) + const databaseFileAccess = new ServiceDatabaseFileAccessCLI({ + API: services.API, + database: services.database, + path: services.path, + storageAccess: storageAccess, + vault: services.vault, + }); + + // File handler (platform-independent) + const fileHandler = new (ServiceFileHandler as any)({ + API: services.API, + databaseFileAccess: databaseFileAccess, + conflict: services.conflict, + setting: services.setting, + fileProcessing: services.fileProcessing, + vault: services.vault, + path: services.path, + replication: services.replication, + storageAccess: storageAccess, + }); + + // Rebuilder (platform-independent) + const rebuilder = new ServiceRebuilder({ + API: services.API, + database: services.database, + appLifecycle: services.appLifecycle, + setting: services.setting, + remote: services.remote, + databaseEvents: services.databaseEvents, + replication: services.replication, + replicator: services.replicator, + UI: services.UI, + vault: services.vault, + fileHandler: fileHandler, + storageAccess: storageAccess, + control: services.control, + }); + + return { + rebuilder, + fileHandler, + databaseFileAccess, + storageAccess, + }; +} diff --git a/src/apps/cli/serviceModules/DatabaseFileAccess.ts b/src/apps/cli/serviceModules/DatabaseFileAccess.ts new file mode 100644 index 0000000..583a9f5 --- /dev/null +++ b/src/apps/cli/serviceModules/DatabaseFileAccess.ts @@ -0,0 +1,15 @@ +import { + ServiceDatabaseFileAccessBase, + type ServiceDatabaseFileAccessDependencies, +} from "@lib/serviceModules/ServiceDatabaseFileAccessBase"; +import type { DatabaseFileAccess } from "@lib/interfaces/DatabaseFileAccess"; + +/** + * CLI-specific implementation of ServiceDatabaseFileAccess + * Same as Obsidian version, no platform-specific changes needed + */ +export class ServiceDatabaseFileAccessCLI extends ServiceDatabaseFileAccessBase implements DatabaseFileAccess { + constructor(services: ServiceDatabaseFileAccessDependencies) { + super(services); + } +} diff --git a/src/apps/cli/serviceModules/FileAccessCLI.ts b/src/apps/cli/serviceModules/FileAccessCLI.ts new file mode 100644 index 0000000..3aec6e0 --- /dev/null +++ b/src/apps/cli/serviceModules/FileAccessCLI.ts @@ -0,0 +1,20 @@ +import { FileAccessBase, type FileAccessBaseDependencies } from "@lib/serviceModules/FileAccessBase"; +import { NodeFileSystemAdapter } from "../adapters/NodeFileSystemAdapter"; + +/** + * CLI-specific implementation of FileAccessBase + * Uses NodeFileSystemAdapter for Node.js file operations + */ +export class FileAccessCLI extends FileAccessBase { + constructor(basePath: string, dependencies: FileAccessBaseDependencies) { + const adapter = new NodeFileSystemAdapter(basePath); + super(adapter, dependencies); + } + + /** + * Expose the adapter for accessing scanDirectory + */ + get nodeAdapter(): NodeFileSystemAdapter { + return this.adapter; + } +} diff --git a/src/apps/cli/serviceModules/ServiceFileAccessImpl.ts b/src/apps/cli/serviceModules/ServiceFileAccessImpl.ts new file mode 100644 index 0000000..718fc44 --- /dev/null +++ b/src/apps/cli/serviceModules/ServiceFileAccessImpl.ts @@ -0,0 +1,12 @@ +import { ServiceFileAccessBase, type StorageAccessBaseDependencies } from "@lib/serviceModules/ServiceFileAccessBase"; +import { NodeFileSystemAdapter } from "../adapters/NodeFileSystemAdapter"; + +/** + * CLI-specific implementation of ServiceFileAccess + * Uses NodeFileSystemAdapter for platform-specific operations + */ +export class ServiceFileAccessCLI extends ServiceFileAccessBase { + constructor(services: StorageAccessBaseDependencies) { + super(services); + } +} diff --git a/src/apps/cli/services/NodeKeyValueDBService.ts b/src/apps/cli/services/NodeKeyValueDBService.ts new file mode 100644 index 0000000..349cd4e --- /dev/null +++ b/src/apps/cli/services/NodeKeyValueDBService.ts @@ -0,0 +1,211 @@ +import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "@lib/common/logger"; +import type { KeyValueDatabase } from "@lib/interfaces/KeyValueDatabase"; +import type { IKeyValueDBService } from "@lib/services/base/IService"; +import { ServiceBase, type ServiceContext } from "@lib/services/base/ServiceBase"; +import type { InjectableAppLifecycleService } from "@lib/services/implements/injectable/InjectableAppLifecycleService"; +import type { InjectableDatabaseEventService } from "@lib/services/implements/injectable/InjectableDatabaseEventService"; +import type { IVaultService } from "@lib/services/base/IService"; +import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase"; +import { createInstanceLogFunction } from "@lib/services/lib/logUtils"; +import * as nodeFs from "node:fs"; +import * as nodePath from "node:path"; + +class NodeFileKeyValueDatabase implements KeyValueDatabase { + private filePath: string; + private data = new Map(); + + constructor(filePath: string) { + this.filePath = filePath; + this.load(); + } + + private asKeyString(key: IDBValidKey): string { + if (typeof key === "string") { + return key; + } + return JSON.stringify(key); + } + + private load() { + try { + const loaded = JSON.parse(nodeFs.readFileSync(this.filePath, "utf-8")) as Record; + this.data = new Map(Object.entries(loaded)); + } catch { + this.data = new Map(); + } + } + + private flush() { + nodeFs.mkdirSync(nodePath.dirname(this.filePath), { recursive: true }); + nodeFs.writeFileSync(this.filePath, JSON.stringify(Object.fromEntries(this.data), null, 2), "utf-8"); + } + + async get(key: IDBValidKey): Promise { + return this.data.get(this.asKeyString(key)) as T; + } + + async set(key: IDBValidKey, value: T): Promise { + this.data.set(this.asKeyString(key), value); + this.flush(); + return key; + } + + async del(key: IDBValidKey): Promise { + this.data.delete(this.asKeyString(key)); + this.flush(); + } + + async clear(): Promise { + this.data.clear(); + this.flush(); + } + + private isIDBKeyRangeLike(value: unknown): value is { lower?: IDBValidKey; upper?: IDBValidKey } { + return typeof value === "object" && value !== null && ("lower" in value || "upper" in value); + } + + async keys(query?: IDBValidKey | IDBKeyRange, count?: number): Promise { + const allKeys = [...this.data.keys()]; + let filtered = allKeys; + if (typeof query !== "undefined") { + if (this.isIDBKeyRangeLike(query)) { + const lower = query.lower?.toString() ?? ""; + const upper = query.upper?.toString() ?? "\uffff"; + filtered = filtered.filter((key) => key >= lower && key <= upper); + } else { + const exact = query.toString(); + filtered = filtered.filter((key) => key === exact); + } + } + if (typeof count === "number") { + filtered = filtered.slice(0, count); + } + return filtered; + } + + async close(): Promise { + this.flush(); + } + + async destroy(): Promise { + this.data.clear(); + nodeFs.rmSync(this.filePath, { force: true }); + } +} + +export interface NodeKeyValueDBDependencies { + databaseEvents: InjectableDatabaseEventService; + vault: IVaultService; + appLifecycle: InjectableAppLifecycleService; +} + +export class NodeKeyValueDBService + extends ServiceBase + implements IKeyValueDBService +{ + private _kvDB: KeyValueDatabase | undefined; + private _simpleStore: SimpleStore | undefined; + private filePath: string; + private _log = createInstanceLogFunction("NodeKeyValueDBService"); + + get simpleStore() { + if (!this._simpleStore) { + throw new Error("SimpleStore is not initialized yet"); + } + return this._simpleStore; + } + + get kvDB() { + if (!this._kvDB) { + throw new Error("KeyValueDB is not initialized yet"); + } + return this._kvDB; + } + + constructor(context: T, dependencies: NodeKeyValueDBDependencies, filePath: string) { + super(context); + this.filePath = filePath; + + dependencies.databaseEvents.onResetDatabase.addHandler(this._everyOnResetDatabase.bind(this)); + dependencies.appLifecycle.onSettingLoaded.addHandler(this._everyOnloadAfterLoadSettings.bind(this)); + dependencies.databaseEvents.onDatabaseInitialisation.addHandler(this._everyOnInitializeDatabase.bind(this)); + dependencies.databaseEvents.onUnloadDatabase.addHandler(this._onOtherDatabaseUnload.bind(this)); + dependencies.databaseEvents.onCloseDatabase.addHandler(this._onOtherDatabaseClose.bind(this)); + } + + private async openKeyValueDB(): Promise { + try { + this._kvDB = new NodeFileKeyValueDatabase(this.filePath); + return true; + } catch (ex) { + this._log("Failed to open Node key-value database", LOG_LEVEL_NOTICE); + this._log(ex, LOG_LEVEL_VERBOSE); + return false; + } + } + + private async _everyOnResetDatabase(): Promise { + try { + await this._kvDB?.del("queued-files"); + await this._kvDB?.destroy(); + return await this.openKeyValueDB(); + } catch (ex) { + this._log("Failed to reset Node key-value database", LOG_LEVEL_NOTICE); + this._log(ex, LOG_LEVEL_VERBOSE); + return false; + } + } + + private async _onOtherDatabaseUnload(): Promise { + await this._kvDB?.close(); + return true; + } + + private async _onOtherDatabaseClose(): Promise { + await this._kvDB?.close(); + return true; + } + + private _everyOnInitializeDatabase(): Promise { + return this.openKeyValueDB(); + } + + private async _everyOnloadAfterLoadSettings(): Promise { + if (!(await this.openKeyValueDB())) { + return false; + } + this._simpleStore = this.openSimpleStore("os"); + return true; + } + + openSimpleStore(kind: string): SimpleStore { + const getDB = () => { + if (!this._kvDB) { + throw new Error("KeyValueDB is not initialized yet"); + } + return this._kvDB; + }; + const prefix = `${kind}-`; + return { + get: async (key: string): Promise => { + return await getDB().get(`${prefix}${key}`); + }, + set: async (key: string, value: any): Promise => { + await getDB().set(`${prefix}${key}`, value); + }, + delete: async (key: string): Promise => { + await getDB().del(`${prefix}${key}`); + }, + keys: async (from: string | undefined, to: string | undefined, count?: number): Promise => { + const allKeys = (await getDB().keys(undefined, count)).map((e) => e.toString()); + const lower = `${prefix}${from ?? ""}`; + const upper = `${prefix}${to ?? "\uffff"}`; + return allKeys + .filter((key) => key.startsWith(prefix)) + .filter((key) => key >= lower && key <= upper) + .map((key) => key.substring(prefix.length)); + }, + db: Promise.resolve(getDB()), + } satisfies SimpleStore; + } +} diff --git a/src/apps/cli/services/NodeServiceHub.ts b/src/apps/cli/services/NodeServiceHub.ts new file mode 100644 index 0000000..9815f42 --- /dev/null +++ b/src/apps/cli/services/NodeServiceHub.ts @@ -0,0 +1,206 @@ +import type { AppLifecycleService, AppLifecycleServiceDependencies } from "@lib/services/base/AppLifecycleService"; +import { ServiceContext } from "@lib/services/base/ServiceBase"; +import * as nodePath from "node:path"; +import { ConfigServiceBrowserCompat } from "@lib/services/implements/browser/ConfigServiceBrowserCompat"; +import { SvelteDialogManagerBase, type ComponentHasResult } from "@lib/services/implements/base/SvelteDialog"; +import { UIService } from "@lib/services/implements/base/UIService"; +import { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub"; +import { InjectableAppLifecycleService } from "@lib/services/implements/injectable/InjectableAppLifecycleService"; +import { InjectableConflictService } from "@lib/services/implements/injectable/InjectableConflictService"; +import { InjectableDatabaseEventService } from "@lib/services/implements/injectable/InjectableDatabaseEventService"; +import { InjectableFileProcessingService } from "@lib/services/implements/injectable/InjectableFileProcessingService"; +import { PathServiceCompat } from "@lib/services/implements/injectable/InjectablePathService"; +import { InjectableRemoteService } from "@lib/services/implements/injectable/InjectableRemoteService"; +import { InjectableReplicationService } from "@lib/services/implements/injectable/InjectableReplicationService"; +import { InjectableReplicatorService } from "@lib/services/implements/injectable/InjectableReplicatorService"; +import { InjectableTestService } from "@lib/services/implements/injectable/InjectableTestService"; +import { InjectableTweakValueService } from "@lib/services/implements/injectable/InjectableTweakValueService"; +import { InjectableVaultServiceCompat } from "@lib/services/implements/injectable/InjectableVaultService"; +import { ControlService } from "@lib/services/base/ControlService"; +import type { IControlService } from "@lib/services/base/IService"; +import { HeadlessAPIService } from "@lib/services/implements/headless/HeadlessAPIService"; +// import { HeadlessDatabaseService } from "@lib/services/implements/headless/HeadlessDatabaseService"; +import type { ServiceInstances } from "@lib/services/ServiceHub"; +import { NodeKeyValueDBService } from "./NodeKeyValueDBService"; +import { NodeSettingService } from "./NodeSettingService"; +import { DatabaseService } from "@lib/services/base/DatabaseService"; +import type { ObsidianLiveSyncSettings } from "@/lib/src/common/types"; + +export class NodeServiceContext extends ServiceContext { + vaultPath: string; + constructor(vaultPath: string) { + super(); + this.vaultPath = vaultPath; + } +} + +class NodeAppLifecycleService extends InjectableAppLifecycleService { + constructor(context: T, dependencies: AppLifecycleServiceDependencies) { + super(context, dependencies); + } +} + +class NodeSvelteDialogManager extends SvelteDialogManagerBase { + openSvelteDialog( + component: ComponentHasResult, + initialData?: UInitial + ): Promise { + throw new Error("Method not implemented."); + } +} + +type NodeUIServiceDependencies = { + appLifecycle: AppLifecycleService; + config: ConfigServiceBrowserCompat; + replicator: InjectableReplicatorService; + APIService: HeadlessAPIService; + control: IControlService; +}; +class NodeDatabaseService extends DatabaseService { + protected override modifyDatabaseOptions( + settings: ObsidianLiveSyncSettings, + name: string, + options: PouchDB.Configuration.DatabaseConfiguration + ): { name: string; options: PouchDB.Configuration.DatabaseConfiguration } { + const optionPass = { + ...options, + prefix: this.context.vaultPath + nodePath.sep, + }; + const passSettings = { ...settings, useIndexedDBAdapter: false }; + return super.modifyDatabaseOptions(passSettings, name, optionPass); + } +} +class NodeUIService extends UIService { + override get dialogToCopy(): never { + throw new Error("Method not implemented."); + } + + constructor(context: T, dependencies: NodeUIServiceDependencies) { + const headlessConfirm = dependencies.APIService.confirm; + const dialogManager = new NodeSvelteDialogManager(context, { + confirm: headlessConfirm, + appLifecycle: dependencies.appLifecycle, + config: dependencies.config, + replicator: dependencies.replicator, + control: dependencies.control, + }); + + super(context, { + appLifecycle: dependencies.appLifecycle, + dialogManager, + APIService: dependencies.APIService, + }); + } +} + +export class NodeServiceHub extends InjectableServiceHub { + constructor(basePath: string, context: T = new NodeServiceContext(basePath) as T) { + const runtimeDir = nodePath.join(basePath, ".livesync", "runtime"); + const localStoragePath = nodePath.join(runtimeDir, "local-storage.json"); + const keyValueDBPath = nodePath.join(runtimeDir, "keyvalue-db.json"); + + const API = new HeadlessAPIService(context); + const conflict = new InjectableConflictService(context); + const fileProcessing = new InjectableFileProcessingService(context); + + const setting = new NodeSettingService(context, { APIService: API }, localStoragePath); + + const appLifecycle = new NodeAppLifecycleService(context, { + settingService: setting, + }); + + const remote = new InjectableRemoteService(context, { + APIService: API, + appLifecycle, + setting, + }); + + const tweakValue = new InjectableTweakValueService(context); + const vault = new InjectableVaultServiceCompat(context, { + settingService: setting, + APIService: API, + }); + const test = new InjectableTestService(context); + const databaseEvents = new InjectableDatabaseEventService(context); + const path = new PathServiceCompat(context, { + settingService: setting, + }); + + const database = new NodeDatabaseService(context, { + API: API, + path, + vault, + setting, + }); + + const config = new ConfigServiceBrowserCompat(context, { + settingService: setting, + APIService: API, + }); + + const replicator = new InjectableReplicatorService(context, { + settingService: setting, + appLifecycleService: appLifecycle, + databaseEventService: databaseEvents, + }); + + const replication = new InjectableReplicationService(context, { + APIService: API, + appLifecycleService: appLifecycle, + replicatorService: replicator, + settingService: setting, + fileProcessingService: fileProcessing, + databaseService: database, + }); + + const keyValueDB = new NodeKeyValueDBService( + context, + { + appLifecycle, + databaseEvents, + vault, + }, + keyValueDBPath + ); + + const control = new ControlService(context, { + appLifecycleService: appLifecycle, + settingService: setting, + databaseService: database, + fileProcessingService: fileProcessing, + APIService: API, + replicatorService: replicator, + }); + + const ui = new NodeUIService(context, { + appLifecycle, + config, + replicator, + APIService: API, + control, + }); + + const serviceInstancesToInit: Required> = { + appLifecycle, + conflict, + database, + databaseEvents, + fileProcessing, + replication, + replicator, + remote, + setting, + tweakValue, + vault, + test, + ui, + path, + API, + config, + keyValueDB: keyValueDB as any, + control, + }; + + super(context, serviceInstancesToInit as any); + } +} diff --git a/src/apps/cli/services/NodeSettingService.ts b/src/apps/cli/services/NodeSettingService.ts new file mode 100644 index 0000000..f231fba --- /dev/null +++ b/src/apps/cli/services/NodeSettingService.ts @@ -0,0 +1,61 @@ +import { EVENT_SETTING_SAVED } from "@lib/events/coreEvents"; +import { EVENT_REQUEST_RELOAD_SETTING_TAB } from "@/common/events"; +import { eventHub } from "@lib/hub/hub"; +import { handlers } from "@lib/services/lib/HandlerUtils"; +import type { ObsidianLiveSyncSettings } from "@lib/common/types"; +import type { ServiceContext } from "@lib/services/base/ServiceBase"; +import { SettingService, type SettingServiceDependencies } from "@lib/services/base/SettingService"; +import * as nodeFs from "node:fs"; +import * as nodePath from "node:path"; + +export class NodeSettingService extends SettingService { + private storagePath: string; + private localStore: Record = {}; + + constructor(context: T, dependencies: SettingServiceDependencies, storagePath: string) { + super(context, dependencies); + this.storagePath = storagePath; + this.loadLocalStoreFromFile(); + this.onSettingSaved.addHandler((settings) => { + eventHub.emitEvent(EVENT_SETTING_SAVED, settings); + return Promise.resolve(true); + }); + this.onSettingLoaded.addHandler((settings) => { + eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB); + return Promise.resolve(true); + }); + } + + private loadLocalStoreFromFile() { + try { + const loaded = JSON.parse(nodeFs.readFileSync(this.storagePath, "utf-8")) as Record; + this.localStore = { ...loaded }; + } catch { + this.localStore = {}; + } + } + + private flushLocalStoreToFile() { + nodeFs.mkdirSync(nodePath.dirname(this.storagePath), { recursive: true }); + nodeFs.writeFileSync(this.storagePath, JSON.stringify(this.localStore, null, 2), "utf-8"); + } + + protected setItem(key: string, value: string) { + this.localStore[key] = value; + this.flushLocalStoreToFile(); + } + + protected getItem(key: string): string { + return this.localStore[key] ?? ""; + } + + protected deleteItem(key: string): void { + if (key in this.localStore) { + delete this.localStore[key]; + this.flushLocalStoreToFile(); + } + } + + public saveData = handlers<{ saveData: (data: ObsidianLiveSyncSettings) => Promise }>().binder("saveData"); + public loadData = handlers<{ loadData: () => Promise }>().binder("loadData"); +} diff --git a/src/apps/cli/test/test-push-pull-linux.sh b/src/apps/cli/test/test-push-pull-linux.sh new file mode 100644 index 0000000..ca9a846 --- /dev/null +++ b/src/apps/cli/test/test-push-pull-linux.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +CLI_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)" +cd "$CLI_DIR" + +CLI_ENTRY="${CLI_ENTRY:-$CLI_DIR/dist/index.cjs}" +RUN_BUILD="${RUN_BUILD:-1}" +REMOTE_PATH="${REMOTE_PATH:-test/push-pull.txt}" + +WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-test.XXXXXX")" +trap 'rm -rf "$WORK_DIR"' EXIT + +SETTINGS_FILE="${1:-$WORK_DIR/data.json}" + +if [[ "$RUN_BUILD" == "1" ]]; then + echo "[INFO] building CLI..." + npm run build +fi + +if [[ ! -f "$CLI_ENTRY" ]]; then + echo "[ERROR] CLI entry not found: $CLI_ENTRY" >&2 + exit 1 +fi + +echo "[INFO] generating settings from DEFAULT_SETTINGS -> $SETTINGS_FILE" +node "$CLI_ENTRY" init-settings --force "$SETTINGS_FILE" + +if [[ -n "${COUCHDB_URI:-}" && -n "${COUCHDB_USER:-}" && -n "${COUCHDB_PASSWORD:-}" && -n "${COUCHDB_DBNAME:-}" ]]; then + echo "[INFO] applying CouchDB env vars to generated settings" + SETTINGS_FILE="$SETTINGS_FILE" node <<'NODE' +const fs = require("node:fs"); +const settingsPath = process.env.SETTINGS_FILE; +const data = JSON.parse(fs.readFileSync(settingsPath, "utf-8")); +data.couchDB_URI = process.env.COUCHDB_URI; +data.couchDB_USER = process.env.COUCHDB_USER; +data.couchDB_PASSWORD = process.env.COUCHDB_PASSWORD; +data.couchDB_DBNAME = process.env.COUCHDB_DBNAME; +data.isConfigured = true; +fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8"); +NODE +else + echo "[WARN] CouchDB env vars are not fully set. push/pull may fail unless generated settings are updated." +fi + +VAULT_DIR="$WORK_DIR/vault" +mkdir -p "$VAULT_DIR/test" + +SRC_FILE="$WORK_DIR/push-source.txt" +PULLED_FILE="$WORK_DIR/pull-result.txt" +printf 'push-pull-test %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$SRC_FILE" + +echo "[INFO] push -> $REMOTE_PATH" +node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" push "$SRC_FILE" "$REMOTE_PATH" + +echo "[INFO] pull <- $REMOTE_PATH" +node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" pull "$REMOTE_PATH" "$PULLED_FILE" + +if cmp -s "$SRC_FILE" "$PULLED_FILE"; then + echo "[PASS] push/pull roundtrip matched" +else + echo "[FAIL] push/pull roundtrip mismatch" >&2 + echo "--- source ---" >&2 + cat "$SRC_FILE" >&2 + echo "--- pulled ---" >&2 + cat "$PULLED_FILE" >&2 + exit 1 +fi diff --git a/src/apps/cli/tsconfig.json b/src/apps/cli/tsconfig.json new file mode 100644 index 0000000..f79193a --- /dev/null +++ b/src/apps/cli/tsconfig.json @@ -0,0 +1,32 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + /* Linting */ + "strict": false, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true, + + /* Path mapping */ + "baseUrl": ".", + "paths": { + "@/*": ["../../*"], + "@lib/*": ["../../lib/src/*"] + } + }, + "include": ["*.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules", "dist"] +} diff --git a/src/apps/cli/vite.config.ts b/src/apps/cli/vite.config.ts new file mode 100644 index 0000000..d54fb44 --- /dev/null +++ b/src/apps/cli/vite.config.ts @@ -0,0 +1,55 @@ +import { defineConfig } from "vite"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import path from "node:path"; +import { readFileSync } from "node:fs"; +const packageJson = JSON.parse(readFileSync("../../../package.json", "utf-8")); +const manifestJson = JSON.parse(readFileSync("../../../manifest.json", "utf-8")); +// https://vite.dev/config/ +const defaultExternal = ["obsidian", "electron", "crypto", "pouchdb-adapter-leveldb", "commander", "punycode"]; +export default defineConfig({ + plugins: [svelte()], + resolve: { + alias: { + "@lib/worker/bgWorker.ts": "../../lib/src/worker/bgWorker.mock.ts", + "@lib/pouchdb/pouchdb-browser.ts": path.resolve(__dirname, "lib/pouchdb-node.ts"), + "@": path.resolve(__dirname, "../../"), + "@lib": path.resolve(__dirname, "../../lib/src"), + "../../src/worker/bgWorker.ts": "../../src/worker/bgWorker.mock.ts", + }, + }, + + base: "./", + build: { + outDir: "dist", + emptyOutDir: true, + minify: false, + rollupOptions: { + input: { + index: path.resolve(__dirname, "main.ts"), + }, + external: (id) => { + if (defaultExternal.includes(id)) return true; + if (id.startsWith(".") || id.startsWith("/")) return false; + if (id.startsWith("@/") || id.startsWith("@lib/")) return false; + if (id.endsWith(".ts") || id.endsWith(".js")) return false; + if (id === "fs" || id === "fs/promises" || id === "path" || id === "crypto") return true; + if (id.startsWith("pouchdb-")) return true; + if (id.startsWith("node:")) return true; + return false; + }, + }, + lib: { + entry: path.resolve(__dirname, "main.ts"), + formats: ["cjs"], + fileName: "index", + }, + }, + define: { + self: "globalThis", + global: "globalThis", + nonInteractive: "true", + // localStorage: "undefined", // Prevent usage of localStorage in the CLI environment + MANIFEST_VERSION: JSON.stringify(process.env.MANIFEST_VERSION || manifestJson.version || "0.0.0"), + PACKAGE_VERSION: JSON.stringify(process.env.PACKAGE_VERSION || packageJson.version || "0.0.0"), + }, +}); From 4a0d5e99d0225abdfd18179e7f42c4c43820adef Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 11 Mar 2026 14:57:36 +0100 Subject: [PATCH 060/339] Fix import path and add readme --- src/apps/webapp/README.md | 181 ++++++++++++++++++ .../webapp/adapters/FSAPIConversionAdapter.ts | 4 +- .../webapp/adapters/FSAPIFileSystemAdapter.ts | 6 +- src/apps/webapp/adapters/FSAPIPathAdapter.ts | 4 +- .../webapp/adapters/FSAPIStorageAdapter.ts | 4 +- .../webapp/adapters/FSAPITypeGuardAdapter.ts | 2 +- src/apps/webapp/adapters/FSAPITypes.ts | 2 +- src/apps/webapp/adapters/FSAPIVaultAdapter.ts | 4 +- src/apps/webapp/main.ts | 30 +-- .../FSAPIStorageEventManagerAdapter.ts | 10 +- .../managers/StorageEventManagerFSAPI.ts | 9 +- src/apps/webapp/package.json | 4 - .../serviceModules/DatabaseFileAccess.ts | 4 +- .../serviceModules/FSAPIServiceModules.ts | 15 +- .../webapp/serviceModules/FileAccessFSAPI.ts | 2 +- .../serviceModules/ServiceFileAccessImpl.ts | 5 +- 16 files changed, 233 insertions(+), 53 deletions(-) create mode 100644 src/apps/webapp/README.md diff --git a/src/apps/webapp/README.md b/src/apps/webapp/README.md new file mode 100644 index 0000000..b0aba0a --- /dev/null +++ b/src/apps/webapp/README.md @@ -0,0 +1,181 @@ +# LiveSync WebApp +Browser-based implementation of Obsidian LiveSync using the FileSystem API. +Note: (I vrtmrz have not tested this so much yet). + +## Features + +- 🌐 Runs entirely in the browser +- 📁 Uses FileSystem API to access your local vault +- 🔄 Syncs with CouchDB, Object Storage server (compatible with Obsidian LiveSync plugin) +- 🚫 No server-side code required!! +- 💾 Settings stored in `.livesync/settings.json` within your vault +- 👁️ Real-time file watching (Chrome 124+ with FileSystemObserver) + +## Requirements + +- **FileSystem API support**: + - Chrome/Edge 86+ (required) + - Opera 72+ (required) + - Safari 15.2+ (experimental, limited support) + - Firefox: Not supported yet + +- **FileSystemObserver support** (optional, for real-time file watching): + - Chrome 124+ (recommended) + - Without this, files are only scanned on startup + +## Getting Started + +### Installation + +```bash +# Install dependencies (ensure you are in repository root directory, not src/apps/cli) +# due to shared dependencies with webapp and main library +npm install +``` + +### Development + +```bash +# Build the project (ensure you are in `src/apps/webapp` directory) +cd src/apps/webapp +npm run dev +``` + +This will start a development server at `http://localhost:3000`. + +### Build + +```bash +# Build the project (ensure you are in `src/apps/webapp` directory) +cd src/apps/webapp +npm run build +``` + +The built files will be in the `dist` directory. + +### Usage + +1. Open the webapp in your browser +2. Grant directory access when prompted +3. Configure CouchDB connection by editing `.livesync/settings.json` in your vault + - You can also copy data.json from Obsidian's plug-in folder. + +Example `.livesync/settings.json`: + +```json +{ + "couchDB_URI": "https://your-couchdb-server.com", + "couchDB_USER": "your-username", + "couchDB_PASSWORD": "your-password", + "couchDB_DBNAME": "your-database", + "isConfigured": true, + "liveSync": true, + "syncOnSave": true +} +``` + +After editing, reload the page. + +## Architecture + +### Directory Structure + +``` +webapp/ +├── adapters/ # FileSystem API adapters +│ ├── FSAPITypes.ts +│ ├── FSAPIPathAdapter.ts +│ ├── FSAPITypeGuardAdapter.ts +│ ├── FSAPIConversionAdapter.ts +│ ├── FSAPIStorageAdapter.ts +│ ├── FSAPIVaultAdapter.ts +│ └── FSAPIFileSystemAdapter.ts +├── managers/ # Event managers +│ ├── FSAPIStorageEventManagerAdapter.ts +│ └── StorageEventManagerFSAPI.ts +├── serviceModules/ # Service implementations +│ ├── FileAccessFSAPI.ts +│ ├── ServiceFileAccessImpl.ts +│ ├── DatabaseFileAccess.ts +│ └── FSAPIServiceModules.ts +├── main.ts # Application entry point +├── index.html # HTML entry +├── package.json +├── vite.config.ts +└── README.md +``` + +### Key Components + +1. **Adapters**: Implement `IFileSystemAdapter` interface using FileSystem API +2. **Managers**: Handle storage events and file watching +3. **Service Modules**: Integrate with LiveSyncBaseCore +4. **Main**: Application initialization and lifecycle management + +### Service Hub + +Uses `BrowserServiceHub` which provides: + +- Database service (IndexedDB via PouchDB) +- Settings service (file-based in `.livesync/settings.json`) +- Replication service +- File processing service +- And more... + +## Limitations + +- **Real-time file watching**: Requires Chrome 124+ with FileSystemObserver + - Without it, changes are only detected on manual refresh +- **Performance**: Slower than native file system access +- **Permissions**: Requires user to grant directory access (cached via IndexedDB) +- **Browser support**: Limited to browsers with FileSystem API support + +## Differences from CLI Version + +- Uses `BrowserServiceHub` instead of `HeadlessServiceHub` +- Uses FileSystem API instead of Node.js `fs` +- Settings stored in `.livesync/settings.json` in vault +- Real-time file watching only with FileSystemObserver (Chrome 124+) + +## Differences from Obsidian Plugin + +- No Obsidian-specific modules (UI, settings dialog, etc.) +- Simplified configuration +- No plugin/theme sync features +- No internal file handling (`.obsidian` folder) + +## Development Notes + +- TypeScript configuration: Uses project's tsconfig.json +- Module resolution: Aliased paths via Vite config +- External dependencies: Bundled by Vite + +## Troubleshooting + +### "Failed to get directory access" + +- Make sure you're using a supported browser +- Try refreshing the page +- Clear browser cache and IndexedDB + +### "Settings not found" + +- Check that `.livesync/settings.json` exists in your vault directory +- Verify the JSON format is valid +- Create the file manually if needed + +### "File watching not working" + +- Make sure you're using Chrome 124 or later +- Check browser console for FileSystemObserver messages +- Try manually triggering sync if automatic watching isn't available + +### "Sync not working" + +- Verify CouchDB credentials +- Check browser console for errors +- Ensure CouchDB server is accessible (CORS enabled) + +## License + +Same as the main Obsidian LiveSync project. diff --git a/src/apps/webapp/adapters/FSAPIConversionAdapter.ts b/src/apps/webapp/adapters/FSAPIConversionAdapter.ts index 7acd106..f0136ff 100644 --- a/src/apps/webapp/adapters/FSAPIConversionAdapter.ts +++ b/src/apps/webapp/adapters/FSAPIConversionAdapter.ts @@ -1,5 +1,5 @@ -import type { UXFileInfoStub, UXFolderInfo } from "../../../lib/src/common/types"; -import type { IConversionAdapter } from "../../../lib/src/serviceModules/adapters"; +import type { UXFileInfoStub, UXFolderInfo } from "@lib/common/types"; +import type { IConversionAdapter } from "@lib/serviceModules/adapters"; import type { FSAPIFile, FSAPIFolder } from "./FSAPITypes"; /** diff --git a/src/apps/webapp/adapters/FSAPIFileSystemAdapter.ts b/src/apps/webapp/adapters/FSAPIFileSystemAdapter.ts index 1c87b46..e2ebbf3 100644 --- a/src/apps/webapp/adapters/FSAPIFileSystemAdapter.ts +++ b/src/apps/webapp/adapters/FSAPIFileSystemAdapter.ts @@ -1,5 +1,5 @@ -import type { FilePath, UXStat } from "../../../lib/src/common/types"; -import type { IFileSystemAdapter } from "../../../lib/src/serviceModules/adapters"; +import type { FilePath, UXStat } from "@lib/common/types"; +import type { IFileSystemAdapter } from "@lib/serviceModules/adapters"; import { FSAPIPathAdapter } from "./FSAPIPathAdapter"; import { FSAPITypeGuardAdapter } from "./FSAPITypeGuardAdapter"; import { FSAPIConversionAdapter } from "./FSAPIConversionAdapter"; @@ -75,7 +75,7 @@ export class FSAPIFileSystemAdapter implements IFileSystemAdapter { const pathStr = this.normalisePath(p); diff --git a/src/apps/webapp/adapters/FSAPIPathAdapter.ts b/src/apps/webapp/adapters/FSAPIPathAdapter.ts index 640f3bf..06764fa 100644 --- a/src/apps/webapp/adapters/FSAPIPathAdapter.ts +++ b/src/apps/webapp/adapters/FSAPIPathAdapter.ts @@ -1,5 +1,5 @@ -import type { FilePath } from "../../../lib/src/common/types"; -import type { IPathAdapter } from "../../../lib/src/serviceModules/adapters"; +import type { FilePath } from "@lib/common/types"; +import type { IPathAdapter } from "@lib/serviceModules/adapters"; import type { FSAPIFile } from "./FSAPITypes"; /** diff --git a/src/apps/webapp/adapters/FSAPIStorageAdapter.ts b/src/apps/webapp/adapters/FSAPIStorageAdapter.ts index ec4fb81..6767410 100644 --- a/src/apps/webapp/adapters/FSAPIStorageAdapter.ts +++ b/src/apps/webapp/adapters/FSAPIStorageAdapter.ts @@ -1,5 +1,5 @@ -import type { UXDataWriteOptions } from "../../../lib/src/common/types"; -import type { IStorageAdapter } from "../../../lib/src/serviceModules/adapters"; +import type { UXDataWriteOptions } from "@lib/common/types"; +import type { IStorageAdapter } from "@lib/serviceModules/adapters"; import type { FSAPIStat } from "./FSAPITypes"; /** diff --git a/src/apps/webapp/adapters/FSAPITypeGuardAdapter.ts b/src/apps/webapp/adapters/FSAPITypeGuardAdapter.ts index 49e34a8..06fa9b1 100644 --- a/src/apps/webapp/adapters/FSAPITypeGuardAdapter.ts +++ b/src/apps/webapp/adapters/FSAPITypeGuardAdapter.ts @@ -1,4 +1,4 @@ -import type { ITypeGuardAdapter } from "../../../lib/src/serviceModules/adapters"; +import type { ITypeGuardAdapter } from "@lib/serviceModules/adapters"; import type { FSAPIFile, FSAPIFolder } from "./FSAPITypes"; /** diff --git a/src/apps/webapp/adapters/FSAPITypes.ts b/src/apps/webapp/adapters/FSAPITypes.ts index fd696a9..91b3407 100644 --- a/src/apps/webapp/adapters/FSAPITypes.ts +++ b/src/apps/webapp/adapters/FSAPITypes.ts @@ -1,4 +1,4 @@ -import type { FilePath, UXStat } from "../../../lib/src/common/types"; +import type { FilePath, UXStat } from "@lib/common/types"; /** * FileSystem API file representation diff --git a/src/apps/webapp/adapters/FSAPIVaultAdapter.ts b/src/apps/webapp/adapters/FSAPIVaultAdapter.ts index c709e47..acc6ebd 100644 --- a/src/apps/webapp/adapters/FSAPIVaultAdapter.ts +++ b/src/apps/webapp/adapters/FSAPIVaultAdapter.ts @@ -1,5 +1,5 @@ -import type { FilePath, UXDataWriteOptions } from "../../../lib/src/common/types"; -import type { IVaultAdapter } from "../../../lib/src/serviceModules/adapters"; +import type { FilePath, UXDataWriteOptions } from "@lib/common/types"; +import type { IVaultAdapter } from "@lib/serviceModules/adapters"; import type { FSAPIFile, FSAPIFolder } from "./FSAPITypes"; /** diff --git a/src/apps/webapp/main.ts b/src/apps/webapp/main.ts index b723395..a7ffcdd 100644 --- a/src/apps/webapp/main.ts +++ b/src/apps/webapp/main.ts @@ -3,16 +3,19 @@ * Browser-based version of Self-hosted LiveSync plugin using FileSystem API */ -import { BrowserServiceHub } from "../../lib/src/services/BrowserServices"; -import { LiveSyncBaseCore } from "../../LiveSyncBaseCore"; -import { ServiceContext } from "../../lib/src/services/base/ServiceBase"; +import { BrowserServiceHub } from "@lib/services/BrowserServices"; +import { LiveSyncBaseCore } from "@/LiveSyncBaseCore"; +import { ServiceContext } from "@lib/services/base/ServiceBase"; import { initialiseServiceModulesFSAPI } from "./serviceModules/FSAPIServiceModules"; -import type { ObsidianLiveSyncSettings } from "../../lib/src/common/types"; -import type { BrowserAPIService } from "../../lib/src/services/implements/browser/BrowserAPIService"; -import type { InjectableSettingService } from "../../lib/src/services/implements/injectable/InjectableSettingService"; -// import { SetupManager } from "@/modules/features/SetupManager"; +import type { ObsidianLiveSyncSettings } from "@lib/common/types"; +import type { BrowserAPIService } from "@lib/services/implements/browser/BrowserAPIService"; +import type { InjectableSettingService } from "@lib/services/implements/injectable/InjectableSettingService"; +import { useOfflineScanner } from "@lib/serviceFeatures/offlineScanner"; +import { useRedFlagFeatures } from "@/serviceFeatures/redFlag"; +import { useCheckRemoteSize } from "@lib/serviceFeatures/checkRemoteSize"; +import { SetupManager } from "@/modules/features/SetupManager"; // import { ModuleObsidianSettingsAsMarkdown } from "@/modules/features/ModuleObsidianSettingAsMarkdown"; -// import { ModuleSetupObsidian } from "@/modules/features/ModuleSetupObsidian"; +import { ModuleSetupObsidian } from "@/modules/features/ModuleSetupObsidian"; // import { ModuleObsidianMenu } from "@/modules/essentialObsidian/ModuleObsidianMenu"; const SETTINGS_DIR = ".livesync"; @@ -105,7 +108,8 @@ class LiveSyncWebApp { // new ModuleObsidianEvents(this, core), // new ModuleObsidianSettingDialogue(this, core), // new ModuleObsidianMenu(core), - // new ModuleSetupObsidian(core), + new ModuleSetupObsidian(core), + new SetupManager(core), // new ModuleObsidianSettingsAsMarkdown(core), // new ModuleLog(this, core), // new ModuleObsidianDocumentHistory(this, core), @@ -116,8 +120,12 @@ class LiveSyncWebApp { // new ModuleIntegratedTest(this, core), // new SetupManager(core), ], - () => [],// No add-ons - () => [], + () => [], // No add-ons + (core) => { + useOfflineScanner(core); + useRedFlagFeatures(core); + useCheckRemoteSize(core); + } ); // Start the core diff --git a/src/apps/webapp/managers/FSAPIStorageEventManagerAdapter.ts b/src/apps/webapp/managers/FSAPIStorageEventManagerAdapter.ts index 5977c58..e33f5e6 100644 --- a/src/apps/webapp/managers/FSAPIStorageEventManagerAdapter.ts +++ b/src/apps/webapp/managers/FSAPIStorageEventManagerAdapter.ts @@ -1,6 +1,6 @@ -import type { FilePath, UXFileInfoStub, UXInternalFileInfoStub } from "../../../lib/src/common/types"; -import type { FileEventItem } from "../../../lib/src/common/types"; -import type { IStorageEventManagerAdapter } from "../../../lib/src/managers/adapters"; +import type { FilePath, UXFileInfoStub, UXInternalFileInfoStub } from "@lib/common/types"; +import type { FileEventItem } from "@lib/common/types"; +import type { IStorageEventManagerAdapter } from "@lib/managers/adapters"; import type { IStorageEventTypeGuardAdapter, IStorageEventPersistenceAdapter, @@ -8,8 +8,8 @@ import type { IStorageEventStatusAdapter, IStorageEventConverterAdapter, IStorageEventWatchHandlers, -} from "../../../lib/src/managers/adapters"; -import type { FileEventItemSentinel } from "../../../lib/src/managers/StorageEventManager"; +} from "@lib/managers/adapters"; +import type { FileEventItemSentinel } from "@lib/managers/StorageEventManager"; import type { FSAPIFile, FSAPIFolder } from "../adapters/FSAPITypes"; /** diff --git a/src/apps/webapp/managers/StorageEventManagerFSAPI.ts b/src/apps/webapp/managers/StorageEventManagerFSAPI.ts index 1bbc483..fb2761f 100644 --- a/src/apps/webapp/managers/StorageEventManagerFSAPI.ts +++ b/src/apps/webapp/managers/StorageEventManagerFSAPI.ts @@ -1,10 +1,7 @@ -import { - StorageEventManagerBase, - type StorageEventManagerBaseDependencies, -} from "../../../lib/src/managers/StorageEventManager"; +import { StorageEventManagerBase, type StorageEventManagerBaseDependencies } from "@lib/managers/StorageEventManager"; import { FSAPIStorageEventManagerAdapter } from "./FSAPIStorageEventManagerAdapter"; -import type { IMinimumLiveSyncCommands, LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; -import type { ServiceContext } from "../../../lib/src/services/base/ServiceBase"; +import type { IMinimumLiveSyncCommands, LiveSyncBaseCore } from "@/LiveSyncBaseCore"; +import type { ServiceContext } from "@lib/services/base/ServiceBase"; export class StorageEventManagerFSAPI extends StorageEventManagerBase { core: LiveSyncBaseCore; diff --git a/src/apps/webapp/package.json b/src/apps/webapp/package.json index 0e1f5b1..403e8ad 100644 --- a/src/apps/webapp/package.json +++ b/src/apps/webapp/package.json @@ -13,9 +13,5 @@ "devDependencies": { "typescript": "5.9.3", "vite": "^7.3.1" - }, - "imports": { - "../../src/worker/bgWorker.ts": "../../src/worker/bgWorker.mock.ts", - "@lib/worker/bgWorker.ts": "@lib/worker/bgWorker.mock.ts" } } diff --git a/src/apps/webapp/serviceModules/DatabaseFileAccess.ts b/src/apps/webapp/serviceModules/DatabaseFileAccess.ts index 6365042..346a9b6 100644 --- a/src/apps/webapp/serviceModules/DatabaseFileAccess.ts +++ b/src/apps/webapp/serviceModules/DatabaseFileAccess.ts @@ -1,8 +1,8 @@ import { ServiceDatabaseFileAccessBase, type ServiceDatabaseFileAccessDependencies, -} from "../../../lib/src/serviceModules/ServiceDatabaseFileAccessBase"; -import type { DatabaseFileAccess } from "../../../lib/src/interfaces/DatabaseFileAccess"; +} from "@lib/serviceModules/ServiceDatabaseFileAccessBase"; +import type { DatabaseFileAccess } from "@lib/interfaces/DatabaseFileAccess"; /** * FileSystem API-specific implementation of ServiceDatabaseFileAccess diff --git a/src/apps/webapp/serviceModules/FSAPIServiceModules.ts b/src/apps/webapp/serviceModules/FSAPIServiceModules.ts index 152422c..26a0a2f 100644 --- a/src/apps/webapp/serviceModules/FSAPIServiceModules.ts +++ b/src/apps/webapp/serviceModules/FSAPIServiceModules.ts @@ -1,14 +1,15 @@ -import type { InjectableServiceHub } from "../../../lib/src/services/implements/injectable/InjectableServiceHub"; -import { ServiceRebuilder } from "../../../lib/src/serviceModules/Rebuilder"; -import { ServiceFileHandler } from "../../../serviceModules/FileHandler"; -import { StorageAccessManager } from "../../../lib/src/managers/StorageProcessingManager"; -import type { ServiceModules } from "../../../types"; -import type { LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; -import type { ServiceContext } from "../../../lib/src/services/base/ServiceBase"; +import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub"; +import { ServiceRebuilder } from "@lib/serviceModules/Rebuilder"; + +import { StorageAccessManager } from "@lib/managers/StorageProcessingManager"; +import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore"; +import type { ServiceContext } from "@lib/services/base/ServiceBase"; import { FileAccessFSAPI } from "./FileAccessFSAPI"; import { ServiceFileAccessFSAPI } from "./ServiceFileAccessImpl"; import { ServiceDatabaseFileAccessFSAPI } from "./DatabaseFileAccess"; import { StorageEventManagerFSAPI } from "../managers/StorageEventManagerFSAPI"; +import type { ServiceModules } from "@lib/interfaces/ServiceModule"; +import { ServiceFileHandler } from "@/serviceModules/FileHandler"; /** * Initialize service modules for FileSystem API webapp version diff --git a/src/apps/webapp/serviceModules/FileAccessFSAPI.ts b/src/apps/webapp/serviceModules/FileAccessFSAPI.ts index 45e5660..9d1561e 100644 --- a/src/apps/webapp/serviceModules/FileAccessFSAPI.ts +++ b/src/apps/webapp/serviceModules/FileAccessFSAPI.ts @@ -1,4 +1,4 @@ -import { FileAccessBase, type FileAccessBaseDependencies } from "../../../lib/src/serviceModules/FileAccessBase"; +import { FileAccessBase, type FileAccessBaseDependencies } from "@lib/serviceModules/FileAccessBase"; import { FSAPIFileSystemAdapter } from "../adapters/FSAPIFileSystemAdapter"; /** diff --git a/src/apps/webapp/serviceModules/ServiceFileAccessImpl.ts b/src/apps/webapp/serviceModules/ServiceFileAccessImpl.ts index 4b6a474..f5396e2 100644 --- a/src/apps/webapp/serviceModules/ServiceFileAccessImpl.ts +++ b/src/apps/webapp/serviceModules/ServiceFileAccessImpl.ts @@ -1,7 +1,4 @@ -import { - ServiceFileAccessBase, - type StorageAccessBaseDependencies, -} from "../../../lib/src/serviceModules/ServiceFileAccessBase"; +import { ServiceFileAccessBase, type StorageAccessBaseDependencies } from "@lib/serviceModules/ServiceFileAccessBase"; import { FSAPIFileSystemAdapter } from "../adapters/FSAPIFileSystemAdapter"; /** From 70c7624c7a784754b0c54517769cb4342d74d037 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 11 Mar 2026 14:58:06 +0100 Subject: [PATCH 061/339] Add note --- updates.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/updates.md b/updates.md index 17fabc4..4be0b2f 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +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. +## -- Unreleased2 -- + +11th March, 2026 (second commit). + +### Refactored + +- Offline change scanner and the local database preparation has been separated. +- Set default priority for processFileEvent and processSynchroniseResult for the place for adding hooks. +- ControlService now provides the readiness for processing operations. +- DatabaseService now able to modify database opening options on derived classes. +- Now `useOfflineScanner`, `useCheckRemoteSize`, and `useRedFlagFeatures` are set from `main.ts`, instead of `LiveSyncBaseCore`. + +### Fixed + +- HeadlessAPIService now correctly provides the online status (always online) to the plug-in. +- Non-worker version of bgWorker now correctly handles some functions. + +### New something +- Add `self-hosted-livesync-cli` to `src/apps/cli` as a headless, and a dedicated version. + ## -- Unreleased -- 11th March, 2026 From 16c0dfef4caf90876863a60ed48065c9ad613f5a Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 11 Mar 2026 23:51:35 +0900 Subject: [PATCH 062/339] Remove the grandiloquence from the note written in work in progress. --- src/apps/cli/README.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/apps/cli/README.md b/src/apps/cli/README.md index b6dec8c..890eb7f 100644 --- a/src/apps/cli/README.md +++ b/src/apps/cli/README.md @@ -32,16 +32,11 @@ CLI Main - Platform-agnostic file operations using Node.js `fs/promises` - Implements same interface as Obsidian's file system -2. **CLI Storage Event Manager** (`managers/`) - - File-based snapshot persistence (JSON) - - Console-based status updates - - Optional file watching (can be extended with chokidar) - -3. **Service Modules** (`serviceModules/`) +2. **Service Modules** (`serviceModules/`) - Direct port from `main.ts` `initialiseServiceModules` - All core sync functionality preserved -4. **Main Entry Point** (`main.ts`) +3. **Main Entry Point** (`main.ts`) - Command-line interface - Settings management (JSON file) - Graceful shutdown handling From 5872cad1e5863ee6a3507a948f98b63366ade8d4 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 12 Mar 2026 02:56:30 +0900 Subject: [PATCH 063/339] Implement commands --- src/apps/cli/README.md | 136 ++++++- src/apps/cli/commands/runCommand.ts | 315 ++++++++++++++++ src/apps/cli/commands/types.ts | 49 +++ src/apps/cli/commands/utils.ts | 44 +++ src/apps/cli/main.ts | 177 +++------ .../managers/CLIStorageEventManagerAdapter.ts | 2 +- src/apps/cli/test/test-setup-put-cat-linux.sh | 339 ++++++++++++++++++ src/apps/webapp/README.md | 6 +- src/apps/webapp/package.json | 2 +- src/lib | 2 +- 10 files changed, 921 insertions(+), 151 deletions(-) create mode 100644 src/apps/cli/commands/runCommand.ts create mode 100644 src/apps/cli/commands/types.ts create mode 100644 src/apps/cli/commands/utils.ts create mode 100755 src/apps/cli/test/test-setup-put-cat-linux.sh diff --git a/src/apps/cli/README.md b/src/apps/cli/README.md index 890eb7f..8569075 100644 --- a/src/apps/cli/README.md +++ b/src/apps/cli/README.md @@ -1,10 +1,10 @@ # Self-hosted LiveSync CLI -Command-line version of Obsidian LiveSync plugin for syncing vaults without Obsidian. +Command-line version of Self-hosted LiveSync plugin for syncing vaults without Obsidian. ## Features - ✅ Sync Obsidian vaults using CouchDB without running Obsidian -- ✅ Compatible with Obsidian LiveSync plugin settings +- ✅ Compatible with Self-hosted LiveSync plugin settings - ✅ Supports all core sync features (encryption, conflict resolution, etc.) - ✅ Lightweight and headless operation - ✅ Cross-platform (Windows, macOS, Linux) @@ -59,16 +59,43 @@ As you know, the CLI is designed to be used in a headless environment. Hence all ```bash # Sync local database with CouchDB (no files will be changed). -node dist/cli/index.js /path/to/your-local-database --settings /path/to/settings.json sync +node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json sync # Push files to local database -node dist/cli/index.js /path/to/your-local-database --settings /path/to/settings.json push /your/storage/file.md /vault/path/file.md +node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json push /your/storage/file.md /vault/path/file.md # Pull files from local database -node dist/cli/index.js /path/to/your-local-database --settings /path/to/settings.json pull /vault/path/file.md /your/storage/file.md +node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json pull /vault/path/file.md /your/storage/file.md # Verbose logging -node dist/cli/index.js /path/to/your-local-database --settings /path/to/settings.json --verbose +node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json --verbose + +# Apply setup URI to settings file (settings only; does not run synchronisation) +node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json setup "obsidian://setuplivesync?settings=..." + +# Put text from stdin into local database +echo "Hello from stdin" | node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json put /vault/path/file.md + +# Output a file from local database to stdout +node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json cat /vault/path/file.md + +# Output a specific revision of a file from local database +node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json cat-rev /vault/path/file.md 3-abcdef + +# Pull a specific revision of a file from local database to local storage +node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json pull-rev /vault/path/file.md /your/storage/file.old.md 3-abcdef + +# List files in local database +node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json ls /vault/path/ + +# Show metadata for a file in local database +node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json info /vault/path/file.md + +# Mark a file as deleted in local database +node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json rm /vault/path/file.md + +# Resolve conflict by keeping a specific revision +node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json resolve /vault/path/file.md 3-abcdef ``` ### Configuration @@ -99,43 +126,116 @@ The CLI uses the same settings format as the Obsidian plugin. Create a `.livesyn - `couchDB_DBNAME`: Database name - `isConfigured`: Set to `true` after configuration -### Command-line Options +### Command-line Reference ``` Usage: - livesync-cli [database-path] [options] + livesync-cli [database-path] [options] [command] [command-args] Arguments: database-path Path to the local database directory (required) Options: --settings, -s Path to settings file (default: .livesync/settings.json in local database directory) + --force, -f Overwrite existing file on init-settings --verbose, -v Enable verbose logging --help, -h Show this help message - sync Sync local database with CouchDB or Bucket - push Push file to local database - pull Pull file from local database + +Commands: + init-settings [path] Create settings JSON from DEFAULT_SETTINGS + sync Run one replication cycle and exit + push Push local file into local database path + pull Pull file from local database into local file + pull-rev Pull specific revision into local file + setup Apply setup URI to settings file + put Read text from standard input and write to local database + cat Write latest file content from local database to standard output + cat-rev Write specific revision content from local database to standard output + ls List files as pathsizemtimerevision[*] + info Show file metadata including current and past revisions, conflicts, and chunk list + rm Mark file as deleted in local database + resolve Resolve conflict by keeping the specified revision ``` +`info` output fields: + +- `ID`: Document ID +- `Revision`: Current revision +- `Conflicts`: Conflicted revisions, or `N/A` +- `Filename`: Basename of path +- `Path`: Vault-relative path +- `Size`: Size in bytes +- `PastRevisions`: Available non-current revisions +- `Chunks`: Number of chunk IDs +- `child: ...`: Chunk ID list + ### Planned options: -- `put `: Add/update file in local database from standard input -- `cat `: Output file content to standard output -- `info `: Show file metadata, conflicts, and, other information -- `ls `: List files in local database with optional prefix filter -- `resolve `: Resolve conflict for a file by choosing a specific revision -- `rm `: Remove file from local database. +TODO: Conflict and resolution checks for real local databases. + - `--immediate`: Perform sync after the command (e.g. `push`, `pull`, `put`, `rm`). - `serve`: Start CLI in server mode, exposing REST APIs for remote, and batch operations. - +- `cause-conflicted `: Mark a file as conflicted without changing its content, to trigger conflict resolution in Obsidian. ## Use Cases +### 1. Bootstrap a new headless vault + +Create default settings, apply a setup URI, then run one sync cycle. + +```bash +node dist/index.cjs init-settings /data/livesync-settings.json +printf '%s\n' "$SETUP_PASSPHRASE" | node dist/index.cjs /data/vault --settings /data/livesync-settings.json setup "$SETUP_URI" +node dist/index.cjs /data/vault --settings /data/livesync-settings.json sync +``` + +### 2. Scripted import and export + +Push local files into the database from automation, and pull them back for export or backup. + +```bash +node dist/index.cjs /data/vault --settings /data/livesync-settings.json push ./note.md notes/note.md +node dist/index.cjs /data/vault --settings /data/livesync-settings.json pull notes/note.md ./exports/note.md +``` + +### 3. Revision inspection and restore + +List metadata, find an older revision, then restore it by content (`cat-rev`) or file output (`pull-rev`). + +```bash +node dist/index.cjs /data/vault --settings /data/livesync-settings.json info notes/note.md +node dist/index.cjs /data/vault --settings /data/livesync-settings.json cat-rev notes/note.md 3-abcdef +node dist/index.cjs /data/vault --settings /data/livesync-settings.json pull-rev notes/note.md ./restore/note.old.md 3-abcdef +``` + +### 4. Conflict and cleanup workflow + +Inspect conflicted revisions, resolve by keeping one revision, then delete obsolete files. + +```bash +node dist/index.cjs /data/vault --settings /data/livesync-settings.json info notes/note.md +node dist/index.cjs /data/vault --settings /data/livesync-settings.json resolve notes/note.md 3-abcdef +node dist/index.cjs /data/vault --settings /data/livesync-settings.json rm notes/obsolete.md +``` + +### 5. CI smoke test for content round-trip + +Validate that `put`/`cat` is behaving as expected in a pipeline. + +```bash +echo "hello-ci" | node dist/index.cjs /data/vault --settings /data/livesync-settings.json put ci/test.md +node dist/index.cjs /data/vault --settings /data/livesync-settings.json cat ci/test.md +``` + ## Development ### Project Structure ``` src/apps/cli/ +├── commands/ # Command dispatcher and command utilities +│ ├── runCommand.ts +│ ├── types.ts +│ └── utils.ts ├── adapters/ # Node.js FileSystem Adapter │ ├── NodeFileSystemAdapter.ts │ ├── NodePathAdapter.ts diff --git a/src/apps/cli/commands/runCommand.ts b/src/apps/cli/commands/runCommand.ts new file mode 100644 index 0000000..637f9a5 --- /dev/null +++ b/src/apps/cli/commands/runCommand.ts @@ -0,0 +1,315 @@ +import * as fs from "fs/promises"; +import * as path from "path"; +import { decodeSettingsFromSetupURI } from "@lib/API/processSetting"; +import { configURIBase } from "@lib/common/models/shared.const"; +import { DEFAULT_SETTINGS, type FilePathWithPrefix, type ObsidianLiveSyncSettings } from "@lib/common/types"; +import { stripAllPrefixes } from "@lib/string_and_binary/path"; +import type { CLICommandContext, CLIOptions } from "./types"; +import { promptForPassphrase, readStdinAsUtf8, toArrayBuffer, toVaultRelativePath } from "./utils"; + +export async function runCommand(options: CLIOptions, context: CLICommandContext): Promise { + const { vaultPath, core, settingsPath } = context; + + await core.services.control.activated; + if (options.command === "daemon") { + return true; + } + + if (options.command === "sync") { + console.log("[Command] sync"); + const result = await core.services.replication.replicate(true); + return !!result; + } + + if (options.command === "push") { + if (options.commandArgs.length < 2) { + throw new Error("push requires two arguments: "); + } + const sourcePath = path.resolve(options.commandArgs[0]); + const destinationVaultPath = toVaultRelativePath(options.commandArgs[1], vaultPath); + const sourceData = await fs.readFile(sourcePath); + const sourceStat = await fs.stat(sourcePath); + console.log(`[Command] push ${sourcePath} -> ${destinationVaultPath}`); + + await core.serviceModules.storageAccess.writeFileAuto(destinationVaultPath, toArrayBuffer(sourceData), { + mtime: sourceStat.mtimeMs, + ctime: sourceStat.ctimeMs, + }); + const destinationPathWithPrefix = destinationVaultPath as FilePathWithPrefix; + const stored = await core.serviceModules.fileHandler.storeFileToDB(destinationPathWithPrefix, true); + return stored; + } + + if (options.command === "pull") { + if (options.commandArgs.length < 2) { + throw new Error("pull requires two arguments: "); + } + const sourceVaultPath = toVaultRelativePath(options.commandArgs[0], vaultPath); + const destinationPath = path.resolve(options.commandArgs[1]); + console.log(`[Command] pull ${sourceVaultPath} -> ${destinationPath}`); + + const sourcePathWithPrefix = sourceVaultPath as FilePathWithPrefix; + const restored = await core.serviceModules.fileHandler.dbToStorage(sourcePathWithPrefix, null, true); + if (!restored) { + return false; + } + const data = await core.serviceModules.storageAccess.readFileAuto(sourceVaultPath); + await fs.mkdir(path.dirname(destinationPath), { recursive: true }); + if (typeof data === "string") { + await fs.writeFile(destinationPath, data, "utf-8"); + } else { + await fs.writeFile(destinationPath, new Uint8Array(data)); + } + return true; + } + + if (options.command === "pull-rev") { + if (options.commandArgs.length < 3) { + throw new Error("pull-rev requires three arguments: "); + } + const sourceVaultPath = toVaultRelativePath(options.commandArgs[0], vaultPath); + const destinationPath = path.resolve(options.commandArgs[1]); + const rev = options.commandArgs[2].trim(); + if (!rev) { + throw new Error("pull-rev requires a non-empty revision"); + } + console.log(`[Command] pull-rev ${sourceVaultPath}@${rev} -> ${destinationPath}`); + + const source = await core.serviceModules.databaseFileAccess.fetch( + sourceVaultPath as FilePathWithPrefix, + rev, + true + ); + if (!source || source.deleted) { + return false; + } + + await fs.mkdir(path.dirname(destinationPath), { recursive: true }); + const body = source.body; + if (body.type === "text/plain") { + await fs.writeFile(destinationPath, await body.text(), "utf-8"); + } else { + await fs.writeFile(destinationPath, new Uint8Array(await body.arrayBuffer())); + } + return true; + } + + if (options.command === "setup") { + if (options.commandArgs.length < 1) { + throw new Error("setup requires one argument: "); + } + const setupURI = options.commandArgs[0].trim(); + if (!setupURI.startsWith(configURIBase)) { + throw new Error(`setup URI must start with ${configURIBase}`); + } + const passphrase = await promptForPassphrase(); + const decoded = await decodeSettingsFromSetupURI(setupURI, passphrase); + if (!decoded) { + throw new Error("Failed to decode settings from setup URI"); + } + const nextSettings = { + ...DEFAULT_SETTINGS, + ...decoded, + useIndexedDBAdapter: false, + isConfigured: true, + } as ObsidianLiveSyncSettings; + + console.log(`[Command] setup -> ${settingsPath}`); + await core.services.setting.applyPartial(nextSettings, true); + await core.services.control.applySettings(); + return true; + } + + if (options.command === "put") { + if (options.commandArgs.length < 1) { + throw new Error("put requires one argument: "); + } + const destinationVaultPath = toVaultRelativePath(options.commandArgs[0], vaultPath); + const content = await readStdinAsUtf8(); + console.log(`[Command] put stdin -> ${destinationVaultPath}`); + return await core.serviceModules.databaseFileAccess.storeContent( + destinationVaultPath as FilePathWithPrefix, + content + ); + } + + if (options.command === "cat") { + if (options.commandArgs.length < 1) { + throw new Error("cat requires one argument: "); + } + const sourceVaultPath = toVaultRelativePath(options.commandArgs[0], vaultPath); + console.error(`[Command] cat ${sourceVaultPath}`); + const source = await core.serviceModules.databaseFileAccess.fetch( + sourceVaultPath as FilePathWithPrefix, + undefined, + true + ); + if (!source || source.deleted) { + return false; + } + const body = source.body; + if (body.type === "text/plain") { + process.stdout.write(await body.text()); + } else { + process.stdout.write(Buffer.from(await body.arrayBuffer())); + } + return true; + } + + if (options.command === "cat-rev") { + if (options.commandArgs.length < 2) { + throw new Error("cat-rev requires two arguments: "); + } + const sourceVaultPath = toVaultRelativePath(options.commandArgs[0], vaultPath); + const rev = options.commandArgs[1].trim(); + if (!rev) { + throw new Error("cat-rev requires a non-empty revision"); + } + console.error(`[Command] cat-rev ${sourceVaultPath} @ ${rev}`); + const source = await core.serviceModules.databaseFileAccess.fetch( + sourceVaultPath as FilePathWithPrefix, + rev, + true + ); + if (!source || source.deleted) { + return false; + } + const body = source.body; + if (body.type === "text/plain") { + process.stdout.write(await body.text()); + } else { + process.stdout.write(Buffer.from(await body.arrayBuffer())); + } + return true; + } + + if (options.command === "ls") { + const prefix = + options.commandArgs.length > 0 && options.commandArgs[0].trim() !== "" + ? toVaultRelativePath(options.commandArgs[0], vaultPath) + : ""; + const rows: { path: string; line: string }[] = []; + + for await (const doc of core.services.database.localDatabase.findAllNormalDocs({ conflicts: true })) { + if (doc._deleted || doc.deleted) { + continue; + } + const docPath = stripAllPrefixes(doc.path); + if (prefix !== "" && !docPath.startsWith(prefix)) { + continue; + } + const revision = `${doc._rev ?? ""}${(doc._conflicts?.length ?? 0) > 0 ? "*" : ""}`; + rows.push({ + path: docPath, + line: `${docPath}\t${doc.size}\t${doc.mtime}\t${revision}`, + }); + } + + rows.sort((a, b) => a.path.localeCompare(b.path)); + if (rows.length > 0) { + process.stdout.write(rows.map((e) => e.line).join("\n") + "\n"); + } + return true; + } + + if (options.command === "info") { + if (options.commandArgs.length < 1) { + throw new Error("info requires one argument: "); + } + const targetPath = toVaultRelativePath(options.commandArgs[0], vaultPath); + + for await (const doc of core.services.database.localDatabase.findAllNormalDocs({ conflicts: true })) { + if (doc._deleted || doc.deleted) continue; + const docPath = stripAllPrefixes(doc.path); + if (docPath !== targetPath) continue; + + const filename = path.basename(docPath); + const conflictsText = (doc._conflicts?.length ?? 0) > 0 ? doc._conflicts.join("\n ") : "N/A"; + const children = "children" in doc ? doc.children : []; + const rawDoc = await core.services.database.localDatabase.getRaw(doc._id, { + revs_info: true, + }); + const pastRevisions = (rawDoc._revs_info ?? []) + .filter((entry: { rev?: string; status?: string }) => { + if (!entry.rev) return false; + if (entry.rev === doc._rev) return false; + return entry.status === "available"; + }) + .map((entry: { rev: string }) => entry.rev); + const pastRevisionsText = + pastRevisions.length > 0 ? pastRevisions.map((rev: string) => ` rev: ${rev}`) : [" N/A"]; + + const out = + [ + `ID: ${doc._id}`, + `Revision: ${doc._rev ?? ""}`, + `Conflicts: ${conflictsText}`, + `Filename: ${filename}`, + `Path: ${docPath}`, + `Size: ${doc.size}`, + `PastRevisions:`, + ...pastRevisionsText, + `Chunks: ${children.length}`, + ...children.map((id) => ` child: ${id}`), + ].join("\n") + "\n"; + process.stdout.write(out); + return true; + } + + process.stderr.write(`[Info] File not found: ${targetPath}\n`); + return false; + } + + if (options.command === "rm") { + if (options.commandArgs.length < 1) { + throw new Error("rm requires one argument: "); + } + const targetPath = toVaultRelativePath(options.commandArgs[0], vaultPath); + console.error(`[Command] rm ${targetPath}`); + return await core.serviceModules.databaseFileAccess.delete(targetPath as FilePathWithPrefix); + } + + if (options.command === "resolve") { + if (options.commandArgs.length < 2) { + throw new Error("resolve requires two arguments: "); + } + const targetPath = toVaultRelativePath(options.commandArgs[0], vaultPath) as FilePathWithPrefix; + const revisionToKeep = options.commandArgs[1].trim(); + if (revisionToKeep === "") { + throw new Error("resolve requires a non-empty revision-to-keep"); + } + + const currentMeta = await core.serviceModules.databaseFileAccess.fetchEntryMeta(targetPath, undefined, true); + if (currentMeta === false || currentMeta._deleted || currentMeta.deleted) { + process.stderr.write(`[Info] File not found: ${targetPath}\n`); + return false; + } + + const conflicts = await core.serviceModules.databaseFileAccess.getConflictedRevs(targetPath); + const candidateRevisions = [currentMeta._rev, ...conflicts]; + if (!candidateRevisions.includes(revisionToKeep)) { + process.stderr.write(`[Info] Revision not found for ${targetPath}: ${revisionToKeep}\n`); + return false; + } + + if (conflicts.length === 0 && currentMeta._rev === revisionToKeep) { + console.error(`[Command] resolve ${targetPath} keep ${revisionToKeep} (already resolved)`); + return true; + } + + console.error(`[Command] resolve ${targetPath} keep ${revisionToKeep}`); + for (const revision of candidateRevisions) { + if (revision === revisionToKeep) { + continue; + } + const resolved = await core.services.conflict.resolveByDeletingRevision(targetPath, revision, "CLI"); + if (!resolved) { + process.stderr.write(`[Info] Failed to delete revision ${revision} for ${targetPath}\n`); + return false; + } + } + return true; + } + + throw new Error(`Unsupported command: ${options.command}`); +} diff --git a/src/apps/cli/commands/types.ts b/src/apps/cli/commands/types.ts new file mode 100644 index 0000000..9182fd7 --- /dev/null +++ b/src/apps/cli/commands/types.ts @@ -0,0 +1,49 @@ +import { LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; +import { ServiceContext } from "@lib/services/base/ServiceBase"; + +export type CLICommand = + | "daemon" + | "sync" + | "push" + | "pull" + | "pull-rev" + | "setup" + | "put" + | "cat" + | "cat-rev" + | "ls" + | "info" + | "rm" + | "resolve" + | "init-settings"; + +export interface CLIOptions { + databasePath?: string; + settingsPath?: string; + verbose?: boolean; + force?: boolean; + command: CLICommand; + commandArgs: string[]; +} + +export interface CLICommandContext { + vaultPath: string; + core: LiveSyncBaseCore; + settingsPath: string; +} + +export const VALID_COMMANDS = new Set([ + "sync", + "push", + "pull", + "pull-rev", + "setup", + "put", + "cat", + "cat-rev", + "ls", + "info", + "rm", + "resolve", + "init-settings", +] as const); diff --git a/src/apps/cli/commands/utils.ts b/src/apps/cli/commands/utils.ts new file mode 100644 index 0000000..d085822 --- /dev/null +++ b/src/apps/cli/commands/utils.ts @@ -0,0 +1,44 @@ +import * as path from "path"; +import * as readline from "node:readline/promises"; + +export function toArrayBuffer(data: Buffer): ArrayBuffer { + return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer; +} + +export function toVaultRelativePath(inputPath: string, vaultPath: string): string { + const stripped = inputPath.replace(/^[/\\]+/, ""); + if (!path.isAbsolute(inputPath)) { + return stripped.replace(/\\/g, "/"); + } + const resolved = path.resolve(inputPath); + const rel = path.relative(vaultPath, resolved); + if (rel.startsWith("..") || path.isAbsolute(rel)) { + throw new Error(`Path ${inputPath} is outside of the local database directory`); + } + return rel.replace(/\\/g, "/"); +} + +export async function readStdinAsUtf8(): Promise { + const chunks: Buffer[] = []; + for await (const chunk of process.stdin) { + if (typeof chunk === "string") { + chunks.push(Buffer.from(chunk, "utf-8")); + } else { + chunks.push(chunk); + } + } + return Buffer.concat(chunks).toString("utf-8"); +} + +export async function promptForPassphrase(prompt = "Enter setup URI passphrase: "): Promise { + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + try { + const passphrase = await rl.question(prompt); + if (!passphrase) { + throw new Error("Passphrase is required"); + } + return passphrase; + } finally { + rl.close(); + } +} diff --git a/src/apps/cli/main.ts b/src/apps/cli/main.ts index 649f245..64b3578 100644 --- a/src/apps/cli/main.ts +++ b/src/apps/cli/main.ts @@ -1,7 +1,7 @@ #!/usr/bin/env node /** * Self-hosted LiveSync CLI - * Command-line version of Obsidian LiveSync plugin for syncing vaults without Obsidian + * Command-line version of Self-hosted LiveSync plugin for syncing vaults without Obsidian */ if (!("localStorage" in globalThis)) { @@ -24,24 +24,16 @@ import * as fs from "fs/promises"; import * as path from "path"; import { NodeServiceContext, NodeServiceHub } from "./services/NodeServiceHub"; import { LiveSyncBaseCore } from "../../LiveSyncBaseCore"; -import { ServiceContext } from "@lib/services/base/ServiceBase"; import { initialiseServiceModulesCLI } from "./serviceModules/CLIServiceModules"; -import { - DEFAULT_SETTINGS, - LOG_LEVEL_VERBOSE, - type LOG_LEVEL, - type ObsidianLiveSyncSettings, - type FilePathWithPrefix, -} from "@lib/common/types"; +import { DEFAULT_SETTINGS, LOG_LEVEL_VERBOSE, type LOG_LEVEL, type ObsidianLiveSyncSettings } from "@lib/common/types"; import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub"; import type { InjectableSettingService } from "@/lib/src/services/implements/injectable/InjectableSettingService"; import { LOG_LEVEL_DEBUG, setGlobalLogFunction, defaultLoggerEnv } from "octagonal-wheels/common/logger"; -import PouchDb from "pouchdb-core"; +import { runCommand } from "./commands/runCommand"; +import { VALID_COMMANDS } from "./commands/types"; +import type { CLICommand, CLIOptions } from "./commands/types"; const SETTINGS_FILE = ".livesync/settings.json"; -const VALID_COMMANDS = new Set(["sync", "push", "pull", "init-settings"] as const); - -type CLICommand = "daemon" | "sync" | "push" | "pull" | "init-settings"; defaultLoggerEnv.minLogLevel = LOG_LEVEL_DEBUG; // DI the log again. // const recentLogEntries = reactiveSource([]); @@ -55,20 +47,11 @@ defaultLoggerEnv.minLogLevel = LOG_LEVEL_DEBUG; // }; setGlobalLogFunction((msg, level) => { - console.log(`[${level}] ${typeof msg === "string" ? msg : JSON.stringify(msg)}`); + console.error(`[${level}] ${typeof msg === "string" ? msg : JSON.stringify(msg)}`); if (msg instanceof Error) { console.error(msg); } }); -interface CLIOptions { - databasePath?: string; - settingsPath?: string; - verbose?: boolean; - force?: boolean; - command: CLICommand; - commandArgs: string[]; -} - function printHelp(): void { console.log(` Self-hosted LiveSync CLI @@ -83,18 +66,28 @@ Commands: sync Run one replication cycle and exit push Push local file into local database path pull Pull file from local database into local file - init-settings [path] Create settings JSON from DEFAULT_SETTINGS - -Options: - --settings, -s Path to settings file (default: .livesync/settings.json in local database directory) - --force, -f Overwrite existing file on init-settings - --verbose, -v Enable verbose logging - --help, -h Show this help message - + pull-rev Pull file at specific revision into local file + setup Apply setup URI to settings file + put Read UTF-8 content from stdin and write to local database path + cat Read file from local database and write to stdout + cat-rev Read file at specific revision and write to stdout + ls [prefix] List DB files as pathsizemtimerevision[*] + info Show detailed metadata for a file (ID, revision, conflicts, chunks) + rm Mark a file as deleted in local database + resolve Resolve conflicts by keeping and deleting others Examples: livesync-cli ./my-database sync 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 `); @@ -202,86 +195,16 @@ async function createDefaultSettingsFile(options: CLIOptions) { console.log(`[Done] Created settings file: ${targetPath}`); } -function toArrayBuffer(data: Buffer): ArrayBuffer { - return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer; -} - -function toVaultRelativePath(inputPath: string, vaultPath: string): string { - const stripped = inputPath.replace(/^[/\\]+/, ""); - if (!path.isAbsolute(inputPath)) { - return stripped.replace(/\\/g, "/"); - } - const resolved = path.resolve(inputPath); - const rel = path.relative(vaultPath, resolved); - if (rel.startsWith("..") || path.isAbsolute(rel)) { - throw new Error(`Path ${inputPath} is outside of the local database directory`); - } - return rel.replace(/\\/g, "/"); -} - -async function runCommand( - options: CLIOptions, - vaultPath: string, - core: LiveSyncBaseCore -): Promise { - await core.services.control.activated; - if (options.command === "daemon") { - return true; - } - - if (options.command === "sync") { - console.log("[Command] sync"); - const result = await core.services.replication.replicate(true); - return !!result; - } - - if (options.command === "push") { - if (options.commandArgs.length < 2) { - throw new Error("push requires two arguments: "); - } - const sourcePath = path.resolve(options.commandArgs[0]); - const destinationVaultPath = toVaultRelativePath(options.commandArgs[1], vaultPath); - const sourceData = await fs.readFile(sourcePath); - const sourceStat = await fs.stat(sourcePath); - console.log(`[Command] push ${sourcePath} -> ${destinationVaultPath}`); - - await core.serviceModules.storageAccess.writeFileAuto(destinationVaultPath, toArrayBuffer(sourceData), { - mtime: sourceStat.mtimeMs, - ctime: sourceStat.ctimeMs, - }); - const destinationPathWithPrefix = destinationVaultPath as FilePathWithPrefix; - const stored = await core.serviceModules.fileHandler.storeFileToDB(destinationPathWithPrefix, true); - return stored; - } - - if (options.command === "pull") { - if (options.commandArgs.length < 2) { - throw new Error("pull requires two arguments: "); - } - const sourceVaultPath = toVaultRelativePath(options.commandArgs[0], vaultPath); - const destinationPath = path.resolve(options.commandArgs[1]); - console.log(`[Command] pull ${sourceVaultPath} -> ${destinationPath}`); - - const sourcePathWithPrefix = sourceVaultPath as FilePathWithPrefix; - const restored = await core.serviceModules.fileHandler.dbToStorage(sourcePathWithPrefix, null, true); - if (!restored) { - return false; - } - const data = await core.serviceModules.storageAccess.readFileAuto(sourceVaultPath); - await fs.mkdir(path.dirname(destinationPath), { recursive: true }); - if (typeof data === "string") { - await fs.writeFile(destinationPath, data, "utf-8"); - } else { - await fs.writeFile(destinationPath, new Uint8Array(data)); - } - return true; - } - - throw new Error(`Unsupported command: ${options.command}`); -} - async function main() { const options = parseArgs(); + const avoidStdoutNoise = + options.command === "cat" || + options.command === "cat-rev" || + options.command === "ls" || + options.command === "info" || + options.command === "rm" || + options.command === "resolve"; + const infoLog = avoidStdoutNoise ? console.error : console.log; if (options.command === "init-settings") { await createDefaultSettingsFile(options); @@ -307,10 +230,10 @@ async function main() { ? path.resolve(options.settingsPath) : path.join(vaultPath, SETTINGS_FILE); - console.log(`Self-hosted LiveSync CLI`); - console.log(`Vault: ${vaultPath}`); - console.log(`Settings: ${settingsPath}`); - console.log(); + infoLog(`Self-hosted LiveSync CLI`); + infoLog(`Vault: ${vaultPath}`); + infoLog(`Settings: ${settingsPath}`); + infoLog(""); // Create service context and hub const context = new NodeServiceContext(vaultPath); @@ -320,11 +243,11 @@ async function main() { if (level <= LOG_LEVEL_VERBOSE) { if (!options.verbose) return; } - console.log(`${prefix} ${message}`); + console.error(`${prefix} ${message}`); }); // Prevent replication result to be processed automatically. serviceHubInstance.replication.processSynchroniseResult.addHandler(async () => { - console.log(`[Info] Replication result received, but not processed automatically in CLI mode.`); + console.error(`[Info] Replication result received, but not processed automatically in CLI mode.`); return await Promise.resolve(true); }, -100); // Setup settings handlers @@ -335,7 +258,7 @@ async function main() { try { await fs.writeFile(settingsPath, JSON.stringify(data, null, 2), "utf-8"); if (options.verbose) { - console.log(`[Settings] Saved to ${settingsPath}`); + console.error(`[Settings] Saved to ${settingsPath}`); } } catch (error) { console.error(`[Settings] Failed to save:`, error); @@ -349,14 +272,14 @@ async function main() { const content = await fs.readFile(settingsPath, "utf-8"); const data = JSON.parse(content); if (options.verbose) { - console.log(`[Settings] Loaded from ${settingsPath}`); + console.error(`[Settings] Loaded from ${settingsPath}`); } // Force disable IndexedDB adapter in CLI environment data.useIndexedDBAdapter = false; return data; } catch (error) { if (options.verbose) { - console.log(`[Settings] File not found, using defaults`); + console.error(`[Settings] File not found, using defaults`); } return undefined; } @@ -393,7 +316,7 @@ async function main() { // Start the core try { - console.log(`[Starting] Initializing LiveSync...`); + infoLog(`[Starting] Initializing LiveSync...`); const loadResult = await core.services.control.onLoad(); if (!loadResult) { @@ -403,9 +326,9 @@ async function main() { await core.services.control.onReady(); - console.log(`[Ready] LiveSync is running`); - console.log(`[Ready] Press Ctrl+C to stop`); - console.log(); + infoLog(`[Ready] LiveSync is running`); + infoLog(`[Ready] Press Ctrl+C to stop`); + infoLog(""); // Check if configured const settings = core.services.setting.currentSettings(); @@ -420,17 +343,17 @@ async function main() { console.warn(` - couchDB_DBNAME: Database name`); console.warn(); } else { - console.log(`[Info] LiveSync is configured and ready`); - console.log(`[Info] Database: ${settings.couchDB_URI}/${settings.couchDB_DBNAME}`); - console.log(); + infoLog(`[Info] LiveSync is configured and ready`); + infoLog(`[Info] Database: ${settings.couchDB_URI}/${settings.couchDB_DBNAME}`); + infoLog(""); } - const result = await runCommand(options, vaultPath, core); + const result = await runCommand(options, { vaultPath, core, settingsPath }); if (!result) { console.error(`[Error] Command '${options.command}' failed`); process.exitCode = 1; } else if (options.command !== "daemon") { - console.log(`[Done] Command '${options.command}' completed`); + infoLog(`[Done] Command '${options.command}' completed`); } if (options.command === "daemon") { diff --git a/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts b/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts index 5cb8509..61d214b 100644 --- a/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts +++ b/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts @@ -108,7 +108,7 @@ class CLIWatchAdapter implements IStorageEventWatchAdapter { 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.log("[CLIWatchAdapter] File watching is not enabled in CLI version"); + console.error("[CLIWatchAdapter] File watching is not enabled in CLI version"); return Promise.resolve(); } } diff --git a/src/apps/cli/test/test-setup-put-cat-linux.sh b/src/apps/cli/test/test-setup-put-cat-linux.sh new file mode 100755 index 0000000..eff5a84 --- /dev/null +++ b/src/apps/cli/test/test-setup-put-cat-linux.sh @@ -0,0 +1,339 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +CLI_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)" +REPO_ROOT="$(cd -- "$CLI_DIR/../../.." && pwd)" +cd "$CLI_DIR" + +CLI_ENTRY="${CLI_ENTRY:-$CLI_DIR/dist/index.cjs}" +RUN_BUILD="${RUN_BUILD:-1}" +REMOTE_PATH="${REMOTE_PATH:-test/setup-put-cat.txt}" +SETUP_PASSPHRASE="${SETUP_PASSPHRASE:-setup-passphrase}" + +WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-test.XXXXXX")" +trap 'rm -rf "$WORK_DIR"' EXIT + +SETTINGS_FILE="${1:-$WORK_DIR/data.json}" + +if [[ "$RUN_BUILD" == "1" ]]; then + echo "[INFO] building CLI..." + npm run build +fi + +if [[ ! -f "$CLI_ENTRY" ]]; then + echo "[ERROR] CLI entry not found: $CLI_ENTRY" >&2 + exit 1 +fi + +echo "[INFO] generating settings from DEFAULT_SETTINGS -> $SETTINGS_FILE" +node "$CLI_ENTRY" init-settings --force "$SETTINGS_FILE" + +echo "[INFO] creating setup URI from settings" +SETUP_URI="$( + REPO_ROOT="$REPO_ROOT" SETTINGS_FILE="$SETTINGS_FILE" SETUP_PASSPHRASE="$SETUP_PASSPHRASE" npx tsx -e ' +import fs from "node:fs"; +(async () => { + const { encodeSettingsToSetupURI } = await import(process.env.REPO_ROOT + "/src/lib/src/API/processSetting.ts"); + const settingsPath = process.env.SETTINGS_FILE; + const setupPassphrase = process.env.SETUP_PASSPHRASE; + const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8")); + settings.couchDB_DBNAME = "setup-put-cat-db"; + settings.couchDB_URI = "http://127.0.0.1:5999"; + settings.couchDB_USER = "dummy"; + settings.couchDB_PASSWORD = "dummy"; + settings.liveSync = false; + settings.syncOnStart = false; + settings.syncOnSave = false; + const uri = await encodeSettingsToSetupURI(settings, setupPassphrase); + process.stdout.write(uri.trim()); +})(); +' +)" + +VAULT_DIR="$WORK_DIR/vault" +mkdir -p "$VAULT_DIR/test" + +echo "[INFO] applying setup URI" +SETUP_LOG="$WORK_DIR/setup-output.log" +set +e +printf '%s\n' "$SETUP_PASSPHRASE" | node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" setup "$SETUP_URI" \ + >"$SETUP_LOG" 2>&1 +SETUP_EXIT=$? +set -e +cat "$SETUP_LOG" +if [[ "$SETUP_EXIT" -ne 0 ]]; then + echo "[FAIL] setup command exited with $SETUP_EXIT" >&2 + exit 1 +fi + +if grep -Fq "[Command] setup ->" "$SETUP_LOG"; then + echo "[PASS] setup command executed" +else + echo "[FAIL] setup command did not execute expected code path" >&2 + exit 1 +fi + +SRC_FILE="$WORK_DIR/put-source.txt" +printf 'setup-put-cat-test %s\nline-2\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$SRC_FILE" + +echo "[INFO] put -> $REMOTE_PATH" +cat "$SRC_FILE" | node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" put "$REMOTE_PATH" + +echo "[INFO] cat <- $REMOTE_PATH" +CAT_OUTPUT="$WORK_DIR/cat-output.txt" +node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" cat "$REMOTE_PATH" > "$CAT_OUTPUT" + +CAT_OUTPUT_CLEAN="$WORK_DIR/cat-output-clean.txt" +grep -v '^\[CLIWatchAdapter\] File watching is not enabled in CLI version$' "$CAT_OUTPUT" > "$CAT_OUTPUT_CLEAN" || true + +if cmp -s "$SRC_FILE" "$CAT_OUTPUT_CLEAN"; then + echo "[PASS] setup/put/cat roundtrip matched" +else + echo "[FAIL] setup/put/cat roundtrip mismatch" >&2 + echo "--- source ---" >&2 + cat "$SRC_FILE" >&2 + echo "--- cat-output ---" >&2 + cat "$CAT_OUTPUT_CLEAN" >&2 + exit 1 +fi + +echo "[INFO] ls $REMOTE_PATH" +LS_OUTPUT="$WORK_DIR/ls-output.txt" +node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" ls "$REMOTE_PATH" > "$LS_OUTPUT" + +LS_LINE="$(grep -F "$REMOTE_PATH" "$LS_OUTPUT" | head -n 1 || true)" +if [[ -z "$LS_LINE" ]]; then + echo "[FAIL] ls output did not include target path" >&2 + cat "$LS_OUTPUT" >&2 + exit 1 +fi + +IFS=$'\t' read -r LS_PATH LS_SIZE LS_MTIME LS_REV <<< "$LS_LINE" +if [[ "$LS_PATH" != "$REMOTE_PATH" ]]; then + echo "[FAIL] ls path column mismatch: $LS_PATH" >&2 + exit 1 +fi +if [[ ! "$LS_SIZE" =~ ^[0-9]+$ ]]; then + echo "[FAIL] ls size column is not numeric: $LS_SIZE" >&2 + exit 1 +fi +if [[ ! "$LS_MTIME" =~ ^[0-9]+$ ]]; then + echo "[FAIL] ls mtime column is not numeric: $LS_MTIME" >&2 + exit 1 +fi +if [[ -z "$LS_REV" ]]; then + echo "[FAIL] ls revision column is empty" >&2 + exit 1 +fi +echo "[PASS] ls output format matched" + +echo "[INFO] adding more files for ls test cases" +printf 'file-a\n' | node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" put test/a-first.txt >/dev/null +printf 'file-z\n' | node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" put test/z-last.txt >/dev/null + +echo "[INFO] ls test/ (prefix filter and sorting)" +LS_PREFIX_OUTPUT="$WORK_DIR/ls-prefix-output.txt" +node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" ls test/ > "$LS_PREFIX_OUTPUT" + +if [[ "$(wc -l < "$LS_PREFIX_OUTPUT")" -lt 3 ]]; then + echo "[FAIL] ls prefix output expected at least 3 rows" >&2 + cat "$LS_PREFIX_OUTPUT" >&2 + exit 1 +fi + +FIRST_PATH="$(cut -f1 "$LS_PREFIX_OUTPUT" | sed -n '1p')" +SECOND_PATH="$(cut -f1 "$LS_PREFIX_OUTPUT" | sed -n '2p')" +if [[ "$FIRST_PATH" > "$SECOND_PATH" ]]; then + echo "[FAIL] ls output is not sorted by path" >&2 + cat "$LS_PREFIX_OUTPUT" >&2 + exit 1 +fi + +if ! grep -Fq $'test/a-first.txt\t' "$LS_PREFIX_OUTPUT"; then + echo "[FAIL] ls prefix output missing test/a-first.txt" >&2 + cat "$LS_PREFIX_OUTPUT" >&2 + exit 1 +fi +if ! grep -Fq $'test/z-last.txt\t' "$LS_PREFIX_OUTPUT"; then + echo "[FAIL] ls prefix output missing test/z-last.txt" >&2 + cat "$LS_PREFIX_OUTPUT" >&2 + exit 1 +fi +echo "[PASS] ls prefix and sorting matched" + +echo "[INFO] ls no-match prefix" +LS_EMPTY_OUTPUT="$WORK_DIR/ls-empty-output.txt" +node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" ls no-such-prefix/ > "$LS_EMPTY_OUTPUT" +if [[ -s "$LS_EMPTY_OUTPUT" ]]; then + echo "[FAIL] ls no-match prefix should produce empty output" >&2 + cat "$LS_EMPTY_OUTPUT" >&2 + exit 1 +fi +echo "[PASS] ls no-match prefix matched" + +echo "[INFO] info $REMOTE_PATH" +INFO_OUTPUT="$WORK_DIR/info-output.txt" +node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" info "$REMOTE_PATH" > "$INFO_OUTPUT" + +# Check required label lines +for label in "ID:" "Revision:" "Conflicts:" "Filename:" "Path:" "Size:" "Chunks:"; do + if ! grep -q "^$label" "$INFO_OUTPUT"; then + echo "[FAIL] info output missing label: $label" >&2 + cat "$INFO_OUTPUT" >&2 + exit 1 + fi +done + +# Path value must match +INFO_PATH="$(grep '^Path:' "$INFO_OUTPUT" | sed 's/^Path:[[:space:]]*//')" +if [[ "$INFO_PATH" != "$REMOTE_PATH" ]]; then + echo "[FAIL] info Path mismatch: $INFO_PATH" >&2 + exit 1 +fi + +# Filename must be the basename +INFO_FILENAME="$(grep '^Filename:' "$INFO_OUTPUT" | sed 's/^Filename:[[:space:]]*//')" +EXPECTED_FILENAME="$(basename "$REMOTE_PATH")" +if [[ "$INFO_FILENAME" != "$EXPECTED_FILENAME" ]]; then + echo "[FAIL] info Filename mismatch: $INFO_FILENAME != $EXPECTED_FILENAME" >&2 + exit 1 +fi + +# Size must be numeric +INFO_SIZE="$(grep '^Size:' "$INFO_OUTPUT" | sed 's/^Size:[[:space:]]*//')" +if [[ ! "$INFO_SIZE" =~ ^[0-9]+$ ]]; then + echo "[FAIL] info Size is not numeric: $INFO_SIZE" >&2 + exit 1 +fi + +# Chunks count must be numeric and ≥1 +INFO_CHUNKS="$(grep '^Chunks:' "$INFO_OUTPUT" | sed 's/^Chunks:[[:space:]]*//')" +if [[ ! "$INFO_CHUNKS" =~ ^[0-9]+$ ]] || [[ "$INFO_CHUNKS" -lt 1 ]]; then + echo "[FAIL] info Chunks is not a positive integer: $INFO_CHUNKS" >&2 + exit 1 +fi + +# Conflicts should be N/A (no live CouchDB) +INFO_CONFLICTS="$(grep '^Conflicts:' "$INFO_OUTPUT" | sed 's/^Conflicts:[[:space:]]*//')" +if [[ "$INFO_CONFLICTS" != "N/A" ]]; then + echo "[FAIL] info Conflicts expected N/A, got: $INFO_CONFLICTS" >&2 + exit 1 +fi + +echo "[PASS] info output format matched" + +echo "[INFO] info non-existent path" +INFO_MISSING_EXIT=0 +node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" info no-such-file.md > /dev/null || INFO_MISSING_EXIT=$? +if [[ "$INFO_MISSING_EXIT" -eq 0 ]]; then + echo "[FAIL] info on non-existent file should exit non-zero" >&2 + exit 1 +fi +echo "[PASS] info non-existent path returns non-zero" + +echo "[INFO] rm test/z-last.txt" +node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" rm test/z-last.txt > /dev/null + +RM_CAT_EXIT=0 +node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" cat test/z-last.txt > /dev/null || RM_CAT_EXIT=$? +if [[ "$RM_CAT_EXIT" -eq 0 ]]; then + echo "[FAIL] rm target should not be readable by cat" >&2 + exit 1 +fi + +LS_AFTER_RM="$WORK_DIR/ls-after-rm.txt" +node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" ls test/ > "$LS_AFTER_RM" +if grep -Fq $'test/z-last.txt\t' "$LS_AFTER_RM"; then + echo "[FAIL] rm target should not appear in ls output" >&2 + cat "$LS_AFTER_RM" >&2 + exit 1 +fi +echo "[PASS] rm removed target from visible entries" + +echo "[INFO] resolve test/a-first.txt using current revision" +RESOLVE_LS_LINE="$(node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" ls test/a-first.txt | head -n 1)" +if [[ -z "$RESOLVE_LS_LINE" ]]; then + echo "[FAIL] could not fetch revision for resolve test" >&2 + exit 1 +fi +IFS=$'\t' read -r _ _ _ RESOLVE_REV <<< "$RESOLVE_LS_LINE" +if [[ -z "$RESOLVE_REV" ]]; then + echo "[FAIL] revision was empty for resolve test" >&2 + exit 1 +fi + +node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" resolve test/a-first.txt "$RESOLVE_REV" > /dev/null +echo "[PASS] resolve accepted current revision" + +echo "[INFO] resolve with non-existent revision" +RESOLVE_BAD_EXIT=0 +node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" resolve test/a-first.txt 9-no-such-rev > /dev/null || RESOLVE_BAD_EXIT=$? +if [[ "$RESOLVE_BAD_EXIT" -eq 0 ]]; then + echo "[FAIL] resolve with non-existent revision should exit non-zero" >&2 + exit 1 +fi +echo "[PASS] resolve non-existent revision returns non-zero" + +echo "[INFO] preparing revision history for cat-rev test" +REV_PATH="test/revision-history.txt" +REV_V1_FILE="$WORK_DIR/rev-v1.txt" +REV_V2_FILE="$WORK_DIR/rev-v2.txt" +REV_V3_FILE="$WORK_DIR/rev-v3.txt" + +printf 'revision-v1\n' > "$REV_V1_FILE" +printf 'revision-v2\n' > "$REV_V2_FILE" +printf 'revision-v3\n' > "$REV_V3_FILE" + +cat "$REV_V1_FILE" | node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" put "$REV_PATH" > /dev/null +cat "$REV_V2_FILE" | node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" put "$REV_PATH" > /dev/null +cat "$REV_V3_FILE" | node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" put "$REV_PATH" > /dev/null + +echo "[INFO] info $REV_PATH (past revisions)" +REV_INFO_OUTPUT="$WORK_DIR/rev-info-output.txt" +node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" info "$REV_PATH" > "$REV_INFO_OUTPUT" + +PAST_REV="$(grep '^ rev: ' "$REV_INFO_OUTPUT" | head -n 1 | sed 's/^ rev: //')" +if [[ -z "$PAST_REV" ]]; then + echo "[FAIL] info output did not include any past revision" >&2 + cat "$REV_INFO_OUTPUT" >&2 + exit 1 +fi + +echo "[INFO] cat-rev $REV_PATH @ $PAST_REV" +REV_CAT_OUTPUT="$WORK_DIR/rev-cat-output.txt" +node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" cat-rev "$REV_PATH" "$PAST_REV" > "$REV_CAT_OUTPUT" + +if cmp -s "$REV_CAT_OUTPUT" "$REV_V1_FILE" || cmp -s "$REV_CAT_OUTPUT" "$REV_V2_FILE"; then + echo "[PASS] cat-rev matched one of the past revisions from info" +else + echo "[FAIL] cat-rev output did not match expected past revisions" >&2 + echo "--- info output ---" >&2 + cat "$REV_INFO_OUTPUT" >&2 + echo "--- cat-rev output ---" >&2 + cat "$REV_CAT_OUTPUT" >&2 + echo "--- expected v1 ---" >&2 + cat "$REV_V1_FILE" >&2 + echo "--- expected v2 ---" >&2 + cat "$REV_V2_FILE" >&2 + exit 1 +fi + +echo "[INFO] pull-rev $REV_PATH @ $PAST_REV" +REV_PULL_OUTPUT="$WORK_DIR/rev-pull-output.txt" +node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" pull-rev "$REV_PATH" "$REV_PULL_OUTPUT" "$PAST_REV" > /dev/null + +if cmp -s "$REV_PULL_OUTPUT" "$REV_V1_FILE" || cmp -s "$REV_PULL_OUTPUT" "$REV_V2_FILE"; then + echo "[PASS] pull-rev matched one of the past revisions from info" +else + echo "[FAIL] pull-rev output did not match expected past revisions" >&2 + echo "--- info output ---" >&2 + cat "$REV_INFO_OUTPUT" >&2 + echo "--- pull-rev output ---" >&2 + cat "$REV_PULL_OUTPUT" >&2 + echo "--- expected v1 ---" >&2 + cat "$REV_V1_FILE" >&2 + echo "--- expected v2 ---" >&2 + cat "$REV_V2_FILE" >&2 + exit 1 +fi diff --git a/src/apps/webapp/README.md b/src/apps/webapp/README.md index b0aba0a..5fc34e7 100644 --- a/src/apps/webapp/README.md +++ b/src/apps/webapp/README.md @@ -1,12 +1,12 @@ # LiveSync WebApp -Browser-based implementation of Obsidian LiveSync using the FileSystem API. +Browser-based implementation of Self-hosted LiveSync using the FileSystem API. Note: (I vrtmrz have not tested this so much yet). ## Features - 🌐 Runs entirely in the browser - 📁 Uses FileSystem API to access your local vault -- 🔄 Syncs with CouchDB, Object Storage server (compatible with Obsidian LiveSync plugin) +- 🔄 Syncs with CouchDB, Object Storage server (compatible with Self-hosted LiveSync plugin) - 🚫 No server-side code required!! - 💾 Settings stored in `.livesync/settings.json` within your vault - 👁️ Real-time file watching (Chrome 124+ with FileSystemObserver) @@ -178,4 +178,4 @@ Uses `BrowserServiceHub` which provides: ## License -Same as the main Obsidian LiveSync project. +Same as the main Self-hosted LiveSync project. diff --git a/src/apps/webapp/package.json b/src/apps/webapp/package.json index 403e8ad..414c3e7 100644 --- a/src/apps/webapp/package.json +++ b/src/apps/webapp/package.json @@ -3,7 +3,7 @@ "private": true, "version": "0.0.1", "type": "module", - "description": "Browser-based Obsidian LiveSync using FileSystem API", + "description": "Browser-based Self-hosted LiveSync using FileSystem API", "scripts": { "dev": "vite", "build": "vite build", diff --git a/src/lib b/src/lib index 83e2704..3ce1f81 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 83e2704c818c9563c4649ce3d9c13ed11a774d37 +Subproject commit 3ce1f81a21d6a5278ff876478a89fca5ca6fbbeb From 7992b3c2b995cf3a947df3f5f76263f37fb0d6d3 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 12 Mar 2026 03:01:46 +0900 Subject: [PATCH 064/339] Wrote the test (but untested) --- .../test-sync-two-local-databases-linux.sh | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100755 src/apps/cli/test/test-sync-two-local-databases-linux.sh diff --git a/src/apps/cli/test/test-sync-two-local-databases-linux.sh b/src/apps/cli/test/test-sync-two-local-databases-linux.sh new file mode 100755 index 0000000..e4f7eb6 --- /dev/null +++ b/src/apps/cli/test/test-sync-two-local-databases-linux.sh @@ -0,0 +1,187 @@ +#!/usr/bin/env bash +## TODO: test this script. I would love to go to my bed today (3a.m.) However, I am so excited about the new CLI that I want to at least get this skeleton in place. Delightful days! +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +CLI_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)" +cd "$CLI_DIR" + +CLI_ENTRY="${CLI_ENTRY:-$CLI_DIR/dist/index.cjs}" +RUN_BUILD="${RUN_BUILD:-1}" +COUCHDB_URI="${COUCHDB_URI:-}" +COUCHDB_USER="${COUCHDB_USER:-}" +COUCHDB_PASSWORD="${COUCHDB_PASSWORD:-}" +COUCHDB_DBNAME_BASE="${COUCHDB_DBNAME:-livesync-cli-e2e}" + +if [[ -z "$COUCHDB_URI" || -z "$COUCHDB_USER" || -z "$COUCHDB_PASSWORD" ]]; then + echo "[ERROR] COUCHDB_URI, COUCHDB_USER, COUCHDB_PASSWORD are required" >&2 + exit 1 +fi + +WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-two-db-test.XXXXXX")" +trap 'rm -rf "$WORK_DIR"' EXIT + +if [[ "$RUN_BUILD" == "1" ]]; then + echo "[INFO] building CLI..." + npm run build +fi + +if [[ ! -f "$CLI_ENTRY" ]]; then + echo "[ERROR] CLI entry not found: $CLI_ENTRY" >&2 + exit 1 +fi + +DB_SUFFIX="$(date +%s)-$RANDOM" +COUCHDB_DBNAME="${COUCHDB_DBNAME_BASE}-${DB_SUFFIX}" + +echo "[INFO] using CouchDB database: $COUCHDB_DBNAME" + +VAULT_A="$WORK_DIR/vault-a" +VAULT_B="$WORK_DIR/vault-b" +SETTINGS_A="$WORK_DIR/a-settings.json" +SETTINGS_B="$WORK_DIR/b-settings.json" +mkdir -p "$VAULT_A" "$VAULT_B" + +node "$CLI_ENTRY" init-settings --force "$SETTINGS_A" >/dev/null +node "$CLI_ENTRY" init-settings --force "$SETTINGS_B" >/dev/null + +apply_settings() { + local settings_file="$1" + SETTINGS_FILE="$settings_file" \ + COUCHDB_URI="$COUCHDB_URI" \ + COUCHDB_USER="$COUCHDB_USER" \ + COUCHDB_PASSWORD="$COUCHDB_PASSWORD" \ + COUCHDB_DBNAME="$COUCHDB_DBNAME" \ + node <<'NODE' +const fs = require("node:fs"); +const settingsPath = process.env.SETTINGS_FILE; +const data = JSON.parse(fs.readFileSync(settingsPath, "utf-8")); +data.couchDB_URI = process.env.COUCHDB_URI; +data.couchDB_USER = process.env.COUCHDB_USER; +data.couchDB_PASSWORD = process.env.COUCHDB_PASSWORD; +data.couchDB_DBNAME = process.env.COUCHDB_DBNAME; +data.liveSync = true; +data.syncOnStart = false; +data.syncOnSave = false; +data.usePluginSync = false; +data.isConfigured = true; +fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8"); +NODE +} + +apply_settings "$SETTINGS_A" +apply_settings "$SETTINGS_B" + +run_cli_a() { + node "$CLI_ENTRY" "$VAULT_A" --settings "$SETTINGS_A" "$@" +} + +run_cli_b() { + node "$CLI_ENTRY" "$VAULT_B" --settings "$SETTINGS_B" "$@" +} + +sync_a() { + run_cli_a sync >/dev/null +} + +sync_b() { + run_cli_b sync >/dev/null +} + +cat_a() { + run_cli_a cat "$1" +} + +cat_b() { + run_cli_b cat "$1" +} + +assert_equal() { + local expected="$1" + local actual="$2" + local message="$3" + if [[ "$expected" != "$actual" ]]; then + echo "[FAIL] $message" >&2 + echo "expected: $expected" >&2 + echo "actual: $actual" >&2 + exit 1 + fi +} + +echo "[INFO] case1: A creates file, B can read after sync" +printf 'from-a\n' | run_cli_a put shared/from-a.txt >/dev/null +sync_a +sync_b +VALUE_FROM_B="$(cat_b shared/from-a.txt)" +assert_equal "from-a" "$VALUE_FROM_B" "B could not read file created on A" +echo "[PASS] case1 passed" + +echo "[INFO] case2: B creates file, A can read after sync" +printf 'from-b\n' | run_cli_b put shared/from-b.txt >/dev/null +sync_b +sync_a +VALUE_FROM_A="$(cat_a shared/from-b.txt)" +assert_equal "from-b" "$VALUE_FROM_A" "A could not read file created on B" +echo "[PASS] case2 passed" + +echo "[INFO] case3: concurrent edits create conflict" +printf 'base\n' | run_cli_a put shared/conflicted.txt >/dev/null +sync_a +sync_b + +printf 'edit-from-a\n' | run_cli_a put shared/conflicted.txt >/dev/null +printf 'edit-from-b\n' | run_cli_b put shared/conflicted.txt >/dev/null + +sync_a +sync_b + +INFO_A="$WORK_DIR/info-a.txt" +INFO_B="$WORK_DIR/info-b.txt" +run_cli_a info shared/conflicted.txt > "$INFO_A" +run_cli_b info shared/conflicted.txt > "$INFO_B" + +if grep -q '^Conflicts: N/A$' "$INFO_A" && grep -q '^Conflicts: N/A$' "$INFO_B"; then + echo "[FAIL] expected conflict after concurrent edits, but both sides show N/A" >&2 + echo "--- A info ---" >&2 + cat "$INFO_A" >&2 + echo "--- B info ---" >&2 + cat "$INFO_B" >&2 + exit 1 +fi +echo "[PASS] case3 conflict detected" + +echo "[INFO] case4: resolve on A, sync, and verify B has no conflict" +KEEP_REV="$(sed -n 's/^Revision:[[:space:]]*//p' "$INFO_A" | head -n 1)" +if [[ -z "$KEEP_REV" ]]; then + echo "[FAIL] could not read Revision from A info output" >&2 + cat "$INFO_A" >&2 + exit 1 +fi + +run_cli_a resolve shared/conflicted.txt "$KEEP_REV" >/dev/null +sync_a +sync_b + +INFO_B_AFTER="$WORK_DIR/info-b-after-resolve.txt" +run_cli_b info shared/conflicted.txt > "$INFO_B_AFTER" +if ! grep -q '^Conflicts: N/A$' "$INFO_B_AFTER"; then + echo "[FAIL] B still has conflicts after resolving on A and syncing" >&2 + cat "$INFO_B_AFTER" >&2 + exit 1 +fi + +CONTENT_A="$WORK_DIR/conflicted-a.txt" +CONTENT_B="$WORK_DIR/conflicted-b.txt" +cat_a shared/conflicted.txt > "$CONTENT_A" +cat_b shared/conflicted.txt > "$CONTENT_B" +if ! cmp -s "$CONTENT_A" "$CONTENT_B"; then + echo "[FAIL] resolved content mismatch between A and B" >&2 + echo "--- A ---" >&2 + cat "$CONTENT_A" >&2 + echo "--- B ---" >&2 + cat "$CONTENT_B" >&2 + exit 1 +fi + +echo "[PASS] case4 passed" +echo "[PASS] all sync/resolve scenarios passed" From fa14531599b97b45e21004eceaeb4c40024ff9be Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 12 Mar 2026 03:03:03 +0900 Subject: [PATCH 065/339] Add note --- src/apps/cli/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/apps/cli/README.md b/src/apps/cli/README.md index 8569075..5f0d8ff 100644 --- a/src/apps/cli/README.md +++ b/src/apps/cli/README.md @@ -176,6 +176,10 @@ TODO: Conflict and resolution checks for real local databases. - `--immediate`: Perform sync after the command (e.g. `push`, `pull`, `put`, `rm`). - `serve`: Start CLI in server mode, exposing REST APIs for remote, and batch operations. - `cause-conflicted `: Mark a file as conflicted without changing its content, to trigger conflict resolution in Obsidian. + +## Current Limitations and known issues +- Binary files are not supported yet (it seems... but I haven't tested this yet). + ## Use Cases ### 1. Bootstrap a new headless vault From 5d80258a7799fa73b0c4bb6ab823610e5d990305 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 12 Mar 2026 12:20:39 +0900 Subject: [PATCH 066/339] Add e2e-test (not passed yet) Refine readme --- package-lock.json | 86 ++---- src/apps/cli/.test.env | 8 + src/apps/cli/README.md | 124 +++++---- src/apps/cli/commands/runCommand.ts | 29 +- src/apps/cli/package.json | 8 +- .../test-e2e-two-vaults-with-docker-linux.sh | 247 ++++++++++++++++++ src/apps/cli/test/test-push-pull-linux.sh | 15 +- src/apps/cli/test/test-setup-put-cat-linux.sh | 55 ++-- .../test-sync-two-local-databases-linux.sh | 17 +- src/apps/cli/util/couchdb-init.sh | 47 ++++ src/apps/cli/util/couchdb-start.sh | 4 + src/apps/cli/util/couchdb-stop.sh | 3 + src/apps/cli/util/minio-init.sh | 47 ++++ src/apps/cli/util/minio-start.sh | 2 + src/apps/cli/util/minio-stop.sh | 3 + src/lib | 2 +- 16 files changed, 520 insertions(+), 177 deletions(-) create mode 100644 src/apps/cli/.test.env create mode 100644 src/apps/cli/test/test-e2e-two-vaults-with-docker-linux.sh create mode 100755 src/apps/cli/util/couchdb-init.sh create mode 100755 src/apps/cli/util/couchdb-start.sh create mode 100755 src/apps/cli/util/couchdb-stop.sh create mode 100755 src/apps/cli/util/minio-init.sh create mode 100755 src/apps/cli/util/minio-start.sh create mode 100755 src/apps/cli/util/minio-stop.sh diff --git a/package-lock.json b/package-lock.json index d89d159..dfb7f22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1939,7 +1939,6 @@ "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.4.tgz", "integrity": "sha512-pUxEGmR+uu21OG/icAovjlu1fcYJzyVhhT0rsCrn+zi+nHtrS43Bp9KPn9KGa4NMspCUE++nkyiqziuIvJdwzw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", @@ -2006,7 +2005,6 @@ "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.4.tgz", "integrity": "sha512-T7ifGmb+awJEcp542Ek4HtNfBxcBrnuk1ggUdqyFEdsXHdq7+wVlhvE6YukTL7NS8hIkEfL7TMAPx/uCNqt30g==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@firebase/app": "0.14.4", "@firebase/component": "0.7.0", @@ -2022,8 +2020,7 @@ "version": "0.9.3", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/@firebase/app/node_modules/idb": { "version": "7.1.1", @@ -2492,7 +2489,6 @@ "integrity": "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==", "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.1.0" }, @@ -3085,7 +3081,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/@multiformats/dns": { "version": "1.0.10", @@ -4947,7 +4944,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", @@ -5427,7 +5423,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", @@ -5632,7 +5627,6 @@ "integrity": "sha512-t4toy8X/YTnjYEPoY0pbDBg3EvDPg1elCDrfc+VupPHwoN/5/FNQ8Z+xBYIaEnOE2vVEyKwqYBzZ9h9rJtZVcg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/mocker": "4.0.16", "@vitest/utils": "4.0.16", @@ -5656,7 +5650,6 @@ "integrity": "sha512-I2Fy/ANdphi1yI46d15o0M1M4M0UJrUiVKkH5oKeRZZCdPg0fw/cfTKZzv9Ge9eobtJYp4BGblMzXdXH0vcl5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/browser": "4.0.16", "@vitest/mocker": "4.0.16", @@ -6401,7 +6394,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7410,7 +7402,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", @@ -8293,7 +8286,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -8404,7 +8396,6 @@ "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -10777,7 +10768,6 @@ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -11329,7 +11319,6 @@ "resolved": "https://registry.npmjs.org/libp2p/-/libp2p-2.8.11.tgz", "integrity": "sha512-EjkyN0CI6uP+e4OOkEcZvhbZtlwFl4Y0rkkMvDbXmcfILX4E4n/jKE4Ppoc1qhNufxToxVWCMDS2ipniQgiYaw==", "license": "Apache-2.0 OR MIT", - "peer": true, "dependencies": { "@chainsafe/is-ip": "^2.1.0", "@chainsafe/netmask": "^2.0.0", @@ -12630,7 +12619,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -12655,7 +12643,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "lilconfig": "^3.1.1" }, @@ -14226,7 +14213,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", @@ -14293,7 +14281,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", @@ -14611,7 +14598,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -14739,7 +14725,6 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -15345,7 +15330,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15482,7 +15466,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -16043,7 +16026,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -16077,7 +16059,6 @@ "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", @@ -16173,7 +16154,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", @@ -16689,7 +16671,6 @@ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "dev": true, "license": "ISC", - "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -18037,7 +18018,6 @@ "version": "0.14.4", "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.4.tgz", "integrity": "sha512-pUxEGmR+uu21OG/icAovjlu1fcYJzyVhhT0rsCrn+zi+nHtrS43Bp9KPn9KGa4NMspCUE++nkyiqziuIvJdwzw==", - "peer": true, "requires": { "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", @@ -18091,7 +18071,6 @@ "version": "0.5.4", "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.4.tgz", "integrity": "sha512-T7ifGmb+awJEcp542Ek4HtNfBxcBrnuk1ggUdqyFEdsXHdq7+wVlhvE6YukTL7NS8hIkEfL7TMAPx/uCNqt30g==", - "peer": true, "requires": { "@firebase/app": "0.14.4", "@firebase/component": "0.7.0", @@ -18103,8 +18082,7 @@ "@firebase/app-types": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", - "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", - "peer": true + "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==" }, "@firebase/auth": { "version": "1.11.0", @@ -18432,7 +18410,6 @@ "version": "1.13.0", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.13.0.tgz", "integrity": "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==", - "peer": true, "requires": { "tslib": "^2.1.0" } @@ -18905,7 +18882,8 @@ "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 + "dev": true, + "peer": true }, "@multiformats/dns": { "version": "1.0.10", @@ -20089,7 +20067,6 @@ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz", "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", "dev": true, - "peer": true, "requires": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "deepmerge": "^4.3.1", @@ -20517,7 +20494,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, - "peer": true, "requires": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", @@ -20625,7 +20601,6 @@ "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.0.16.tgz", "integrity": "sha512-t4toy8X/YTnjYEPoY0pbDBg3EvDPg1elCDrfc+VupPHwoN/5/FNQ8Z+xBYIaEnOE2vVEyKwqYBzZ9h9rJtZVcg==", "dev": true, - "peer": true, "requires": { "@vitest/mocker": "4.0.16", "@vitest/utils": "4.0.16", @@ -20642,7 +20617,6 @@ "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.0.16.tgz", "integrity": "sha512-I2Fy/ANdphi1yI46d15o0M1M4M0UJrUiVKkH5oKeRZZCdPg0fw/cfTKZzv9Ge9eobtJYp4BGblMzXdXH0vcl5g==", "dev": true, - "peer": true, "requires": { "@vitest/browser": "4.0.16", "@vitest/mocker": "4.0.16", @@ -21165,8 +21139,7 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "peer": true + "dev": true }, "acorn-jsx": { "version": "5.3.2", @@ -21840,7 +21813,8 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", - "dev": true + "dev": true, + "peer": true }, "cross-spawn": { "version": "7.0.6", @@ -22433,7 +22407,6 @@ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", "dev": true, - "peer": true, "requires": { "@esbuild/aix-ppc64": "0.25.0", "@esbuild/android-arm": "0.25.0", @@ -22509,7 +22482,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, - "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -24105,8 +24077,7 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, - "peer": true + "dev": true }, "js-sdsl": { "version": "4.3.0", @@ -24500,7 +24471,6 @@ "version": "2.8.11", "resolved": "https://registry.npmjs.org/libp2p/-/libp2p-2.8.11.tgz", "integrity": "sha512-EjkyN0CI6uP+e4OOkEcZvhbZtlwFl4Y0rkkMvDbXmcfILX4E4n/jKE4Ppoc1qhNufxToxVWCMDS2ipniQgiYaw==", - "peer": true, "requires": { "@chainsafe/is-ip": "^2.1.0", "@chainsafe/netmask": "^2.0.0", @@ -25385,7 +25355,6 @@ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, - "peer": true, "requires": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -25397,7 +25366,6 @@ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", "dev": true, - "peer": true, "requires": { "lilconfig": "^3.1.1" } @@ -26500,7 +26468,8 @@ "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 + "dev": true, + "peer": true }, "sublevel-pouchdb": { "version": "9.0.0", @@ -26555,7 +26524,6 @@ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.41.1.tgz", "integrity": "sha512-0a/huwc8e2es+7KFi70esqsReRfRbrT8h1cJSY/+z1lF0yKM6TT+//HYu28Yxstr50H7ifaqZRDGd0KuKDxP7w==", "dev": true, - "peer": true, "requires": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -26740,8 +26708,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "peer": true + "dev": true } } }, @@ -26839,7 +26806,6 @@ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, - "peer": true, "requires": { "esbuild": "~0.27.0", "fsevents": "~2.3.3", @@ -27139,8 +27105,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "peer": true + "dev": true }, "uint8-varint": { "version": "2.0.4", @@ -27241,7 +27206,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, - "peer": true, "requires": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -27472,8 +27436,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "peer": true + "dev": true } } }, @@ -27489,7 +27452,6 @@ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.16.tgz", "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", "dev": true, - "peer": true, "requires": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", @@ -27530,7 +27492,8 @@ "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", - "dev": true + "dev": true, + "peer": true }, "wait-port": { "version": "1.1.0", @@ -27899,8 +27862,7 @@ "version": "2.8.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "dev": true, - "peer": true + "dev": true }, "yargs": { "version": "17.7.2", diff --git a/src/apps/cli/.test.env b/src/apps/cli/.test.env new file mode 100644 index 0000000..641c73c --- /dev/null +++ b/src/apps/cli/.test.env @@ -0,0 +1,8 @@ +hostname=http://127.0.0.1:5989/ +dbname=livesync-test-db2 +minioEndpoint=http://127.0.0.1:9000 +username=admin +password=testpassword +accessKey=minioadmin +secretKey=minioadmin +bucketName=livesync-test-bucket \ No newline at end of file diff --git a/src/apps/cli/README.md b/src/apps/cli/README.md index 5f0d8ff..ca9a383 100644 --- a/src/apps/cli/README.md +++ b/src/apps/cli/README.md @@ -16,12 +16,12 @@ This CLI version is built using the same core as the Obsidian plugin: ``` CLI Main └─ LiveSyncBaseCore - ├─ HeadlessServiceHub (All services without Obsidian dependencies) - └─ ServiceModules (Ported from main.ts) + ├─ NodeServiceHub (All services without Obsidian dependencies) + └─ ServiceModules (wired by initialiseServiceModulesCLI) ├─ FileAccessCLI (Node.js FileSystemAdapter) ├─ StorageEventManagerCLI ├─ ServiceFileAccessCLI - ├─ ServiceDatabaseFileAccess + ├─ ServiceDatabaseFileAccessCLI ├─ ServiceFileHandler └─ ServiceRebuilder ``` @@ -33,10 +33,14 @@ CLI Main - Implements same interface as Obsidian's file system 2. **Service Modules** (`serviceModules/`) - - Direct port from `main.ts` `initialiseServiceModules` + - Initialised by `initialiseServiceModulesCLI` - All core sync functionality preserved -3. **Main Entry Point** (`main.ts`) +3. **Service Hub and Settings Services** (`services/`) + - `NodeServiceHub` provides the CLI service context + - Node-specific settings and key-value services are provided without Obsidian dependencies + +4. **Main Entry Point** (`main.ts`) - Command-line interface - Settings management (JSON file) - Graceful shutdown handling @@ -59,43 +63,43 @@ As you know, the CLI is designed to be used in a headless environment. Hence all ```bash # Sync local database with CouchDB (no files will be changed). -node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json sync +npm run cli -- /path/to/your-local-database --settings /path/to/settings.json sync # Push files to local database -node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json push /your/storage/file.md /vault/path/file.md +npm run cli -- /path/to/your-local-database --settings /path/to/settings.json push /your/storage/file.md /vault/path/file.md # Pull files from local database -node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json pull /vault/path/file.md /your/storage/file.md +npm run cli -- /path/to/your-local-database --settings /path/to/settings.json pull /vault/path/file.md /your/storage/file.md # Verbose logging -node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json --verbose +npm run cli -- /path/to/your-local-database --settings /path/to/settings.json --verbose # Apply setup URI to settings file (settings only; does not run synchronisation) -node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json setup "obsidian://setuplivesync?settings=..." +npm run cli -- /path/to/your-local-database --settings /path/to/settings.json setup "obsidian://setuplivesync?settings=..." # Put text from stdin into local database -echo "Hello from stdin" | node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json put /vault/path/file.md +echo "Hello from stdin" | npm run cli -- /path/to/your-local-database --settings /path/to/settings.json put /vault/path/file.md # Output a file from local database to stdout -node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json cat /vault/path/file.md +npm run cli -- /path/to/your-local-database --settings /path/to/settings.json cat /vault/path/file.md # Output a specific revision of a file from local database -node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json cat-rev /vault/path/file.md 3-abcdef +npm run cli -- /path/to/your-local-database --settings /path/to/settings.json cat-rev /vault/path/file.md 3-abcdef # Pull a specific revision of a file from local database to local storage -node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json pull-rev /vault/path/file.md /your/storage/file.old.md 3-abcdef +npm run cli -- /path/to/your-local-database --settings /path/to/settings.json pull-rev /vault/path/file.md /your/storage/file.old.md 3-abcdef # List files in local database -node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json ls /vault/path/ +npm run cli -- /path/to/your-local-database --settings /path/to/settings.json ls /vault/path/ # Show metadata for a file in local database -node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json info /vault/path/file.md +npm run cli -- /path/to/your-local-database --settings /path/to/settings.json info /vault/path/file.md # Mark a file as deleted in local database -node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json rm /vault/path/file.md +npm run cli -- /path/to/your-local-database --settings /path/to/settings.json rm /vault/path/file.md # Resolve conflict by keeping a specific revision -node dist/index.cjs /path/to/your-local-database --settings /path/to/settings.json resolve /vault/path/file.md 3-abcdef +npm run cli -- /path/to/your-local-database --settings /path/to/settings.json resolve /vault/path/file.md 3-abcdef ``` ### Configuration @@ -157,15 +161,21 @@ Commands: resolve Resolve conflict by keeping the specified revision ``` +Run via npm script: + +```bash +npm run cli -- [database-path] [options] [command] [command-args] +``` + `info` output fields: -- `ID`: Document ID -- `Revision`: Current revision -- `Conflicts`: Conflicted revisions, or `N/A` -- `Filename`: Basename of path -- `Path`: Vault-relative path -- `Size`: Size in bytes -- `PastRevisions`: Available non-current revisions +- `id`: Document ID +- `revision`: Current revision +- `conflicts`: Conflicted revisions, or `N/A` +- `filename`: Basename of path +- `path`: Vault-relative path +- `size`: Size in bytes +- `revisions`: Available non-current revisions - `Chunks`: Number of chunk IDs - `child: ...`: Chunk ID list @@ -187,9 +197,9 @@ TODO: Conflict and resolution checks for real local databases. Create default settings, apply a setup URI, then run one sync cycle. ```bash -node dist/index.cjs init-settings /data/livesync-settings.json -printf '%s\n' "$SETUP_PASSPHRASE" | node dist/index.cjs /data/vault --settings /data/livesync-settings.json setup "$SETUP_URI" -node dist/index.cjs /data/vault --settings /data/livesync-settings.json sync +npm run cli -- init-settings /data/livesync-settings.json +printf '%s\n' "$SETUP_PASSPHRASE" | npm run cli -- /data/vault --settings /data/livesync-settings.json setup "$SETUP_URI" +npm run cli -- /data/vault --settings /data/livesync-settings.json sync ``` ### 2. Scripted import and export @@ -197,8 +207,8 @@ node dist/index.cjs /data/vault --settings /data/livesync-settings.json sync Push local files into the database from automation, and pull them back for export or backup. ```bash -node dist/index.cjs /data/vault --settings /data/livesync-settings.json push ./note.md notes/note.md -node dist/index.cjs /data/vault --settings /data/livesync-settings.json pull notes/note.md ./exports/note.md +npm run cli -- /data/vault --settings /data/livesync-settings.json push ./note.md notes/note.md +npm run cli -- /data/vault --settings /data/livesync-settings.json pull notes/note.md ./exports/note.md ``` ### 3. Revision inspection and restore @@ -206,9 +216,9 @@ node dist/index.cjs /data/vault --settings /data/livesync-settings.json pull not List metadata, find an older revision, then restore it by content (`cat-rev`) or file output (`pull-rev`). ```bash -node dist/index.cjs /data/vault --settings /data/livesync-settings.json info notes/note.md -node dist/index.cjs /data/vault --settings /data/livesync-settings.json cat-rev notes/note.md 3-abcdef -node dist/index.cjs /data/vault --settings /data/livesync-settings.json pull-rev notes/note.md ./restore/note.old.md 3-abcdef +npm run cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md +npm run cli -- /data/vault --settings /data/livesync-settings.json cat-rev notes/note.md 3-abcdef +npm run cli -- /data/vault --settings /data/livesync-settings.json pull-rev notes/note.md ./restore/note.old.md 3-abcdef ``` ### 4. Conflict and cleanup workflow @@ -216,9 +226,9 @@ node dist/index.cjs /data/vault --settings /data/livesync-settings.json pull-rev Inspect conflicted revisions, resolve by keeping one revision, then delete obsolete files. ```bash -node dist/index.cjs /data/vault --settings /data/livesync-settings.json info notes/note.md -node dist/index.cjs /data/vault --settings /data/livesync-settings.json resolve notes/note.md 3-abcdef -node dist/index.cjs /data/vault --settings /data/livesync-settings.json rm notes/obsolete.md +npm run cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md +npm run cli -- /data/vault --settings /data/livesync-settings.json resolve notes/note.md 3-abcdef +npm run cli -- /data/vault --settings /data/livesync-settings.json rm notes/obsolete.md ``` ### 5. CI smoke test for content round-trip @@ -226,8 +236,8 @@ node dist/index.cjs /data/vault --settings /data/livesync-settings.json rm notes Validate that `put`/`cat` is behaving as expected in a pipeline. ```bash -echo "hello-ci" | node dist/index.cjs /data/vault --settings /data/livesync-settings.json put ci/test.md -node dist/index.cjs /data/vault --settings /data/livesync-settings.json cat ci/test.md +echo "hello-ci" | npm run cli -- /data/vault --settings /data/livesync-settings.json put ci/test.md +npm run cli -- /data/vault --settings /data/livesync-settings.json cat ci/test.md ``` ## Development @@ -236,26 +246,40 @@ node dist/index.cjs /data/vault --settings /data/livesync-settings.json cat ci/t ``` src/apps/cli/ -├── commands/ # Command dispatcher and command utilities +├── commands/ # Command dispatcher and command utilities │ ├── runCommand.ts │ ├── types.ts │ └── utils.ts -├── adapters/ # Node.js FileSystem Adapter +├── adapters/ # Node.js FileSystem Adapter +│ ├── NodeConversionAdapter.ts │ ├── NodeFileSystemAdapter.ts │ ├── NodePathAdapter.ts -│ ├── NodeTypeGuardAdapter.ts -│ ├── NodeConversionAdapter.ts │ ├── NodeStorageAdapter.ts -│ ├── NodeVaultAdapter.ts -│ └── NodeTypes.ts -├── managers/ # CLI-specific managers +│ ├── NodeTypeGuardAdapter.ts +│ ├── NodeTypes.ts +│ └── NodeVaultAdapter.ts +├── lib/ +│ └── pouchdb-node.ts +├── managers/ # CLI-specific managers │ ├── CLIStorageEventManagerAdapter.ts │ └── StorageEventManagerCLI.ts -├── serviceModules/ # Service modules (ported from main.ts) +├── serviceModules/ # Service modules (ported from main.ts) │ ├── CLIServiceModules.ts +│ ├── DatabaseFileAccess.ts │ ├── FileAccessCLI.ts -│ ├── ServiceFileAccessImpl.ts -│ └── DatabaseFileAccess.ts -├── main.ts # CLI entry point -└── README.md # This file +│ └── ServiceFileAccessImpl.ts +├── services/ +│ ├── NodeKeyValueDBService.ts +│ ├── NodeServiceHub.ts +│ └── NodeSettingService.ts +├── test/ +│ ├── test-push-pull-linux.sh +│ ├── test-setup-put-cat-linux.sh +│ └── test-sync-two-local-databases-linux.sh +├── .gitignore +├── main.ts # CLI entry point +├── package.json +├── README.md # This file +├── tsconfig.json +└── vite.config.ts ``` diff --git a/src/apps/cli/commands/runCommand.ts b/src/apps/cli/commands/runCommand.ts index 637f9a5..9137920 100644 --- a/src/apps/cli/commands/runCommand.ts +++ b/src/apps/cli/commands/runCommand.ts @@ -237,22 +237,19 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext }) .map((entry: { rev: string }) => entry.rev); const pastRevisionsText = - pastRevisions.length > 0 ? pastRevisions.map((rev: string) => ` rev: ${rev}`) : [" N/A"]; - - const out = - [ - `ID: ${doc._id}`, - `Revision: ${doc._rev ?? ""}`, - `Conflicts: ${conflictsText}`, - `Filename: ${filename}`, - `Path: ${docPath}`, - `Size: ${doc.size}`, - `PastRevisions:`, - ...pastRevisionsText, - `Chunks: ${children.length}`, - ...children.map((id) => ` child: ${id}`), - ].join("\n") + "\n"; - process.stdout.write(out); + pastRevisions.length > 0 ? pastRevisions.map((rev: string) => `${rev}`) : ["N/A"]; + const out = { + id: doc._id, + revision: doc._rev ?? "", + conflicts: conflictsText, + filename: filename, + path: docPath, + size: doc.size, + revisions: pastRevisionsText, + chunks: children.length, + children: children, + }; + process.stdout.write(JSON.stringify(out, null, 2) + "\n"); return true; } diff --git a/src/apps/cli/package.json b/src/apps/cli/package.json index ddfe3ef..327e4fe 100644 --- a/src/apps/cli/package.json +++ b/src/apps/cli/package.json @@ -2,14 +2,16 @@ "name": "self-hosted-livesync-cli", "private": true, "version": "0.0.0", + "main": "dist/index.cjs", "type": "module", "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview", - "run": "node dist/index.cjs", - "buildRun": "npm run build && npm run", - "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json" + "cli": "node dist/index.cjs", + "buildRun": "npm run build && npm run cli --", + "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json", + "test:e2e:two-vaults": "bash test/test-e2e-two-vaults-with-docker-linux.sh" }, "dependencies": {}, "devDependencies": {} diff --git a/src/apps/cli/test/test-e2e-two-vaults-with-docker-linux.sh b/src/apps/cli/test/test-e2e-two-vaults-with-docker-linux.sh new file mode 100644 index 0000000..b23b00c --- /dev/null +++ b/src/apps/cli/test/test-e2e-two-vaults-with-docker-linux.sh @@ -0,0 +1,247 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +CLI_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)" +cd "$CLI_DIR" + +# verbose +CLI_CMD=(npm run cli -- -v ) +RUN_BUILD="${RUN_BUILD:-1}" +KEEP_TEST_DATA="${KEEP_TEST_DATA:-0}" +TEST_ENV_FILE="${TEST_ENV_FILE:-$CLI_DIR/.test.env}" + +if [[ ! -f "$TEST_ENV_FILE" ]]; then + echo "[ERROR] test env file not found: $TEST_ENV_FILE" >&2 + exit 1 +fi + +set -a +source "$TEST_ENV_FILE" +set +a + +for var in hostname dbname username password; do + if [[ -z "${!var:-}" ]]; then + echo "[ERROR] required variable '$var' is missing in $TEST_ENV_FILE" >&2 + exit 1 + fi +done + +COUCHDB_URI="${hostname%/}" +DB_SUFFIX="$(date +%s)-$RANDOM" +COUCHDB_DBNAME="${dbname}-${DB_SUFFIX}" + +VAULT_ROOT="$CLI_DIR/.livesync" +VAULT_A="$VAULT_ROOT/testvault_a" +VAULT_B="$VAULT_ROOT/testvault_b" +SETTINGS_A="$VAULT_ROOT/test-settings-a.json" +SETTINGS_B="$VAULT_ROOT/test-settings-b.json" +WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-e2e.XXXXXX")" + +cleanup() { + local exit_code=$? + bash "$CLI_DIR/util/couchdb-stop.sh" >/dev/null 2>&1 || true + if [[ "$KEEP_TEST_DATA" != "1" ]]; then + rm -rf "$VAULT_A" "$VAULT_B" "$SETTINGS_A" "$SETTINGS_B" "$WORK_DIR" + else + echo "[INFO] KEEP_TEST_DATA=1, preserving test artefacts" + echo " vault a: $VAULT_A" + echo " vault b: $VAULT_B" + echo " settings: $SETTINGS_A, $SETTINGS_B" + echo " work dir: $WORK_DIR" + fi + exit "$exit_code" +} +trap cleanup EXIT + +run_cli() { + "${CLI_CMD[@]}" "$@" +} + +run_cli_a() { + run_cli "$VAULT_A" --settings "$SETTINGS_A" "$@" +} + +run_cli_b() { + run_cli "$VAULT_B" --settings "$SETTINGS_B" "$@" +} + +assert_contains() { + local haystack="$1" + local needle="$2" + local message="$3" + if ! grep -Fq "$needle" <<< "$haystack"; then + echo "[FAIL] $message" >&2 + echo "[FAIL] expected to find: $needle" >&2 + echo "[FAIL] actual output:" >&2 + echo "$haystack" >&2 + exit 1 + fi +} + +assert_equal() { + local expected="$1" + local actual="$2" + local message="$3" + if [[ "$expected" != "$actual" ]]; then + echo "[FAIL] $message" >&2 + echo "[FAIL] expected: $expected" >&2 + echo "[FAIL] actual: $actual" >&2 + exit 1 + fi +} + +assert_command_fails() { + local message="$1" + shift + set +e + "$@" >"$WORK_DIR/failed-command.log" 2>&1 + local exit_code=$? + set -e + if [[ "$exit_code" -eq 0 ]]; then + echo "[FAIL] $message" >&2 + cat "$WORK_DIR/failed-command.log" >&2 + exit 1 + fi +} + +sanitise_cat_stdout() { + sed '/^\[CLIWatchAdapter\] File watching is not enabled in CLI version$/d' +} + +sync_both() { + run_cli_a sync >/dev/null + run_cli_b sync >/dev/null +} + +curl_json() { + curl -4 -sS --fail --connect-timeout 3 --max-time 15 "$@" +} + +init_settings() { + local settings_file="$1" + run_cli init-settings --force "$settings_file" >/dev/null + SETTINGS_FILE="$settings_file" \ + COUCHDB_URI="$COUCHDB_URI" \ + COUCHDB_USER="$username" \ + COUCHDB_PASSWORD="$password" \ + COUCHDB_DBNAME="$COUCHDB_DBNAME" \ + node <<'NODE' +const fs = require("node:fs"); +const settingsPath = process.env.SETTINGS_FILE; +const data = JSON.parse(fs.readFileSync(settingsPath, "utf-8")); + +data.couchDB_URI = process.env.COUCHDB_URI; +data.couchDB_USER = process.env.COUCHDB_USER; +data.couchDB_PASSWORD = process.env.COUCHDB_PASSWORD; +data.couchDB_DBNAME = process.env.COUCHDB_DBNAME; +data.liveSync = true; +data.syncOnStart = false; +data.syncOnSave = false; +data.usePluginSync = false; +data.isConfigured = true; + +fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8"); +NODE +cat "$settings_file" +} + +echo "[INFO] stopping leftover CouchDB container if present" +bash "$CLI_DIR/util/couchdb-stop.sh" >/dev/null 2>&1 || true + +echo "[INFO] starting CouchDB test container" +bash "$CLI_DIR/util/couchdb-start.sh" + +echo "status" +docker ps --filter "name=couchdb-test" + +echo "[INFO] initialising CouchDB test container" +bash "$CLI_DIR/util/couchdb-init.sh" + +echo "[INFO] CouchDB create test database: $COUCHDB_DBNAME" +until (curl_json -X PUT --user "${username}:${password}" "${hostname}/${COUCHDB_DBNAME}" ); do sleep 5; done + +if [[ "$RUN_BUILD" == "1" ]]; then + echo "[INFO] building CLI" + npm run build +fi + +echo "[INFO] preparing vaults and settings" +rm -rf "$VAULT_A" "$VAULT_B" "$SETTINGS_A" "$SETTINGS_B" +mkdir -p "$VAULT_A" "$VAULT_B" +init_settings "$SETTINGS_A" +init_settings "$SETTINGS_B" + +echo "[INFO] test DB: $COUCHDB_DBNAME" + +TARGET_A_ONLY="e2e/a-only-info.md" +TARGET_SYNC="e2e/sync-info.md" +TARGET_PUSH="e2e/pushed-from-a.md" +TARGET_PUT="e2e/put-from-a.md" +TARGET_CONFLICT="e2e/conflict.md" + +echo "[CASE] A puts and A can get info" +printf 'alpha-from-a\n' | run_cli_a put "$TARGET_A_ONLY" >/dev/null +INFO_A_ONLY="$(run_cli_a info "$TARGET_A_ONLY")" +assert_contains "$INFO_A_ONLY" "\"path\": \"$TARGET_A_ONLY\"" "A info should include path after put" +echo "[PASS] A put/info" + +echo "[CASE] A puts, both sync, and B can get info" +printf 'visible-after-sync\n' | run_cli_a put "$TARGET_SYNC" >/dev/null +sync_both +INFO_B_SYNC="$(run_cli_b info "$TARGET_SYNC")" +assert_contains "$INFO_B_SYNC" "\"path\": \"$TARGET_SYNC\"" "B info should include path after sync" +echo "[PASS] sync A->B and B info" + +echo "[CASE] A pushes and puts, both sync, and B can pull and cat" +PUSH_SRC="$WORK_DIR/push-source.txt" +PULL_DST="$WORK_DIR/pull-destination.txt" +printf 'pushed-content-%s\n' "$DB_SUFFIX" > "$PUSH_SRC" +run_cli_a push "$PUSH_SRC" "$TARGET_PUSH" >/dev/null +printf 'put-content-%s\n' "$DB_SUFFIX" | run_cli_a put "$TARGET_PUT" >/dev/null +sync_both +run_cli_b pull "$TARGET_PUSH" "$PULL_DST" >/dev/null +if ! cmp -s "$PUSH_SRC" "$PULL_DST"; then + echo "[FAIL] B pull result does not match pushed source" >&2 + echo "--- source ---" >&2 + cat "$PUSH_SRC" >&2 + echo "--- pulled ---" >&2 + cat "$PULL_DST" >&2 + exit 1 +fi +CAT_B_PUT="$(run_cli_b cat "$TARGET_PUT" | sanitise_cat_stdout)" +assert_equal "put-content-$DB_SUFFIX" "$CAT_B_PUT" "B cat should return A put content" +echo "[PASS] push/pull and put/cat across vaults" + +echo "[CASE] A removes, both sync, and B can no longer cat" +run_cli_a rm "$TARGET_PUT" >/dev/null +sync_both +assert_command_fails "B cat should fail after A removed the file and synced" run_cli_b cat "$TARGET_PUT" +echo "[PASS] rm is replicated" + +echo "[CASE] verify conflict detection" +printf 'conflict-base\n' | run_cli_a put "$TARGET_CONFLICT" >/dev/null +sync_both +INFO_B_BASE="$(run_cli_b info "$TARGET_CONFLICT")" +assert_contains "$INFO_B_BASE" "\"path\": \"$TARGET_CONFLICT\"" "B should be able to info before creating conflict" + +printf 'conflict-from-a-%s\n' "$DB_SUFFIX" | run_cli_a put "$TARGET_CONFLICT" >/dev/null +printf 'conflict-from-b-%s\n' "$DB_SUFFIX" | run_cli_b put "$TARGET_CONFLICT" >/dev/null + +run_cli_a sync >/dev/null +run_cli_b sync >/dev/null +run_cli_a sync >/dev/null + +INFO_A_CONFLICT="$(run_cli_a info "$TARGET_CONFLICT")" +INFO_B_CONFLICT="$(run_cli_b info "$TARGET_CONFLICT")" +if grep -qF '"conflicts": "N/A"' <<< "$INFO_A_CONFLICT" && grep -qF '"conflicts": "N/A"' <<< "$INFO_B_CONFLICT"; then + echo "[FAIL] conflict was expected but both A and B show Conflicts: N/A" >&2 + echo "--- A info ---" >&2 + echo "$INFO_A_CONFLICT" >&2 + echo "--- B info ---" >&2 + echo "$INFO_B_CONFLICT" >&2 + exit 1 +fi +echo "[PASS] conflict detected by info" + +echo "[PASS] all requested E2E scenarios completed" \ No newline at end of file diff --git a/src/apps/cli/test/test-push-pull-linux.sh b/src/apps/cli/test/test-push-pull-linux.sh index ca9a846..ffa73ef 100644 --- a/src/apps/cli/test/test-push-pull-linux.sh +++ b/src/apps/cli/test/test-push-pull-linux.sh @@ -5,7 +5,7 @@ SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" CLI_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)" cd "$CLI_DIR" -CLI_ENTRY="${CLI_ENTRY:-$CLI_DIR/dist/index.cjs}" +CLI_CMD=(npm run cli --) RUN_BUILD="${RUN_BUILD:-1}" REMOTE_PATH="${REMOTE_PATH:-test/push-pull.txt}" @@ -19,13 +19,12 @@ if [[ "$RUN_BUILD" == "1" ]]; then npm run build fi -if [[ ! -f "$CLI_ENTRY" ]]; then - echo "[ERROR] CLI entry not found: $CLI_ENTRY" >&2 - exit 1 -fi +run_cli() { + "${CLI_CMD[@]}" "$@" +} echo "[INFO] generating settings from DEFAULT_SETTINGS -> $SETTINGS_FILE" -node "$CLI_ENTRY" init-settings --force "$SETTINGS_FILE" +run_cli init-settings --force "$SETTINGS_FILE" if [[ -n "${COUCHDB_URI:-}" && -n "${COUCHDB_USER:-}" && -n "${COUCHDB_PASSWORD:-}" && -n "${COUCHDB_DBNAME:-}" ]]; then echo "[INFO] applying CouchDB env vars to generated settings" @@ -52,10 +51,10 @@ PULLED_FILE="$WORK_DIR/pull-result.txt" printf 'push-pull-test %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$SRC_FILE" echo "[INFO] push -> $REMOTE_PATH" -node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" push "$SRC_FILE" "$REMOTE_PATH" +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" push "$SRC_FILE" "$REMOTE_PATH" echo "[INFO] pull <- $REMOTE_PATH" -node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" pull "$REMOTE_PATH" "$PULLED_FILE" +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" pull "$REMOTE_PATH" "$PULLED_FILE" if cmp -s "$SRC_FILE" "$PULLED_FILE"; then echo "[PASS] push/pull roundtrip matched" diff --git a/src/apps/cli/test/test-setup-put-cat-linux.sh b/src/apps/cli/test/test-setup-put-cat-linux.sh index eff5a84..0e4be1e 100755 --- a/src/apps/cli/test/test-setup-put-cat-linux.sh +++ b/src/apps/cli/test/test-setup-put-cat-linux.sh @@ -6,7 +6,7 @@ CLI_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)" REPO_ROOT="$(cd -- "$CLI_DIR/../../.." && pwd)" cd "$CLI_DIR" -CLI_ENTRY="${CLI_ENTRY:-$CLI_DIR/dist/index.cjs}" +CLI_CMD=(npm run cli --) RUN_BUILD="${RUN_BUILD:-1}" REMOTE_PATH="${REMOTE_PATH:-test/setup-put-cat.txt}" SETUP_PASSPHRASE="${SETUP_PASSPHRASE:-setup-passphrase}" @@ -21,13 +21,12 @@ if [[ "$RUN_BUILD" == "1" ]]; then npm run build fi -if [[ ! -f "$CLI_ENTRY" ]]; then - echo "[ERROR] CLI entry not found: $CLI_ENTRY" >&2 - exit 1 -fi +run_cli() { + "${CLI_CMD[@]}" "$@" +} echo "[INFO] generating settings from DEFAULT_SETTINGS -> $SETTINGS_FILE" -node "$CLI_ENTRY" init-settings --force "$SETTINGS_FILE" +run_cli init-settings --force "$SETTINGS_FILE" echo "[INFO] creating setup URI from settings" SETUP_URI="$( @@ -57,7 +56,7 @@ mkdir -p "$VAULT_DIR/test" echo "[INFO] applying setup URI" SETUP_LOG="$WORK_DIR/setup-output.log" set +e -printf '%s\n' "$SETUP_PASSPHRASE" | node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" setup "$SETUP_URI" \ +printf '%s\n' "$SETUP_PASSPHRASE" | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" setup "$SETUP_URI" \ >"$SETUP_LOG" 2>&1 SETUP_EXIT=$? set -e @@ -78,11 +77,11 @@ SRC_FILE="$WORK_DIR/put-source.txt" printf 'setup-put-cat-test %s\nline-2\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$SRC_FILE" echo "[INFO] put -> $REMOTE_PATH" -cat "$SRC_FILE" | node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" put "$REMOTE_PATH" +cat "$SRC_FILE" | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" put "$REMOTE_PATH" echo "[INFO] cat <- $REMOTE_PATH" CAT_OUTPUT="$WORK_DIR/cat-output.txt" -node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" cat "$REMOTE_PATH" > "$CAT_OUTPUT" +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" cat "$REMOTE_PATH" > "$CAT_OUTPUT" CAT_OUTPUT_CLEAN="$WORK_DIR/cat-output-clean.txt" grep -v '^\[CLIWatchAdapter\] File watching is not enabled in CLI version$' "$CAT_OUTPUT" > "$CAT_OUTPUT_CLEAN" || true @@ -100,7 +99,7 @@ fi echo "[INFO] ls $REMOTE_PATH" LS_OUTPUT="$WORK_DIR/ls-output.txt" -node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" ls "$REMOTE_PATH" > "$LS_OUTPUT" +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" ls "$REMOTE_PATH" > "$LS_OUTPUT" LS_LINE="$(grep -F "$REMOTE_PATH" "$LS_OUTPUT" | head -n 1 || true)" if [[ -z "$LS_LINE" ]]; then @@ -129,12 +128,12 @@ fi echo "[PASS] ls output format matched" echo "[INFO] adding more files for ls test cases" -printf 'file-a\n' | node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" put test/a-first.txt >/dev/null -printf 'file-z\n' | node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" put test/z-last.txt >/dev/null +printf 'file-a\n' | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" put test/a-first.txt >/dev/null +printf 'file-z\n' | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" put test/z-last.txt >/dev/null echo "[INFO] ls test/ (prefix filter and sorting)" LS_PREFIX_OUTPUT="$WORK_DIR/ls-prefix-output.txt" -node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" ls test/ > "$LS_PREFIX_OUTPUT" +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" ls test/ > "$LS_PREFIX_OUTPUT" if [[ "$(wc -l < "$LS_PREFIX_OUTPUT")" -lt 3 ]]; then echo "[FAIL] ls prefix output expected at least 3 rows" >&2 @@ -164,7 +163,7 @@ echo "[PASS] ls prefix and sorting matched" echo "[INFO] ls no-match prefix" LS_EMPTY_OUTPUT="$WORK_DIR/ls-empty-output.txt" -node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" ls no-such-prefix/ > "$LS_EMPTY_OUTPUT" +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" ls no-such-prefix/ > "$LS_EMPTY_OUTPUT" if [[ -s "$LS_EMPTY_OUTPUT" ]]; then echo "[FAIL] ls no-match prefix should produce empty output" >&2 cat "$LS_EMPTY_OUTPUT" >&2 @@ -174,7 +173,7 @@ echo "[PASS] ls no-match prefix matched" echo "[INFO] info $REMOTE_PATH" INFO_OUTPUT="$WORK_DIR/info-output.txt" -node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" info "$REMOTE_PATH" > "$INFO_OUTPUT" +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" info "$REMOTE_PATH" > "$INFO_OUTPUT" # Check required label lines for label in "ID:" "Revision:" "Conflicts:" "Filename:" "Path:" "Size:" "Chunks:"; do @@ -225,7 +224,7 @@ echo "[PASS] info output format matched" echo "[INFO] info non-existent path" INFO_MISSING_EXIT=0 -node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" info no-such-file.md > /dev/null || INFO_MISSING_EXIT=$? +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" info no-such-file.md > /dev/null || INFO_MISSING_EXIT=$? if [[ "$INFO_MISSING_EXIT" -eq 0 ]]; then echo "[FAIL] info on non-existent file should exit non-zero" >&2 exit 1 @@ -233,17 +232,17 @@ fi echo "[PASS] info non-existent path returns non-zero" echo "[INFO] rm test/z-last.txt" -node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" rm test/z-last.txt > /dev/null +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" rm test/z-last.txt > /dev/null RM_CAT_EXIT=0 -node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" cat test/z-last.txt > /dev/null || RM_CAT_EXIT=$? +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" cat test/z-last.txt > /dev/null || RM_CAT_EXIT=$? if [[ "$RM_CAT_EXIT" -eq 0 ]]; then echo "[FAIL] rm target should not be readable by cat" >&2 exit 1 fi LS_AFTER_RM="$WORK_DIR/ls-after-rm.txt" -node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" ls test/ > "$LS_AFTER_RM" +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" ls test/ > "$LS_AFTER_RM" if grep -Fq $'test/z-last.txt\t' "$LS_AFTER_RM"; then echo "[FAIL] rm target should not appear in ls output" >&2 cat "$LS_AFTER_RM" >&2 @@ -252,7 +251,7 @@ fi echo "[PASS] rm removed target from visible entries" echo "[INFO] resolve test/a-first.txt using current revision" -RESOLVE_LS_LINE="$(node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" ls test/a-first.txt | head -n 1)" +RESOLVE_LS_LINE="$(run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" ls test/a-first.txt | head -n 1)" if [[ -z "$RESOLVE_LS_LINE" ]]; then echo "[FAIL] could not fetch revision for resolve test" >&2 exit 1 @@ -263,12 +262,12 @@ if [[ -z "$RESOLVE_REV" ]]; then exit 1 fi -node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" resolve test/a-first.txt "$RESOLVE_REV" > /dev/null +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" resolve test/a-first.txt "$RESOLVE_REV" > /dev/null echo "[PASS] resolve accepted current revision" echo "[INFO] resolve with non-existent revision" RESOLVE_BAD_EXIT=0 -node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" resolve test/a-first.txt 9-no-such-rev > /dev/null || RESOLVE_BAD_EXIT=$? +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" resolve test/a-first.txt 9-no-such-rev > /dev/null || RESOLVE_BAD_EXIT=$? if [[ "$RESOLVE_BAD_EXIT" -eq 0 ]]; then echo "[FAIL] resolve with non-existent revision should exit non-zero" >&2 exit 1 @@ -285,13 +284,13 @@ printf 'revision-v1\n' > "$REV_V1_FILE" printf 'revision-v2\n' > "$REV_V2_FILE" printf 'revision-v3\n' > "$REV_V3_FILE" -cat "$REV_V1_FILE" | node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" put "$REV_PATH" > /dev/null -cat "$REV_V2_FILE" | node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" put "$REV_PATH" > /dev/null -cat "$REV_V3_FILE" | node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" put "$REV_PATH" > /dev/null +cat "$REV_V1_FILE" | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" put "$REV_PATH" > /dev/null +cat "$REV_V2_FILE" | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" put "$REV_PATH" > /dev/null +cat "$REV_V3_FILE" | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" put "$REV_PATH" > /dev/null echo "[INFO] info $REV_PATH (past revisions)" REV_INFO_OUTPUT="$WORK_DIR/rev-info-output.txt" -node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" info "$REV_PATH" > "$REV_INFO_OUTPUT" +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" info "$REV_PATH" > "$REV_INFO_OUTPUT" PAST_REV="$(grep '^ rev: ' "$REV_INFO_OUTPUT" | head -n 1 | sed 's/^ rev: //')" if [[ -z "$PAST_REV" ]]; then @@ -302,7 +301,7 @@ fi echo "[INFO] cat-rev $REV_PATH @ $PAST_REV" REV_CAT_OUTPUT="$WORK_DIR/rev-cat-output.txt" -node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" cat-rev "$REV_PATH" "$PAST_REV" > "$REV_CAT_OUTPUT" +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" cat-rev "$REV_PATH" "$PAST_REV" > "$REV_CAT_OUTPUT" if cmp -s "$REV_CAT_OUTPUT" "$REV_V1_FILE" || cmp -s "$REV_CAT_OUTPUT" "$REV_V2_FILE"; then echo "[PASS] cat-rev matched one of the past revisions from info" @@ -321,7 +320,7 @@ fi echo "[INFO] pull-rev $REV_PATH @ $PAST_REV" REV_PULL_OUTPUT="$WORK_DIR/rev-pull-output.txt" -node "$CLI_ENTRY" "$VAULT_DIR" --settings "$SETTINGS_FILE" pull-rev "$REV_PATH" "$REV_PULL_OUTPUT" "$PAST_REV" > /dev/null +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" pull-rev "$REV_PATH" "$REV_PULL_OUTPUT" "$PAST_REV" > /dev/null if cmp -s "$REV_PULL_OUTPUT" "$REV_V1_FILE" || cmp -s "$REV_PULL_OUTPUT" "$REV_V2_FILE"; then echo "[PASS] pull-rev matched one of the past revisions from info" diff --git a/src/apps/cli/test/test-sync-two-local-databases-linux.sh b/src/apps/cli/test/test-sync-two-local-databases-linux.sh index e4f7eb6..8875943 100755 --- a/src/apps/cli/test/test-sync-two-local-databases-linux.sh +++ b/src/apps/cli/test/test-sync-two-local-databases-linux.sh @@ -6,7 +6,7 @@ SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" CLI_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)" cd "$CLI_DIR" -CLI_ENTRY="${CLI_ENTRY:-$CLI_DIR/dist/index.cjs}" +CLI_CMD=(npm run cli --) RUN_BUILD="${RUN_BUILD:-1}" COUCHDB_URI="${COUCHDB_URI:-}" COUCHDB_USER="${COUCHDB_USER:-}" @@ -26,10 +26,9 @@ if [[ "$RUN_BUILD" == "1" ]]; then npm run build fi -if [[ ! -f "$CLI_ENTRY" ]]; then - echo "[ERROR] CLI entry not found: $CLI_ENTRY" >&2 - exit 1 -fi +run_cli() { + "${CLI_CMD[@]}" "$@" +} DB_SUFFIX="$(date +%s)-$RANDOM" COUCHDB_DBNAME="${COUCHDB_DBNAME_BASE}-${DB_SUFFIX}" @@ -42,8 +41,8 @@ SETTINGS_A="$WORK_DIR/a-settings.json" SETTINGS_B="$WORK_DIR/b-settings.json" mkdir -p "$VAULT_A" "$VAULT_B" -node "$CLI_ENTRY" init-settings --force "$SETTINGS_A" >/dev/null -node "$CLI_ENTRY" init-settings --force "$SETTINGS_B" >/dev/null +run_cli init-settings --force "$SETTINGS_A" >/dev/null +run_cli init-settings --force "$SETTINGS_B" >/dev/null apply_settings() { local settings_file="$1" @@ -73,11 +72,11 @@ apply_settings "$SETTINGS_A" apply_settings "$SETTINGS_B" run_cli_a() { - node "$CLI_ENTRY" "$VAULT_A" --settings "$SETTINGS_A" "$@" + run_cli "$VAULT_A" --settings "$SETTINGS_A" "$@" } run_cli_b() { - node "$CLI_ENTRY" "$VAULT_B" --settings "$SETTINGS_B" "$@" + run_cli "$VAULT_B" --settings "$SETTINGS_B" "$@" } sync_a() { diff --git a/src/apps/cli/util/couchdb-init.sh b/src/apps/cli/util/couchdb-init.sh new file mode 100755 index 0000000..7537ef8 --- /dev/null +++ b/src/apps/cli/util/couchdb-init.sh @@ -0,0 +1,47 @@ +#!/bin/bash +set -euo pipefail + +if [[ -z "${hostname:-}" ]]; then + echo "ERROR: Hostname missing" + exit 1 +fi +if [[ -z "${username:-}" ]]; then + echo "ERROR: Username missing" + exit 1 +fi + +if [[ -z "${password:-}" ]]; then + echo "ERROR: Password missing" + exit 1 +fi +if [[ -z "${node:-}" ]]; then + echo "INFO: defaulting to _local" + node=_local +fi + +hostname="${hostname%/}" +# Podman environments often resolve localhost to ::1 while published ports are IPv4-only. +hostname="${hostname/localhost/127.0.0.1}" + +curl_json() { + curl -4 -sS --fail --connect-timeout 3 --max-time 15 "$@" +} + +echo "-- Configuring CouchDB by REST APIs... -->" +echo " Hostname: $hostname" +echo " Username: $username" + +until (curl_json -X POST "${hostname}/_cluster_setup" -H "Content-Type: application/json" -d "{\"action\":\"enable_single_node\",\"username\":\"${username}\",\"password\":\"${password}\",\"bind_address\":\"0.0.0.0\",\"port\":5984,\"singlenode\":true}" --user "${username}:${password}"); do sleep 5; done +until (curl_json -X PUT "${hostname}/_node/${node}/_config/chttpd/require_valid_user" -H "Content-Type: application/json" -d '"true"' --user "${username}:${password}"); do sleep 5; done +until (curl_json -X PUT "${hostname}/_node/${node}/_config/chttpd_auth/require_valid_user" -H "Content-Type: application/json" -d '"true"' --user "${username}:${password}"); do sleep 5; done +until (curl_json -X PUT "${hostname}/_node/${node}/_config/httpd/WWW-Authenticate" -H "Content-Type: application/json" -d '"Basic realm=\"couchdb\""' --user "${username}:${password}"); do sleep 5; done +until (curl_json -X PUT "${hostname}/_node/${node}/_config/httpd/enable_cors" -H "Content-Type: application/json" -d '"true"' --user "${username}:${password}"); do sleep 5; done +until (curl_json -X PUT "${hostname}/_node/${node}/_config/chttpd/enable_cors" -H "Content-Type: application/json" -d '"true"' --user "${username}:${password}"); do sleep 5; done +until (curl_json -X PUT "${hostname}/_node/${node}/_config/chttpd/max_http_request_size" -H "Content-Type: application/json" -d '"4294967296"' --user "${username}:${password}"); do sleep 5; done +until (curl_json -X PUT "${hostname}/_node/${node}/_config/couchdb/max_document_size" -H "Content-Type: application/json" -d '"50000000"' --user "${username}:${password}"); do sleep 5; done +until (curl_json -X PUT "${hostname}/_node/${node}/_config/cors/credentials" -H "Content-Type: application/json" -d '"true"' --user "${username}:${password}"); do sleep 5; done +until (curl_json -X PUT "${hostname}/_node/${node}/_config/cors/origins" -H "Content-Type: application/json" -d '"*"' --user "${username}:${password}"); do sleep 5; done + +# Create test database +until (curl_json -X PUT --user "${username}:${password}" "${hostname}/${dbname}" >/dev/null); do sleep 5; done +echo "<-- Configuring CouchDB by REST APIs Done!" diff --git a/src/apps/cli/util/couchdb-start.sh b/src/apps/cli/util/couchdb-start.sh new file mode 100755 index 0000000..038457f --- /dev/null +++ b/src/apps/cli/util/couchdb-start.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -e +echo "username: $username" +docker run -d --name couchdb-test -p 5989:5984 -e COUCHDB_USER=$username -e COUCHDB_PASSWORD=$password -e COUCHDB_SINGLE_NODE=y couchdb:3.5.0 \ No newline at end of file diff --git a/src/apps/cli/util/couchdb-stop.sh b/src/apps/cli/util/couchdb-stop.sh new file mode 100755 index 0000000..bf423f4 --- /dev/null +++ b/src/apps/cli/util/couchdb-stop.sh @@ -0,0 +1,3 @@ +#!/bin/bash +docker stop couchdb-test +docker rm couchdb-test \ No newline at end of file diff --git a/src/apps/cli/util/minio-init.sh b/src/apps/cli/util/minio-init.sh new file mode 100755 index 0000000..353832b --- /dev/null +++ b/src/apps/cli/util/minio-init.sh @@ -0,0 +1,47 @@ +#!/bin/bash +set -e +cat >/tmp/mybucket-rw.json < +# +# http://localhost:63315 +# http://localhost:63316 +# http://localhost +# GET +# PUT +# POST +# DELETE +# HEAD +# * +# +# " > /tmp/cors.xml +# docker run --rm --network host -v /tmp/mybucket-rw.json:/tmp/mybucket-rw.json --entrypoint=/bin/sh minio/mc -c " +# mc alias set myminio $minioEndpoint $username $password +# mc mb --ignore-existing myminio/$bucketName +# mc admin policy create myminio my-custom-policy /tmp/mybucket-rw.json +# echo 'Creating service account for user $username with access key $accessKey' +# mc admin user svcacct add --access-key '$accessKey' --secret-key '$secretKey' myminio '$username' +# mc admin policy attach myminio my-custom-policy --user '$accessKey' +# echo 'Verifying policy and user creation:' +# mc admin user svcacct info myminio '$accessKey' +# " + +docker run --rm --network host -v /tmp/mybucket-rw.json:/tmp/mybucket-rw.json --entrypoint=/bin/sh minio/mc -c " + mc alias set myminio $minioEndpoint $accessKey $secretKey + mc mb --ignore-existing myminio/$bucketName +" \ No newline at end of file diff --git a/src/apps/cli/util/minio-start.sh b/src/apps/cli/util/minio-start.sh new file mode 100755 index 0000000..4554786 --- /dev/null +++ b/src/apps/cli/util/minio-start.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker run -d --name minio-test -p 9000:9000 -p 9001:9001 -e MINIO_ROOT_USER=$accessKey -e MINIO_ROOT_PASSWORD=$secretKey -e MINIO_SERVER_URL=$minioEndpoint minio/minio server /data --console-address ':9001' \ No newline at end of file diff --git a/src/apps/cli/util/minio-stop.sh b/src/apps/cli/util/minio-stop.sh new file mode 100755 index 0000000..08703b7 --- /dev/null +++ b/src/apps/cli/util/minio-stop.sh @@ -0,0 +1,3 @@ +#!/bin/bash +docker stop minio-test +docker rm minio-test \ No newline at end of file diff --git a/src/lib b/src/lib index 3ce1f81..41e2340 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 3ce1f81a21d6a5278ff876478a89fca5ca6fbbeb +Subproject commit 41e234023530cea101473c5d2f781941b6bbc9e8 From d4aedf59f334f3e945f3df9ce1bad1bc7c86e707 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 12 Mar 2026 18:20:55 +0900 Subject: [PATCH 067/339] A- Add more tests. - Object Storage support has also been confirmed (and fixed) in CLI. --- package-lock.json | 86 +++- src/apps/cli/commands/runCommand.ts | 9 +- src/apps/cli/commands/runCommand.unit.spec.ts | 204 ++++++++ src/apps/cli/commands/utils.ts | 8 +- src/apps/cli/commands/utils.unit.spec.ts | 29 ++ src/apps/cli/main.ts | 32 +- src/apps/cli/main.unit.spec.ts | 61 +++ src/apps/cli/package.json | 9 +- .../cli/test/test-e2e-two-vaults-common.sh | 445 ++++++++++++++++++ .../cli/test/test-e2e-two-vaults-matrix.sh | 31 ++ .../test-e2e-two-vaults-with-docker-linux.sh | 246 +--------- src/apps/cli/vite.config.ts | 11 +- src/lib | 2 +- 13 files changed, 892 insertions(+), 281 deletions(-) create mode 100644 src/apps/cli/commands/runCommand.unit.spec.ts create mode 100644 src/apps/cli/commands/utils.unit.spec.ts create mode 100644 src/apps/cli/main.unit.spec.ts create mode 100755 src/apps/cli/test/test-e2e-two-vaults-common.sh create mode 100755 src/apps/cli/test/test-e2e-two-vaults-matrix.sh mode change 100644 => 100755 src/apps/cli/test/test-e2e-two-vaults-with-docker-linux.sh diff --git a/package-lock.json b/package-lock.json index dfb7f22..d89d159 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1939,6 +1939,7 @@ "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.4.tgz", "integrity": "sha512-pUxEGmR+uu21OG/icAovjlu1fcYJzyVhhT0rsCrn+zi+nHtrS43Bp9KPn9KGa4NMspCUE++nkyiqziuIvJdwzw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", @@ -2005,6 +2006,7 @@ "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.4.tgz", "integrity": "sha512-T7ifGmb+awJEcp542Ek4HtNfBxcBrnuk1ggUdqyFEdsXHdq7+wVlhvE6YukTL7NS8hIkEfL7TMAPx/uCNqt30g==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@firebase/app": "0.14.4", "@firebase/component": "0.7.0", @@ -2020,7 +2022,8 @@ "version": "0.9.3", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@firebase/app/node_modules/idb": { "version": "7.1.1", @@ -2489,6 +2492,7 @@ "integrity": "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==", "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.1.0" }, @@ -3081,8 +3085,7 @@ "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", - "peer": true + "license": "MIT" }, "node_modules/@multiformats/dns": { "version": "1.0.10", @@ -4944,6 +4947,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", @@ -5423,6 +5427,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", @@ -5627,6 +5632,7 @@ "integrity": "sha512-t4toy8X/YTnjYEPoY0pbDBg3EvDPg1elCDrfc+VupPHwoN/5/FNQ8Z+xBYIaEnOE2vVEyKwqYBzZ9h9rJtZVcg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/mocker": "4.0.16", "@vitest/utils": "4.0.16", @@ -5650,6 +5656,7 @@ "integrity": "sha512-I2Fy/ANdphi1yI46d15o0M1M4M0UJrUiVKkH5oKeRZZCdPg0fw/cfTKZzv9Ge9eobtJYp4BGblMzXdXH0vcl5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/browser": "4.0.16", "@vitest/mocker": "4.0.16", @@ -6394,6 +6401,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7402,8 +7410,7 @@ "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -8286,6 +8293,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -8396,6 +8404,7 @@ "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -10768,6 +10777,7 @@ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -11319,6 +11329,7 @@ "resolved": "https://registry.npmjs.org/libp2p/-/libp2p-2.8.11.tgz", "integrity": "sha512-EjkyN0CI6uP+e4OOkEcZvhbZtlwFl4Y0rkkMvDbXmcfILX4E4n/jKE4Ppoc1qhNufxToxVWCMDS2ipniQgiYaw==", "license": "Apache-2.0 OR MIT", + "peer": true, "dependencies": { "@chainsafe/is-ip": "^2.1.0", "@chainsafe/netmask": "^2.0.0", @@ -12619,6 +12630,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -12643,6 +12655,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "lilconfig": "^3.1.1" }, @@ -14213,8 +14226,7 @@ "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/sublevel-pouchdb": { "version": "9.0.0", @@ -14281,6 +14293,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", @@ -14598,6 +14611,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -14725,6 +14739,7 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -15330,6 +15345,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15466,6 +15482,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -16026,6 +16043,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -16059,6 +16077,7 @@ "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", @@ -16154,8 +16173,7 @@ "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/wait-port": { "version": "1.1.0", @@ -16671,6 +16689,7 @@ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "dev": true, "license": "ISC", + "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -18018,6 +18037,7 @@ "version": "0.14.4", "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.4.tgz", "integrity": "sha512-pUxEGmR+uu21OG/icAovjlu1fcYJzyVhhT0rsCrn+zi+nHtrS43Bp9KPn9KGa4NMspCUE++nkyiqziuIvJdwzw==", + "peer": true, "requires": { "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", @@ -18071,6 +18091,7 @@ "version": "0.5.4", "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.4.tgz", "integrity": "sha512-T7ifGmb+awJEcp542Ek4HtNfBxcBrnuk1ggUdqyFEdsXHdq7+wVlhvE6YukTL7NS8hIkEfL7TMAPx/uCNqt30g==", + "peer": true, "requires": { "@firebase/app": "0.14.4", "@firebase/component": "0.7.0", @@ -18082,7 +18103,8 @@ "@firebase/app-types": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", - "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==" + "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", + "peer": true }, "@firebase/auth": { "version": "1.11.0", @@ -18410,6 +18432,7 @@ "version": "1.13.0", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.13.0.tgz", "integrity": "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==", + "peer": true, "requires": { "tslib": "^2.1.0" } @@ -18882,8 +18905,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, - "peer": true + "dev": true }, "@multiformats/dns": { "version": "1.0.10", @@ -20067,6 +20089,7 @@ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz", "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", "dev": true, + "peer": true, "requires": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "deepmerge": "^4.3.1", @@ -20494,6 +20517,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, + "peer": true, "requires": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", @@ -20601,6 +20625,7 @@ "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.0.16.tgz", "integrity": "sha512-t4toy8X/YTnjYEPoY0pbDBg3EvDPg1elCDrfc+VupPHwoN/5/FNQ8Z+xBYIaEnOE2vVEyKwqYBzZ9h9rJtZVcg==", "dev": true, + "peer": true, "requires": { "@vitest/mocker": "4.0.16", "@vitest/utils": "4.0.16", @@ -20617,6 +20642,7 @@ "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.0.16.tgz", "integrity": "sha512-I2Fy/ANdphi1yI46d15o0M1M4M0UJrUiVKkH5oKeRZZCdPg0fw/cfTKZzv9Ge9eobtJYp4BGblMzXdXH0vcl5g==", "dev": true, + "peer": true, "requires": { "@vitest/browser": "4.0.16", "@vitest/mocker": "4.0.16", @@ -21139,7 +21165,8 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true + "dev": true, + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -21813,8 +21840,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", - "dev": true, - "peer": true + "dev": true }, "cross-spawn": { "version": "7.0.6", @@ -22407,6 +22433,7 @@ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", "dev": true, + "peer": true, "requires": { "@esbuild/aix-ppc64": "0.25.0", "@esbuild/android-arm": "0.25.0", @@ -22482,6 +22509,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, + "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -24077,7 +24105,8 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true + "dev": true, + "peer": true }, "js-sdsl": { "version": "4.3.0", @@ -24471,6 +24500,7 @@ "version": "2.8.11", "resolved": "https://registry.npmjs.org/libp2p/-/libp2p-2.8.11.tgz", "integrity": "sha512-EjkyN0CI6uP+e4OOkEcZvhbZtlwFl4Y0rkkMvDbXmcfILX4E4n/jKE4Ppoc1qhNufxToxVWCMDS2ipniQgiYaw==", + "peer": true, "requires": { "@chainsafe/is-ip": "^2.1.0", "@chainsafe/netmask": "^2.0.0", @@ -25355,6 +25385,7 @@ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, + "peer": true, "requires": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -25366,6 +25397,7 @@ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", "dev": true, + "peer": true, "requires": { "lilconfig": "^3.1.1" } @@ -26468,8 +26500,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, - "peer": true + "dev": true }, "sublevel-pouchdb": { "version": "9.0.0", @@ -26524,6 +26555,7 @@ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.41.1.tgz", "integrity": "sha512-0a/huwc8e2es+7KFi70esqsReRfRbrT8h1cJSY/+z1lF0yKM6TT+//HYu28Yxstr50H7ifaqZRDGd0KuKDxP7w==", "dev": true, + "peer": true, "requires": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -26708,7 +26740,8 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true + "dev": true, + "peer": true } } }, @@ -26806,6 +26839,7 @@ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, + "peer": true, "requires": { "esbuild": "~0.27.0", "fsevents": "~2.3.3", @@ -27105,7 +27139,8 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true + "dev": true, + "peer": true }, "uint8-varint": { "version": "2.0.4", @@ -27206,6 +27241,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, + "peer": true, "requires": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -27436,7 +27472,8 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true + "dev": true, + "peer": true } } }, @@ -27452,6 +27489,7 @@ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.16.tgz", "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", "dev": true, + "peer": true, "requires": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", @@ -27492,8 +27530,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, - "peer": true + "dev": true }, "wait-port": { "version": "1.1.0", @@ -27862,7 +27899,8 @@ "version": "2.8.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "dev": true + "dev": true, + "peer": true }, "yargs": { "version": "17.7.2", diff --git a/src/apps/cli/commands/runCommand.ts b/src/apps/cli/commands/runCommand.ts index 9137920..18ccb35 100644 --- a/src/apps/cli/commands/runCommand.ts +++ b/src/apps/cli/commands/runCommand.ts @@ -151,7 +151,8 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext if (body.type === "text/plain") { process.stdout.write(await body.text()); } else { - process.stdout.write(Buffer.from(await body.arrayBuffer())); + const buffer = Buffer.from(await body.arrayBuffer()); + process.stdout.write(new Uint8Array(buffer)); } return true; } @@ -178,7 +179,8 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext if (body.type === "text/plain") { process.stdout.write(await body.text()); } else { - process.stdout.write(Buffer.from(await body.arrayBuffer())); + const buffer = Buffer.from(await body.arrayBuffer()); + process.stdout.write(new Uint8Array(buffer)); } return true; } @@ -236,8 +238,7 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext return entry.status === "available"; }) .map((entry: { rev: string }) => entry.rev); - const pastRevisionsText = - pastRevisions.length > 0 ? pastRevisions.map((rev: string) => `${rev}`) : ["N/A"]; + const pastRevisionsText = pastRevisions.length > 0 ? pastRevisions.map((rev: string) => `${rev}`) : ["N/A"]; const out = { id: doc._id, revision: doc._rev ?? "", diff --git a/src/apps/cli/commands/runCommand.unit.spec.ts b/src/apps/cli/commands/runCommand.unit.spec.ts new file mode 100644 index 0000000..a616656 --- /dev/null +++ b/src/apps/cli/commands/runCommand.unit.spec.ts @@ -0,0 +1,204 @@ +import * as processSetting from "@lib/API/processSetting"; +import { configURIBase } from "@lib/common/models/shared.const"; +import { DEFAULT_SETTINGS } from "@lib/common/types"; +import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; +import { runCommand } from "./runCommand"; +import type { CLIOptions } from "./types"; +import * as commandUtils from "./utils"; + +function createCoreMock() { + return { + services: { + control: { + activated: Promise.resolve(), + applySettings: vi.fn(async () => {}), + }, + setting: { + applyPartial: vi.fn(async () => {}), + }, + }, + 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 makeOptions(command: CLIOptions["command"], commandArgs: string[]): CLIOptions { + return { + command, + commandArgs, + databasePath: "/tmp/vault", + verbose: false, + force: false, + }; +} + +async function createSetupURI(passphrase: string): Promise { + const settings = { + ...DEFAULT_SETTINGS, + couchDB_URI: "http://127.0.0.1:5984", + couchDB_DBNAME: "livesync-test-db", + couchDB_USER: "user", + couchDB_PASSWORD: "pass", + isConfigured: true, + } as any; + return await processSetting.encodeSettingsToSetupURI(settings, passphrase); +} + +describe("runCommand abnormal cases", () => { + const context = { + vaultPath: "/tmp/vault", + settingsPath: "/tmp/vault/.livesync/settings.json", + } as any; + + beforeEach(() => { + vi.restoreAllMocks(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("pull returns false for non-existing path", async () => { + const core = createCoreMock(); + core.serviceModules.fileHandler.dbToStorage.mockResolvedValue(false); + + const result = await runCommand(makeOptions("pull", ["missing.md", "/tmp/out.md"]), { + ...context, + core, + }); + + expect(result).toBe(false); + expect(core.serviceModules.fileHandler.dbToStorage).toHaveBeenCalled(); + }); + + it("pull-rev throws on empty revision", async () => { + const core = createCoreMock(); + + await expect( + runCommand(makeOptions("pull-rev", ["file.md", "/tmp/out.md", " "]), { + ...context, + core, + }) + ).rejects.toThrow("pull-rev requires a non-empty revision"); + }); + + it("pull-rev returns false for invalid revision", async () => { + const core = createCoreMock(); + core.serviceModules.databaseFileAccess.fetch.mockResolvedValue(undefined); + + const result = await runCommand(makeOptions("pull-rev", ["file.md", "/tmp/out.md", "9-invalid"]), { + ...context, + core, + }); + + expect(result).toBe(false); + expect(core.serviceModules.databaseFileAccess.fetch).toHaveBeenCalledWith("file.md", "9-invalid", true); + }); + + it("cat-rev throws on empty revision", async () => { + const core = createCoreMock(); + + await expect( + runCommand(makeOptions("cat-rev", ["file.md", " "]), { + ...context, + core, + }) + ).rejects.toThrow("cat-rev requires a non-empty revision"); + }); + + it("cat-rev returns false for invalid revision", async () => { + const core = createCoreMock(); + core.serviceModules.databaseFileAccess.fetch.mockResolvedValue(undefined); + + const result = await runCommand(makeOptions("cat-rev", ["file.md", "9-invalid"]), { + ...context, + core, + }); + + expect(result).toBe(false); + expect(core.serviceModules.databaseFileAccess.fetch).toHaveBeenCalledWith("file.md", "9-invalid", true); + }); + + it("push rejects when source file does not exist", async () => { + const core = createCoreMock(); + + await expect( + runCommand(makeOptions("push", ["/tmp/livesync-missing-src-file.md", "dst.md"]), { + ...context, + core, + }) + ).rejects.toMatchObject({ code: "ENOENT" }); + }); + + it("setup rejects invalid URI", async () => { + const core = createCoreMock(); + + await expect( + runCommand(makeOptions("setup", ["https://invalid.example/setup"]), { + ...context, + core, + }) + ).rejects.toThrow(`setup URI must start with ${configURIBase}`); + }); + + it("setup rejects empty passphrase", async () => { + const core = createCoreMock(); + vi.spyOn(commandUtils, "promptForPassphrase").mockRejectedValue(new Error("Passphrase is required")); + + await expect( + runCommand(makeOptions("setup", [`${configURIBase}dummy`]), { + ...context, + core, + }) + ).rejects.toThrow("Passphrase is required"); + }); + + it("setup accepts URI generated by encodeSettingsToSetupURI", async () => { + const core = createCoreMock(); + const passphrase = "correct-passphrase"; + const setupURI = await createSetupURI(passphrase); + vi.spyOn(commandUtils, "promptForPassphrase").mockResolvedValue(passphrase); + + const result = await runCommand(makeOptions("setup", [setupURI]), { + ...context, + core, + }); + + expect(result).toBe(true); + expect(core.services.setting.applyPartial).toHaveBeenCalledTimes(1); + expect(core.services.control.applySettings).toHaveBeenCalledTimes(1); + const [appliedSettings, saveImmediately] = core.services.setting.applyPartial.mock.calls[0]; + expect(saveImmediately).toBe(true); + expect(appliedSettings.couchDB_URI).toBe("http://127.0.0.1:5984"); + expect(appliedSettings.couchDB_DBNAME).toBe("livesync-test-db"); + expect(appliedSettings.isConfigured).toBe(true); + expect(appliedSettings.useIndexedDBAdapter).toBe(false); + }); + + it("setup rejects encoded URI when passphrase is wrong", async () => { + const core = createCoreMock(); + const setupURI = await createSetupURI("correct-passphrase"); + vi.spyOn(commandUtils, "promptForPassphrase").mockResolvedValue("wrong-passphrase"); + + await expect( + runCommand(makeOptions("setup", [setupURI]), { + ...context, + core, + }) + ).rejects.toThrow(); + + expect(core.services.setting.applyPartial).not.toHaveBeenCalled(); + expect(core.services.control.applySettings).not.toHaveBeenCalled(); + }); +}); diff --git a/src/apps/cli/commands/utils.ts b/src/apps/cli/commands/utils.ts index d085822..f56940f 100644 --- a/src/apps/cli/commands/utils.ts +++ b/src/apps/cli/commands/utils.ts @@ -8,7 +8,13 @@ export function toArrayBuffer(data: Buffer): ArrayBuffer { export function toVaultRelativePath(inputPath: string, vaultPath: string): string { const stripped = inputPath.replace(/^[/\\]+/, ""); if (!path.isAbsolute(inputPath)) { - return stripped.replace(/\\/g, "/"); + const normalized = stripped.replace(/\\/g, "/"); + const resolved = path.resolve(vaultPath, normalized); + const rel = path.relative(vaultPath, resolved); + if (rel.startsWith("..") || path.isAbsolute(rel)) { + throw new Error(`Path ${inputPath} is outside of the local database directory`); + } + return rel.replace(/\\/g, "/"); } const resolved = path.resolve(inputPath); const rel = path.relative(vaultPath, resolved); diff --git a/src/apps/cli/commands/utils.unit.spec.ts b/src/apps/cli/commands/utils.unit.spec.ts new file mode 100644 index 0000000..e209bf7 --- /dev/null +++ b/src/apps/cli/commands/utils.unit.spec.ts @@ -0,0 +1,29 @@ +import * as path from "path"; +import { describe, expect, it } from "vitest"; +import { toVaultRelativePath } from "./utils"; + +describe("toVaultRelativePath", () => { + const vaultPath = path.resolve("/tmp/livesync-vault"); + + it("rejects absolute paths outside vault", () => { + expect(() => toVaultRelativePath("/etc/passwd", vaultPath)).toThrow("outside of the local database directory"); + }); + + it("normalizes leading slash for absolute path inside vault", () => { + const absoluteInsideVault = path.join(vaultPath, "notes", "foo.md"); + expect(toVaultRelativePath(absoluteInsideVault, vaultPath)).toBe("notes/foo.md"); + }); + + it("normalizes Windows-style separators", () => { + expect(toVaultRelativePath("notes\\daily\\2026-03-12.md", vaultPath)).toBe("notes/daily/2026-03-12.md"); + }); + + it("returns vault-relative path for another absolute path inside vault", () => { + const absoluteInsideVault = path.join(vaultPath, "docs", "inside.md"); + expect(toVaultRelativePath(absoluteInsideVault, vaultPath)).toBe("docs/inside.md"); + }); + + it("rejects relative path traversal that escapes vault", () => { + expect(() => toVaultRelativePath("../escape.md", vaultPath)).toThrow("outside of the local database directory"); + }); +}); diff --git a/src/apps/cli/main.ts b/src/apps/cli/main.ts index 64b3578..58817b3 100644 --- a/src/apps/cli/main.ts +++ b/src/apps/cli/main.ts @@ -22,6 +22,7 @@ if (!("localStorage" in globalThis)) { import * as fs from "fs/promises"; import * as path from "path"; +import { pathToFileURL } from "node:url"; import { NodeServiceContext, NodeServiceHub } from "./services/NodeServiceHub"; import { LiveSyncBaseCore } from "../../LiveSyncBaseCore"; import { initialiseServiceModulesCLI } from "./serviceModules/CLIServiceModules"; @@ -93,7 +94,7 @@ Examples: `); } -function parseArgs(): CLIOptions { +export function parseArgs(): CLIOptions { const args = process.argv.slice(2); if (args.length === 0 || args.includes("--help") || args.includes("-h")) { @@ -157,6 +158,11 @@ function parseArgs(): CLIOptions { process.exit(1); } + if (command === "daemon" && commandArgs.length > 0) { + console.error(`Error: Unknown command '${commandArgs[0]}'`); + process.exit(1); + } + return { databasePath, settingsPath, @@ -323,7 +329,7 @@ async function main() { console.error(`[Error] Failed to initialize LiveSync`); process.exit(1); } - + await core.services.setting.suspendAllSync(); await core.services.control.onReady(); infoLog(`[Ready] LiveSync is running`); @@ -368,8 +374,20 @@ async function main() { } } -// Run main -main().catch((error) => { - console.error(`[Fatal Error]`, error); - process.exit(1); -}); +// Run main only when invoked as the entrypoint, not when imported by tests. +const isEntryPoint = (() => { + const argv1 = process.argv[1]; + if (!argv1) return false; + try { + return import.meta.url === pathToFileURL(argv1).href; + } catch { + return false; + } +})(); + +if (isEntryPoint) { + main().catch((error) => { + console.error(`[Fatal Error]`, error); + process.exit(1); + }); +} diff --git a/src/apps/cli/main.unit.spec.ts b/src/apps/cli/main.unit.spec.ts new file mode 100644 index 0000000..032a2b4 --- /dev/null +++ b/src/apps/cli/main.unit.spec.ts @@ -0,0 +1,61 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { parseArgs } from "./main"; + +function mockProcessExit() { + const exitMock = vi.spyOn(process, "exit").mockImplementation(((code?: number) => { + throw new Error(`__EXIT__:${code ?? 0}`); + }) as any); + return exitMock; +} + +describe("CLI parseArgs", () => { + const originalArgv = process.argv.slice(); + + afterEach(() => { + process.argv = originalArgv.slice(); + vi.restoreAllMocks(); + }); + + it("exits 1 when --settings has no value", () => { + process.argv = ["node", "livesync-cli", "./vault", "--settings"]; + const exitMock = mockProcessExit(); + const stderr = vi.spyOn(console, "error").mockImplementation(() => {}); + + expect(() => parseArgs()).toThrowError("__EXIT__:1"); + expect(exitMock).toHaveBeenCalledWith(1); + expect(stderr).toHaveBeenCalledWith("Error: Missing value for --settings"); + }); + + it("exits 1 when database-path is missing", () => { + process.argv = ["node", "livesync-cli", "sync"]; + const exitMock = mockProcessExit(); + const stderr = vi.spyOn(console, "error").mockImplementation(() => {}); + + expect(() => parseArgs()).toThrowError("__EXIT__:1"); + expect(exitMock).toHaveBeenCalledWith(1); + expect(stderr).toHaveBeenCalledWith("Error: database-path is required"); + }); + + it("exits 1 for unknown command after database-path", () => { + process.argv = ["node", "livesync-cli", "./vault", "unknown-cmd"]; + const exitMock = mockProcessExit(); + const stderr = vi.spyOn(console, "error").mockImplementation(() => {}); + + expect(() => parseArgs()).toThrowError("__EXIT__:1"); + expect(exitMock).toHaveBeenCalledWith(1); + expect(stderr).toHaveBeenCalledWith("Error: Unknown command 'unknown-cmd'"); + }); + + it("exits 0 and prints help for --help", () => { + process.argv = ["node", "livesync-cli", "--help"]; + const exitMock = mockProcessExit(); + const stdout = vi.spyOn(console, "log").mockImplementation(() => {}); + + expect(() => parseArgs()).toThrowError("__EXIT__:0"); + expect(exitMock).toHaveBeenCalledWith(0); + expect(stdout).toHaveBeenCalled(); + const combined = stdout.mock.calls.flat().join("\n"); + expect(combined).toContain("Usage:"); + expect(combined).toContain("livesync-cli [database-path]"); + }); +}); diff --git a/src/apps/cli/package.json b/src/apps/cli/package.json index 327e4fe..611f54d 100644 --- a/src/apps/cli/package.json +++ b/src/apps/cli/package.json @@ -11,7 +11,14 @@ "cli": "node dist/index.cjs", "buildRun": "npm run build && npm run cli --", "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json", - "test:e2e:two-vaults": "bash test/test-e2e-two-vaults-with-docker-linux.sh" + "test:unit": "cd ../../.. && npx vitest run --config vitest.config.unit.ts src/apps/cli/main.unit.spec.ts src/apps/cli/commands/utils.unit.spec.ts src/apps/cli/commands/runCommand.unit.spec.ts", + "test:e2e:two-vaults": "bash test/test-e2e-two-vaults-with-docker-linux.sh", + "test:e2e:two-vaults:common": "bash test/test-e2e-two-vaults-common.sh", + "test:e2e:two-vaults:matrix": "bash test/test-e2e-two-vaults-matrix.sh", + "test:e2e:push-pull": "bash test/test-push-pull-linux.sh", + "test:e2e:setup-put-cat": "bash test/test-setup-put-cat-linux.sh", + "test:e2e:sync-two-local": "bash test/test-sync-two-local-databases-linux.sh", + "test:e2e:all": "npm run test:e2e:two-vaults && npm run test:e2e:push-pull && npm run test:e2e:setup-put-cat && npm run test:e2e:sync-two-local" }, "dependencies": {}, "devDependencies": {} diff --git a/src/apps/cli/test/test-e2e-two-vaults-common.sh b/src/apps/cli/test/test-e2e-two-vaults-common.sh new file mode 100755 index 0000000..03a5400 --- /dev/null +++ b/src/apps/cli/test/test-e2e-two-vaults-common.sh @@ -0,0 +1,445 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +CLI_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)" +cd "$CLI_DIR" + +CLI_CMD=(npm --silent run cli -- -v) +RUN_BUILD="${RUN_BUILD:-1}" +KEEP_TEST_DATA="${KEEP_TEST_DATA:-0}" +TEST_ENV_FILE="${TEST_ENV_FILE:-$CLI_DIR/.test.env}" +REMOTE_TYPE="${REMOTE_TYPE:-COUCHDB}" +ENCRYPT="${ENCRYPT:-0}" +TEST_LABEL="${TEST_LABEL:-${REMOTE_TYPE}-enc${ENCRYPT}}" +E2E_PASSPHRASE="${E2E_PASSPHRASE:-e2e-passphrase}" + +if [[ ! -f "$TEST_ENV_FILE" ]]; then + echo "[ERROR] test env file not found: $TEST_ENV_FILE" >&2 + exit 1 +fi + +set -a +source "$TEST_ENV_FILE" +set +a + +DB_SUFFIX="$(date +%s)-$RANDOM" + +VAULT_ROOT="$CLI_DIR/.livesync" +VAULT_A="$VAULT_ROOT/testvault_a" +VAULT_B="$VAULT_ROOT/testvault_b" +SETTINGS_A="$VAULT_ROOT/test-settings-a.json" +SETTINGS_B="$VAULT_ROOT/test-settings-b.json" +WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-e2e.${TEST_LABEL}.XXXXXX")" + +COUCHDB_URI="" +COUCHDB_DBNAME="" +MINIO_BUCKET="" + +require_env() { + local var_name="$1" + if [[ -z "${!var_name:-}" ]]; then + echo "[ERROR] required variable '$var_name' is missing in $TEST_ENV_FILE" >&2 + exit 1 + fi +} + +if [[ "$REMOTE_TYPE" == "COUCHDB" ]]; then + require_env hostname + require_env dbname + require_env username + require_env password + COUCHDB_URI="${hostname%/}" + COUCHDB_DBNAME="${dbname}-${DB_SUFFIX}" +elif [[ "$REMOTE_TYPE" == "MINIO" ]]; then + require_env accessKey + require_env secretKey + require_env minioEndpoint + require_env bucketName + MINIO_BUCKET="${bucketName}-${DB_SUFFIX}" +else + echo "[ERROR] unsupported REMOTE_TYPE: $REMOTE_TYPE (use COUCHDB or MINIO)" >&2 + exit 1 +fi + +cleanup() { + local exit_code=$? + if [[ "$REMOTE_TYPE" == "COUCHDB" ]]; then + bash "$CLI_DIR/util/couchdb-stop.sh" >/dev/null 2>&1 || true + else + bash "$CLI_DIR/util/minio-stop.sh" >/dev/null 2>&1 || true + fi + + if [[ "$KEEP_TEST_DATA" != "1" ]]; then + rm -rf "$VAULT_A" "$VAULT_B" "$SETTINGS_A" "$SETTINGS_B" "$WORK_DIR" + else + echo "[INFO] KEEP_TEST_DATA=1, preserving test artefacts" + echo " vault a: $VAULT_A" + echo " vault b: $VAULT_B" + echo " settings: $SETTINGS_A, $SETTINGS_B" + echo " work dir: $WORK_DIR" + fi + exit "$exit_code" +} +trap cleanup EXIT + +run_cli() { + "${CLI_CMD[@]}" "$@" +} + +run_cli_a() { + run_cli "$VAULT_A" --settings "$SETTINGS_A" "$@" +} + +run_cli_b() { + run_cli "$VAULT_B" --settings "$SETTINGS_B" "$@" +} + +assert_contains() { + local haystack="$1" + local needle="$2" + local message="$3" + if ! grep -Fq "$needle" <<< "$haystack"; then + echo "[FAIL] $message" >&2 + echo "[FAIL] expected to find: $needle" >&2 + echo "[FAIL] actual output:" >&2 + echo "$haystack" >&2 + exit 1 + fi +} + +assert_equal() { + local expected="$1" + local actual="$2" + local message="$3" + if [[ "$expected" != "$actual" ]]; then + echo "[FAIL] $message" >&2 + echo "[FAIL] expected: $expected" >&2 + echo "[FAIL] actual: $actual" >&2 + exit 1 + fi +} + +assert_command_fails() { + local message="$1" + shift + set +e + "$@" >"$WORK_DIR/failed-command.log" 2>&1 + local exit_code=$? + set -e + if [[ "$exit_code" -eq 0 ]]; then + echo "[FAIL] $message" >&2 + cat "$WORK_DIR/failed-command.log" >&2 + exit 1 + fi +} + +sanitise_cat_stdout() { + sed '/^\[CLIWatchAdapter\] File watching is not enabled in CLI version$/d' +} + +extract_json_string_field() { + local field_name="$1" + node -e ' +const fs = require("node:fs"); +const fieldName = process.argv[1]; +const data = JSON.parse(fs.readFileSync(0, "utf-8")); +const value = data[fieldName]; +if (typeof value === "string") { + process.stdout.write(value); +} +' "$field_name" +} + +sync_both() { + run_cli_a sync >/dev/null + run_cli_b sync >/dev/null +} + +curl_json() { + curl -4 -sS --fail --connect-timeout 3 --max-time 15 "$@" +} + +configure_remote_settings() { + local settings_file="$1" + SETTINGS_FILE="$settings_file" \ + REMOTE_TYPE="$REMOTE_TYPE" \ + COUCHDB_URI="$COUCHDB_URI" \ + COUCHDB_USER="${username:-}" \ + COUCHDB_PASSWORD="${password:-}" \ + COUCHDB_DBNAME="$COUCHDB_DBNAME" \ + MINIO_ENDPOINT="${minioEndpoint:-}" \ + MINIO_BUCKET="$MINIO_BUCKET" \ + MINIO_ACCESS_KEY="${accessKey:-}" \ + MINIO_SECRET_KEY="${secretKey:-}" \ + ENCRYPT="$ENCRYPT" \ + E2E_PASSPHRASE="$E2E_PASSPHRASE" \ + node <<'NODE' +const fs = require("node:fs"); +const settingsPath = process.env.SETTINGS_FILE; +const data = JSON.parse(fs.readFileSync(settingsPath, "utf-8")); + +const remoteType = process.env.REMOTE_TYPE; +if (remoteType === "COUCHDB") { + data.remoteType = ""; + data.couchDB_URI = process.env.COUCHDB_URI; + data.couchDB_USER = process.env.COUCHDB_USER; + data.couchDB_PASSWORD = process.env.COUCHDB_PASSWORD; + data.couchDB_DBNAME = process.env.COUCHDB_DBNAME; +} else if (remoteType === "MINIO") { + data.remoteType = "MINIO"; + data.bucket = process.env.MINIO_BUCKET; + data.endpoint = process.env.MINIO_ENDPOINT; + data.accessKey = process.env.MINIO_ACCESS_KEY; + data.secretKey = process.env.MINIO_SECRET_KEY; + data.region = "auto"; + data.forcePathStyle = true; +} + +data.liveSync = true; +data.syncOnStart = false; +data.syncOnSave = false; +data.usePluginSync = false; + +data.encrypt = process.env.ENCRYPT === "1"; +data.passphrase = data.encrypt ? process.env.E2E_PASSPHRASE : ""; + +data.isConfigured = true; + +fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8"); +NODE +} + +init_settings() { + local settings_file="$1" + run_cli init-settings --force "$settings_file" >/dev/null + configure_remote_settings "$settings_file" + cat "$settings_file" +} + +wait_for_minio_bucket() { + local retries=30 + local delay_sec=2 + local i + for ((i = 1; i <= retries; i++)); do + if docker run --rm --network host --entrypoint=/bin/sh minio/mc -c "mc alias set myminio $minioEndpoint $accessKey $secretKey >/dev/null 2>&1 && mc ls myminio/$MINIO_BUCKET >/dev/null 2>&1"; then + return 0 + fi + bucketName="$MINIO_BUCKET" bash "$CLI_DIR/util/minio-init.sh" >/dev/null 2>&1 || true + sleep "$delay_sec" + done + return 1 +} + +start_remote() { + if [[ "$REMOTE_TYPE" == "COUCHDB" ]]; then + echo "[INFO] stopping leftover CouchDB container if present" + bash "$CLI_DIR/util/couchdb-stop.sh" >/dev/null 2>&1 || true + + echo "[INFO] starting CouchDB test container" + bash "$CLI_DIR/util/couchdb-start.sh" + + echo "[INFO] initialising CouchDB test container" + bash "$CLI_DIR/util/couchdb-init.sh" + + echo "[INFO] CouchDB create test database: $COUCHDB_DBNAME" + until (curl_json -X PUT --user "${username}:${password}" "${hostname}/${COUCHDB_DBNAME}"); do sleep 5; done + else + echo "[INFO] stopping leftover MinIO container if present" + bash "$CLI_DIR/util/minio-stop.sh" >/dev/null 2>&1 || true + + echo "[INFO] starting MinIO test container" + bucketName="$MINIO_BUCKET" bash "$CLI_DIR/util/minio-start.sh" + + echo "[INFO] initialising MinIO test bucket: $MINIO_BUCKET" + local minio_init_ok=0 + for _ in 1 2 3 4 5; do + if bucketName="$MINIO_BUCKET" bash "$CLI_DIR/util/minio-init.sh"; then + minio_init_ok=1 + break + fi + sleep 2 + done + if [[ "$minio_init_ok" != "1" ]]; then + echo "[FAIL] could not initialise MinIO bucket after retries: $MINIO_BUCKET" >&2 + exit 1 + fi + if ! wait_for_minio_bucket; then + echo "[FAIL] MinIO bucket not ready: $MINIO_BUCKET" >&2 + exit 1 + fi + fi +} + +if [[ "$RUN_BUILD" == "1" ]]; then + echo "[INFO] building CLI" + npm run build +fi + +echo "[INFO] e2e case: remote=$REMOTE_TYPE encrypt=$ENCRYPT label=$TEST_LABEL" +start_remote + +echo "[INFO] preparing vaults and settings" +rm -rf "$VAULT_A" "$VAULT_B" "$SETTINGS_A" "$SETTINGS_B" +mkdir -p "$VAULT_A" "$VAULT_B" +init_settings "$SETTINGS_A" +init_settings "$SETTINGS_B" + +if [[ "$REMOTE_TYPE" == "COUCHDB" ]]; then + echo "[INFO] test remote DB: $COUCHDB_DBNAME" +else + echo "[INFO] test remote bucket: $MINIO_BUCKET" +fi + +TARGET_A_ONLY="e2e/a-only-info.md" +TARGET_SYNC="e2e/sync-info.md" +TARGET_PUSH="e2e/pushed-from-a.md" +TARGET_PUT="e2e/put-from-a.md" +TARGET_CONFLICT="e2e/conflict.md" + +echo "[CASE] A puts and A can get info" +printf 'alpha-from-a\n' | run_cli_a put "$TARGET_A_ONLY" >/dev/null +INFO_A_ONLY="$(run_cli_a info "$TARGET_A_ONLY")" +assert_contains "$INFO_A_ONLY" "\"path\": \"$TARGET_A_ONLY\"" "A info should include path after put" +echo "[PASS] A put/info" + +echo "[CASE] A puts, both sync, and B can get info" +printf 'visible-after-sync\n' | run_cli_a put "$TARGET_SYNC" >/dev/null +sync_both +INFO_B_SYNC="$(run_cli_b info "$TARGET_SYNC")" +assert_contains "$INFO_B_SYNC" "\"path\": \"$TARGET_SYNC\"" "B info should include path after sync" +echo "[PASS] sync A->B and B info" + +echo "[CASE] A pushes and puts, both sync, and B can pull and cat" +PUSH_SRC="$WORK_DIR/push-source.txt" +PULL_DST="$WORK_DIR/pull-destination.txt" +printf 'pushed-content-%s\n' "$DB_SUFFIX" > "$PUSH_SRC" +run_cli_a push "$PUSH_SRC" "$TARGET_PUSH" >/dev/null +printf 'put-content-%s\n' "$DB_SUFFIX" | run_cli_a put "$TARGET_PUT" >/dev/null +sync_both +run_cli_b pull "$TARGET_PUSH" "$PULL_DST" >/dev/null +if ! cmp -s "$PUSH_SRC" "$PULL_DST"; then + echo "[FAIL] B pull result does not match pushed source" >&2 + echo "--- source ---" >&2 + cat "$PUSH_SRC" >&2 + echo "--- pulled ---" >&2 + cat "$PULL_DST" >&2 + exit 1 +fi +CAT_B_PUT="$(run_cli_b cat "$TARGET_PUT" | sanitise_cat_stdout)" +assert_equal "put-content-$DB_SUFFIX" "$CAT_B_PUT" "B cat should return A put content" +echo "[PASS] push/pull and put/cat across vaults" + +echo "[CASE] A removes, both sync, and B can no longer cat" +run_cli_a rm "$TARGET_PUT" >/dev/null +sync_both +assert_command_fails "B cat should fail after A removed the file and synced" run_cli_b cat "$TARGET_PUT" +echo "[PASS] rm is replicated" + +echo "[CASE] verify conflict detection" +printf 'conflict-base\n' | run_cli_a put "$TARGET_CONFLICT" >/dev/null +sync_both +INFO_B_BASE="$(run_cli_b info "$TARGET_CONFLICT")" +assert_contains "$INFO_B_BASE" "\"path\": \"$TARGET_CONFLICT\"" "B should be able to info before creating conflict" + +printf 'conflict-from-a-%s\n' "$DB_SUFFIX" | run_cli_a put "$TARGET_CONFLICT" >/dev/null +printf 'conflict-from-b-%s\n' "$DB_SUFFIX" | run_cli_b put "$TARGET_CONFLICT" >/dev/null + +run_cli_a sync >/dev/null +run_cli_b sync >/dev/null +run_cli_a sync >/dev/null + +INFO_A_CONFLICT="$(run_cli_a info "$TARGET_CONFLICT")" +INFO_B_CONFLICT="$(run_cli_b info "$TARGET_CONFLICT")" +if grep -qF '"conflicts": "N/A"' <<< "$INFO_A_CONFLICT" && grep -qF '"conflicts": "N/A"' <<< "$INFO_B_CONFLICT"; then + echo "[FAIL] conflict was expected but both A and B show Conflicts: N/A" >&2 + echo "--- A info ---" >&2 + echo "$INFO_A_CONFLICT" >&2 + echo "--- B info ---" >&2 + echo "$INFO_B_CONFLICT" >&2 + exit 1 +fi +echo "[PASS] conflict detected by info" + +echo "[CASE] verify ls marks conflicted revisions" +LS_A_CONFLICT_LINE="$(run_cli_a ls "$TARGET_CONFLICT" | awk -F $'\t' -v p="$TARGET_CONFLICT" '$1==p {print; exit}')" +LS_B_CONFLICT_LINE="$(run_cli_b ls "$TARGET_CONFLICT" | awk -F $'\t' -v p="$TARGET_CONFLICT" '$1==p {print; exit}')" +if [[ -z "$LS_A_CONFLICT_LINE" || -z "$LS_B_CONFLICT_LINE" ]]; then + echo "[FAIL] ls output did not include conflict target on one of the vaults" >&2 + echo "--- A ls ---" >&2 + run_cli_a ls "$TARGET_CONFLICT" >&2 || true + echo "--- B ls ---" >&2 + run_cli_b ls "$TARGET_CONFLICT" >&2 || true + exit 1 +fi +LS_A_CONFLICT_REV="$(awk -F $'\t' '{print $4}' <<< "$LS_A_CONFLICT_LINE")" +LS_B_CONFLICT_REV="$(awk -F $'\t' '{print $4}' <<< "$LS_B_CONFLICT_LINE")" +if [[ "$LS_A_CONFLICT_REV" != *"*" && "$LS_B_CONFLICT_REV" != *"*" ]]; then + echo "[FAIL] conflicted entry should be marked with '*' in ls revision column on at least one vault" >&2 + echo "A: $LS_A_CONFLICT_LINE" >&2 + echo "B: $LS_B_CONFLICT_LINE" >&2 + exit 1 +fi +echo "[PASS] ls marks conflicts" + +echo "[CASE] resolve conflict on A and verify both vaults are clean" +KEEP_REVISION="$(printf '%s' "$INFO_A_CONFLICT" | extract_json_string_field revision)" +if [[ -z "$KEEP_REVISION" ]]; then + echo "[FAIL] could not extract current revision from A info output" >&2 + echo "$INFO_A_CONFLICT" >&2 + exit 1 +fi + +run_cli_a resolve "$TARGET_CONFLICT" "$KEEP_REVISION" >/dev/null + +INFO_A_RESOLVED="" +INFO_B_RESOLVED="" +RESOLVE_PROPAGATED=0 +for _ in 1 2 3 4 5; do + sync_both + INFO_A_RESOLVED="$(run_cli_a info "$TARGET_CONFLICT")" + INFO_B_RESOLVED="$(run_cli_b info "$TARGET_CONFLICT")" + if grep -qF '"conflicts": "N/A"' <<< "$INFO_A_RESOLVED" && grep -qF '"conflicts": "N/A"' <<< "$INFO_B_RESOLVED"; then + RESOLVE_PROPAGATED=1 + break + fi +done +if [[ "$RESOLVE_PROPAGATED" != "1" ]]; then + KEEP_REVISION_B="$(printf '%s' "$INFO_B_RESOLVED" | extract_json_string_field revision)" + if [[ -n "$KEEP_REVISION_B" ]]; then + run_cli_b resolve "$TARGET_CONFLICT" "$KEEP_REVISION_B" >/dev/null + sync_both + INFO_A_RESOLVED="$(run_cli_a info "$TARGET_CONFLICT")" + INFO_B_RESOLVED="$(run_cli_b info "$TARGET_CONFLICT")" + if grep -qF '"conflicts": "N/A"' <<< "$INFO_A_RESOLVED" && grep -qF '"conflicts": "N/A"' <<< "$INFO_B_RESOLVED"; then + RESOLVE_PROPAGATED=1 + fi + fi +fi + +if [[ "$RESOLVE_PROPAGATED" != "1" ]]; then + echo "[FAIL] conflicts should be resolved on both vaults" >&2 + echo "--- A info after resolve ---" >&2 + echo "$INFO_A_RESOLVED" >&2 + echo "--- B info after resolve ---" >&2 + echo "$INFO_B_RESOLVED" >&2 + exit 1 +fi + +LS_A_RESOLVED_LINE="$(run_cli_a ls "$TARGET_CONFLICT" | awk -F $'\t' -v p="$TARGET_CONFLICT" '$1==p {print; exit}')" +LS_B_RESOLVED_LINE="$(run_cli_b ls "$TARGET_CONFLICT" | awk -F $'\t' -v p="$TARGET_CONFLICT" '$1==p {print; exit}')" +LS_A_RESOLVED_REV="$(awk -F $'\t' '{print $4}' <<< "$LS_A_RESOLVED_LINE")" +LS_B_RESOLVED_REV="$(awk -F $'\t' '{print $4}' <<< "$LS_B_RESOLVED_LINE")" +if [[ "$LS_A_RESOLVED_REV" == *"*" || "$LS_B_RESOLVED_REV" == *"*" ]]; then + echo "[FAIL] resolved entry should not be marked as conflicted in ls" >&2 + echo "A: $LS_A_RESOLVED_LINE" >&2 + echo "B: $LS_B_RESOLVED_LINE" >&2 + exit 1 +fi + +CAT_A_RESOLVED="$(run_cli_a cat "$TARGET_CONFLICT" | sanitise_cat_stdout)" +CAT_B_RESOLVED="$(run_cli_b cat "$TARGET_CONFLICT" | sanitise_cat_stdout)" +assert_equal "$CAT_A_RESOLVED" "$CAT_B_RESOLVED" "resolved content should match across both vaults" +echo "[PASS] resolve is replicated and ls reflects resolved state" + +echo "[PASS] all requested E2E scenarios completed (${TEST_LABEL})" diff --git a/src/apps/cli/test/test-e2e-two-vaults-matrix.sh b/src/apps/cli/test/test-e2e-two-vaults-matrix.sh new file mode 100755 index 0000000..843e60f --- /dev/null +++ b/src/apps/cli/test/test-e2e-two-vaults-matrix.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" + +RUN_BUILD="${RUN_BUILD:-1}" +KEEP_TEST_DATA="${KEEP_TEST_DATA:-0}" +TEST_ENV_FILE="${TEST_ENV_FILE:-$(cd -- "$SCRIPT_DIR/.." && pwd)/.test.env}" + +run_case() { + local remote_type="$1" + local encrypt="$2" + local label="${remote_type}-enc${encrypt}" + + echo "[INFO] ===== CASE START: $label =====" + REMOTE_TYPE="$remote_type" \ + ENCRYPT="$encrypt" \ + RUN_BUILD="$RUN_BUILD" \ + KEEP_TEST_DATA="$KEEP_TEST_DATA" \ + TEST_ENV_FILE="$TEST_ENV_FILE" \ + TEST_LABEL="$label" \ + bash "$SCRIPT_DIR/test-e2e-two-vaults-common.sh" + echo "[INFO] ===== CASE PASS: $label =====" +} + +run_case COUCHDB 0 +run_case COUCHDB 1 +run_case MINIO 0 +run_case MINIO 1 + +echo "[PASS] all matrix cases completed" diff --git a/src/apps/cli/test/test-e2e-two-vaults-with-docker-linux.sh b/src/apps/cli/test/test-e2e-two-vaults-with-docker-linux.sh old mode 100644 new mode 100755 index b23b00c..9edf76f --- a/src/apps/cli/test/test-e2e-two-vaults-with-docker-linux.sh +++ b/src/apps/cli/test/test-e2e-two-vaults-with-docker-linux.sh @@ -2,246 +2,8 @@ set -euo pipefail SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" -CLI_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)" -cd "$CLI_DIR" -# verbose -CLI_CMD=(npm run cli -- -v ) -RUN_BUILD="${RUN_BUILD:-1}" -KEEP_TEST_DATA="${KEEP_TEST_DATA:-0}" -TEST_ENV_FILE="${TEST_ENV_FILE:-$CLI_DIR/.test.env}" - -if [[ ! -f "$TEST_ENV_FILE" ]]; then - echo "[ERROR] test env file not found: $TEST_ENV_FILE" >&2 - exit 1 -fi - -set -a -source "$TEST_ENV_FILE" -set +a - -for var in hostname dbname username password; do - if [[ -z "${!var:-}" ]]; then - echo "[ERROR] required variable '$var' is missing in $TEST_ENV_FILE" >&2 - exit 1 - fi -done - -COUCHDB_URI="${hostname%/}" -DB_SUFFIX="$(date +%s)-$RANDOM" -COUCHDB_DBNAME="${dbname}-${DB_SUFFIX}" - -VAULT_ROOT="$CLI_DIR/.livesync" -VAULT_A="$VAULT_ROOT/testvault_a" -VAULT_B="$VAULT_ROOT/testvault_b" -SETTINGS_A="$VAULT_ROOT/test-settings-a.json" -SETTINGS_B="$VAULT_ROOT/test-settings-b.json" -WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-e2e.XXXXXX")" - -cleanup() { - local exit_code=$? - bash "$CLI_DIR/util/couchdb-stop.sh" >/dev/null 2>&1 || true - if [[ "$KEEP_TEST_DATA" != "1" ]]; then - rm -rf "$VAULT_A" "$VAULT_B" "$SETTINGS_A" "$SETTINGS_B" "$WORK_DIR" - else - echo "[INFO] KEEP_TEST_DATA=1, preserving test artefacts" - echo " vault a: $VAULT_A" - echo " vault b: $VAULT_B" - echo " settings: $SETTINGS_A, $SETTINGS_B" - echo " work dir: $WORK_DIR" - fi - exit "$exit_code" -} -trap cleanup EXIT - -run_cli() { - "${CLI_CMD[@]}" "$@" -} - -run_cli_a() { - run_cli "$VAULT_A" --settings "$SETTINGS_A" "$@" -} - -run_cli_b() { - run_cli "$VAULT_B" --settings "$SETTINGS_B" "$@" -} - -assert_contains() { - local haystack="$1" - local needle="$2" - local message="$3" - if ! grep -Fq "$needle" <<< "$haystack"; then - echo "[FAIL] $message" >&2 - echo "[FAIL] expected to find: $needle" >&2 - echo "[FAIL] actual output:" >&2 - echo "$haystack" >&2 - exit 1 - fi -} - -assert_equal() { - local expected="$1" - local actual="$2" - local message="$3" - if [[ "$expected" != "$actual" ]]; then - echo "[FAIL] $message" >&2 - echo "[FAIL] expected: $expected" >&2 - echo "[FAIL] actual: $actual" >&2 - exit 1 - fi -} - -assert_command_fails() { - local message="$1" - shift - set +e - "$@" >"$WORK_DIR/failed-command.log" 2>&1 - local exit_code=$? - set -e - if [[ "$exit_code" -eq 0 ]]; then - echo "[FAIL] $message" >&2 - cat "$WORK_DIR/failed-command.log" >&2 - exit 1 - fi -} - -sanitise_cat_stdout() { - sed '/^\[CLIWatchAdapter\] File watching is not enabled in CLI version$/d' -} - -sync_both() { - run_cli_a sync >/dev/null - run_cli_b sync >/dev/null -} - -curl_json() { - curl -4 -sS --fail --connect-timeout 3 --max-time 15 "$@" -} - -init_settings() { - local settings_file="$1" - run_cli init-settings --force "$settings_file" >/dev/null - SETTINGS_FILE="$settings_file" \ - COUCHDB_URI="$COUCHDB_URI" \ - COUCHDB_USER="$username" \ - COUCHDB_PASSWORD="$password" \ - COUCHDB_DBNAME="$COUCHDB_DBNAME" \ - node <<'NODE' -const fs = require("node:fs"); -const settingsPath = process.env.SETTINGS_FILE; -const data = JSON.parse(fs.readFileSync(settingsPath, "utf-8")); - -data.couchDB_URI = process.env.COUCHDB_URI; -data.couchDB_USER = process.env.COUCHDB_USER; -data.couchDB_PASSWORD = process.env.COUCHDB_PASSWORD; -data.couchDB_DBNAME = process.env.COUCHDB_DBNAME; -data.liveSync = true; -data.syncOnStart = false; -data.syncOnSave = false; -data.usePluginSync = false; -data.isConfigured = true; - -fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8"); -NODE -cat "$settings_file" -} - -echo "[INFO] stopping leftover CouchDB container if present" -bash "$CLI_DIR/util/couchdb-stop.sh" >/dev/null 2>&1 || true - -echo "[INFO] starting CouchDB test container" -bash "$CLI_DIR/util/couchdb-start.sh" - -echo "status" -docker ps --filter "name=couchdb-test" - -echo "[INFO] initialising CouchDB test container" -bash "$CLI_DIR/util/couchdb-init.sh" - -echo "[INFO] CouchDB create test database: $COUCHDB_DBNAME" -until (curl_json -X PUT --user "${username}:${password}" "${hostname}/${COUCHDB_DBNAME}" ); do sleep 5; done - -if [[ "$RUN_BUILD" == "1" ]]; then - echo "[INFO] building CLI" - npm run build -fi - -echo "[INFO] preparing vaults and settings" -rm -rf "$VAULT_A" "$VAULT_B" "$SETTINGS_A" "$SETTINGS_B" -mkdir -p "$VAULT_A" "$VAULT_B" -init_settings "$SETTINGS_A" -init_settings "$SETTINGS_B" - -echo "[INFO] test DB: $COUCHDB_DBNAME" - -TARGET_A_ONLY="e2e/a-only-info.md" -TARGET_SYNC="e2e/sync-info.md" -TARGET_PUSH="e2e/pushed-from-a.md" -TARGET_PUT="e2e/put-from-a.md" -TARGET_CONFLICT="e2e/conflict.md" - -echo "[CASE] A puts and A can get info" -printf 'alpha-from-a\n' | run_cli_a put "$TARGET_A_ONLY" >/dev/null -INFO_A_ONLY="$(run_cli_a info "$TARGET_A_ONLY")" -assert_contains "$INFO_A_ONLY" "\"path\": \"$TARGET_A_ONLY\"" "A info should include path after put" -echo "[PASS] A put/info" - -echo "[CASE] A puts, both sync, and B can get info" -printf 'visible-after-sync\n' | run_cli_a put "$TARGET_SYNC" >/dev/null -sync_both -INFO_B_SYNC="$(run_cli_b info "$TARGET_SYNC")" -assert_contains "$INFO_B_SYNC" "\"path\": \"$TARGET_SYNC\"" "B info should include path after sync" -echo "[PASS] sync A->B and B info" - -echo "[CASE] A pushes and puts, both sync, and B can pull and cat" -PUSH_SRC="$WORK_DIR/push-source.txt" -PULL_DST="$WORK_DIR/pull-destination.txt" -printf 'pushed-content-%s\n' "$DB_SUFFIX" > "$PUSH_SRC" -run_cli_a push "$PUSH_SRC" "$TARGET_PUSH" >/dev/null -printf 'put-content-%s\n' "$DB_SUFFIX" | run_cli_a put "$TARGET_PUT" >/dev/null -sync_both -run_cli_b pull "$TARGET_PUSH" "$PULL_DST" >/dev/null -if ! cmp -s "$PUSH_SRC" "$PULL_DST"; then - echo "[FAIL] B pull result does not match pushed source" >&2 - echo "--- source ---" >&2 - cat "$PUSH_SRC" >&2 - echo "--- pulled ---" >&2 - cat "$PULL_DST" >&2 - exit 1 -fi -CAT_B_PUT="$(run_cli_b cat "$TARGET_PUT" | sanitise_cat_stdout)" -assert_equal "put-content-$DB_SUFFIX" "$CAT_B_PUT" "B cat should return A put content" -echo "[PASS] push/pull and put/cat across vaults" - -echo "[CASE] A removes, both sync, and B can no longer cat" -run_cli_a rm "$TARGET_PUT" >/dev/null -sync_both -assert_command_fails "B cat should fail after A removed the file and synced" run_cli_b cat "$TARGET_PUT" -echo "[PASS] rm is replicated" - -echo "[CASE] verify conflict detection" -printf 'conflict-base\n' | run_cli_a put "$TARGET_CONFLICT" >/dev/null -sync_both -INFO_B_BASE="$(run_cli_b info "$TARGET_CONFLICT")" -assert_contains "$INFO_B_BASE" "\"path\": \"$TARGET_CONFLICT\"" "B should be able to info before creating conflict" - -printf 'conflict-from-a-%s\n' "$DB_SUFFIX" | run_cli_a put "$TARGET_CONFLICT" >/dev/null -printf 'conflict-from-b-%s\n' "$DB_SUFFIX" | run_cli_b put "$TARGET_CONFLICT" >/dev/null - -run_cli_a sync >/dev/null -run_cli_b sync >/dev/null -run_cli_a sync >/dev/null - -INFO_A_CONFLICT="$(run_cli_a info "$TARGET_CONFLICT")" -INFO_B_CONFLICT="$(run_cli_b info "$TARGET_CONFLICT")" -if grep -qF '"conflicts": "N/A"' <<< "$INFO_A_CONFLICT" && grep -qF '"conflicts": "N/A"' <<< "$INFO_B_CONFLICT"; then - echo "[FAIL] conflict was expected but both A and B show Conflicts: N/A" >&2 - echo "--- A info ---" >&2 - echo "$INFO_A_CONFLICT" >&2 - echo "--- B info ---" >&2 - echo "$INFO_B_CONFLICT" >&2 - exit 1 -fi -echo "[PASS] conflict detected by info" - -echo "[PASS] all requested E2E scenarios completed" \ No newline at end of file +REMOTE_TYPE="${REMOTE_TYPE:-COUCHDB}" \ +ENCRYPT="${ENCRYPT:-0}" \ +TEST_LABEL="${TEST_LABEL:-${REMOTE_TYPE}-enc${ENCRYPT}}" \ +bash "$SCRIPT_DIR/test-e2e-two-vaults-common.sh" \ No newline at end of file diff --git a/src/apps/cli/vite.config.ts b/src/apps/cli/vite.config.ts index d54fb44..42e6202 100644 --- a/src/apps/cli/vite.config.ts +++ b/src/apps/cli/vite.config.ts @@ -12,6 +12,14 @@ export default defineConfig({ alias: { "@lib/worker/bgWorker.ts": "../../lib/src/worker/bgWorker.mock.ts", "@lib/pouchdb/pouchdb-browser.ts": path.resolve(__dirname, "lib/pouchdb-node.ts"), + // The CLI runs on Node.js; force AWS XML builder to its CJS Node entry + // so Vite does not resolve the browser DOMParser-based XML parser. + "@aws-sdk/xml-builder": path.resolve( + __dirname, + "../../../node_modules/@aws-sdk/xml-builder/dist-cjs/index.js" + ), + // Force fflate to the Node CJS entry; browser entry expects Web Worker globals. + fflate: path.resolve(__dirname, "../../../node_modules/fflate/lib/node.cjs"), "@": path.resolve(__dirname, "../../"), "@lib": path.resolve(__dirname, "../../lib/src"), "../../src/worker/bgWorker.ts": "../../src/worker/bgWorker.mock.ts", @@ -32,7 +40,8 @@ export default defineConfig({ if (id.startsWith(".") || id.startsWith("/")) return false; if (id.startsWith("@/") || id.startsWith("@lib/")) return false; if (id.endsWith(".ts") || id.endsWith(".js")) return false; - if (id === "fs" || id === "fs/promises" || id === "path" || id === "crypto") return true; + if (id === "fs" || id === "fs/promises" || id === "path" || id === "crypto" || id === "worker_threads") + return true; if (id.startsWith("pouchdb-")) return true; if (id.startsWith("node:")) return true; return false; diff --git a/src/lib b/src/lib index 41e2340..4346ead 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 41e234023530cea101473c5d2f781941b6bbc9e8 +Subproject commit 4346ead9c86574c3f370d458a59ea48b502e6d33 From 822d9579762b528e634a4c5560bc396d17eedf22 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 12 Mar 2026 19:41:10 +0900 Subject: [PATCH 068/339] Refactor: separate entrypoint and main, Fix: readlng binary file --- src/apps/cli/README.md | 3 -- .../adapters/NodeStorageAdapter.unit.spec.ts | 40 +++++++++++++++++++ src/apps/cli/entrypoint.ts | 7 ++++ src/apps/cli/main.ts | 22 +--------- .../cli/test/test-e2e-two-vaults-common.sh | 32 +++++++++++---- src/apps/cli/vite.config.ts | 4 +- src/lib | 2 +- updates.md | 22 +++++++++- 8 files changed, 95 insertions(+), 37 deletions(-) create mode 100644 src/apps/cli/adapters/NodeStorageAdapter.unit.spec.ts create mode 100644 src/apps/cli/entrypoint.ts diff --git a/src/apps/cli/README.md b/src/apps/cli/README.md index ca9a383..c12c2ee 100644 --- a/src/apps/cli/README.md +++ b/src/apps/cli/README.md @@ -187,9 +187,6 @@ TODO: Conflict and resolution checks for real local databases. - `serve`: Start CLI in server mode, exposing REST APIs for remote, and batch operations. - `cause-conflicted `: Mark a file as conflicted without changing its content, to trigger conflict resolution in Obsidian. -## Current Limitations and known issues -- Binary files are not supported yet (it seems... but I haven't tested this yet). - ## Use Cases ### 1. Bootstrap a new headless vault diff --git a/src/apps/cli/adapters/NodeStorageAdapter.unit.spec.ts b/src/apps/cli/adapters/NodeStorageAdapter.unit.spec.ts new file mode 100644 index 0000000..a1b0977 --- /dev/null +++ b/src/apps/cli/adapters/NodeStorageAdapter.unit.spec.ts @@ -0,0 +1,40 @@ +import * as fs from "node:fs/promises"; +import * as os from "node:os"; +import * as path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { NodeStorageAdapter } from "./NodeStorageAdapter"; + +describe("NodeStorageAdapter binary I/O", () => { + const tempDirs: string[] = []; + + async function createAdapter() { + const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "livesync-cli-node-storage-")); + tempDirs.push(tempDir); + return new NodeStorageAdapter(tempDir); + } + + afterEach(async () => { + await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true }))); + }); + + it("writes and reads binary data without corruption", async () => { + const adapter = await createAdapter(); + const expected = Uint8Array.from([0x00, 0x7f, 0x80, 0xff, 0x42]); + + await adapter.writeBinary("files/blob.bin", expected.buffer.slice(0)); + const result = await adapter.readBinary("files/blob.bin"); + + expect(Array.from(new Uint8Array(result))).toEqual(Array.from(expected)); + }); + + it("returns an ArrayBuffer with the exact file length", async () => { + const adapter = await createAdapter(); + const expected = Uint8Array.from([0x10, 0x20, 0x30]); + + await adapter.writeBinary("files/small.bin", expected.buffer.slice(0)); + const result = await adapter.readBinary("files/small.bin"); + + expect(result.byteLength).toBe(expected.byteLength); + expect(Array.from(new Uint8Array(result))).toEqual([0x10, 0x20, 0x30]); + }); +}); diff --git a/src/apps/cli/entrypoint.ts b/src/apps/cli/entrypoint.ts new file mode 100644 index 0000000..b8a1177 --- /dev/null +++ b/src/apps/cli/entrypoint.ts @@ -0,0 +1,7 @@ +#!/usr/bin/env node +import { main } from "./main"; + +main().catch((error) => { + console.error(`[Fatal Error]`, error); + process.exit(1); +}); diff --git a/src/apps/cli/main.ts b/src/apps/cli/main.ts index 58817b3..43d3109 100644 --- a/src/apps/cli/main.ts +++ b/src/apps/cli/main.ts @@ -1,4 +1,3 @@ -#!/usr/bin/env node /** * Self-hosted LiveSync CLI * Command-line version of Self-hosted LiveSync plugin for syncing vaults without Obsidian @@ -22,7 +21,6 @@ if (!("localStorage" in globalThis)) { import * as fs from "fs/promises"; import * as path from "path"; -import { pathToFileURL } from "node:url"; import { NodeServiceContext, NodeServiceHub } from "./services/NodeServiceHub"; import { LiveSyncBaseCore } from "../../LiveSyncBaseCore"; import { initialiseServiceModulesCLI } from "./serviceModules/CLIServiceModules"; @@ -201,7 +199,7 @@ async function createDefaultSettingsFile(options: CLIOptions) { console.log(`[Done] Created settings file: ${targetPath}`); } -async function main() { +export async function main() { const options = parseArgs(); const avoidStdoutNoise = options.command === "cat" || @@ -373,21 +371,3 @@ async function main() { process.exit(1); } } - -// Run main only when invoked as the entrypoint, not when imported by tests. -const isEntryPoint = (() => { - const argv1 = process.argv[1]; - if (!argv1) return false; - try { - return import.meta.url === pathToFileURL(argv1).href; - } catch { - return false; - } -})(); - -if (isEntryPoint) { - main().catch((error) => { - console.error(`[Fatal Error]`, error); - process.exit(1); - }); -} diff --git a/src/apps/cli/test/test-e2e-two-vaults-common.sh b/src/apps/cli/test/test-e2e-two-vaults-common.sh index 03a5400..0745275 100755 --- a/src/apps/cli/test/test-e2e-two-vaults-common.sh +++ b/src/apps/cli/test/test-e2e-two-vaults-common.sh @@ -134,6 +134,18 @@ assert_command_fails() { fi } +assert_files_equal() { + local expected_file="$1" + local actual_file="$2" + local message="$3" + if ! cmp -s "$expected_file" "$actual_file"; then + echo "[FAIL] $message" >&2 + echo "[FAIL] expected sha256: $(sha256sum "$expected_file" | awk '{print $1}')" >&2 + echo "[FAIL] actual sha256: $(sha256sum "$actual_file" | awk '{print $1}')" >&2 + exit 1 + fi +} + sanitise_cat_stdout() { sed '/^\[CLIWatchAdapter\] File watching is not enabled in CLI version$/d' } @@ -295,6 +307,7 @@ TARGET_A_ONLY="e2e/a-only-info.md" TARGET_SYNC="e2e/sync-info.md" TARGET_PUSH="e2e/pushed-from-a.md" TARGET_PUT="e2e/put-from-a.md" +TARGET_PUSH_BINARY="e2e/pushed-from-a.bin" TARGET_CONFLICT="e2e/conflict.md" echo "[CASE] A puts and A can get info" @@ -318,18 +331,21 @@ run_cli_a push "$PUSH_SRC" "$TARGET_PUSH" >/dev/null printf 'put-content-%s\n' "$DB_SUFFIX" | run_cli_a put "$TARGET_PUT" >/dev/null sync_both run_cli_b pull "$TARGET_PUSH" "$PULL_DST" >/dev/null -if ! cmp -s "$PUSH_SRC" "$PULL_DST"; then - echo "[FAIL] B pull result does not match pushed source" >&2 - echo "--- source ---" >&2 - cat "$PUSH_SRC" >&2 - echo "--- pulled ---" >&2 - cat "$PULL_DST" >&2 - exit 1 -fi +assert_files_equal "$PUSH_SRC" "$PULL_DST" "B pull result does not match pushed source" CAT_B_PUT="$(run_cli_b cat "$TARGET_PUT" | sanitise_cat_stdout)" assert_equal "put-content-$DB_SUFFIX" "$CAT_B_PUT" "B cat should return A put content" echo "[PASS] push/pull and put/cat across vaults" +echo "[CASE] A pushes binary, both sync, and B can pull identical bytes" +PUSH_BINARY_SRC="$WORK_DIR/push-source.bin" +PULL_BINARY_DST="$WORK_DIR/pull-destination.bin" +head -c 4096 /dev/urandom > "$PUSH_BINARY_SRC" +run_cli_a push "$PUSH_BINARY_SRC" "$TARGET_PUSH_BINARY" >/dev/null +sync_both +run_cli_b pull "$TARGET_PUSH_BINARY" "$PULL_BINARY_DST" >/dev/null +assert_files_equal "$PUSH_BINARY_SRC" "$PULL_BINARY_DST" "B pull result does not match pushed binary source" +echo "[PASS] binary push/pull across vaults" + echo "[CASE] A removes, both sync, and B can no longer cat" run_cli_a rm "$TARGET_PUT" >/dev/null sync_both diff --git a/src/apps/cli/vite.config.ts b/src/apps/cli/vite.config.ts index 42e6202..d3bdea0 100644 --- a/src/apps/cli/vite.config.ts +++ b/src/apps/cli/vite.config.ts @@ -33,7 +33,7 @@ export default defineConfig({ minify: false, rollupOptions: { input: { - index: path.resolve(__dirname, "main.ts"), + index: path.resolve(__dirname, "entrypoint.ts"), }, external: (id) => { if (defaultExternal.includes(id)) return true; @@ -48,7 +48,7 @@ export default defineConfig({ }, }, lib: { - entry: path.resolve(__dirname, "main.ts"), + entry: path.resolve(__dirname, "entrypoint.ts"), formats: ["cjs"], fileName: "index", }, diff --git a/src/lib b/src/lib index 4346ead..d94c9b3 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 4346ead9c86574c3f370d458a59ea48b502e6d33 +Subproject commit d94c9b3ed74f10ca1ce704f54518485f646aa225 diff --git a/updates.md b/updates.md index 4be0b2f..5106031 100644 --- a/updates.md +++ b/updates.md @@ -3,7 +3,25 @@ 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. -## -- Unreleased2 -- +## Unnamed 12th March, 2026 + +12th March, 2026 + +### Fixed + +- Fixed Journal Sync had not been working on some timing, due to a compatibility issue (for a long time). + +### Internal behaviour change (or fix) + +- Journal Replicator now yields true after the replication is done. + +### CLI + +- Add more tests. +- Object Storage support has also been confirmed (and fixed) in CLI. + - Yes, we have finally managed to 'get one file'. + +## Unnamed 11th March, 2026 11th March, 2026 (second commit). @@ -23,7 +41,7 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid ### New something - Add `self-hosted-livesync-cli` to `src/apps/cli` as a headless, and a dedicated version. -## -- Unreleased -- +## Unnamed 11th March, 2026 11th March, 2026 From 4646577f35cb1f6108e9da533c1e801ebff7980e Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 12 Mar 2026 19:42:10 +0900 Subject: [PATCH 069/339] Update --- updates.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/updates.md b/updates.md index 5106031..6b0edc1 100644 --- a/updates.md +++ b/updates.md @@ -10,6 +10,7 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid ### Fixed - Fixed Journal Sync had not been working on some timing, due to a compatibility issue (for a long time). +- ServiceFileAccessBase now correctly handles the reading of binary files. ### Internal behaviour change (or fix) @@ -20,6 +21,8 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid - Add more tests. - Object Storage support has also been confirmed (and fixed) in CLI. - Yes, we have finally managed to 'get one file'. +- Now binary files are also supported in the CLI. + ## Unnamed 11th March, 2026 From 84110aee9744d0bbef734a3ac6e53d18c6319569 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 12 Mar 2026 19:46:52 +0900 Subject: [PATCH 070/339] update readme --- src/apps/cli/README.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/apps/cli/README.md b/src/apps/cli/README.md index c12c2ee..ea34a79 100644 --- a/src/apps/cli/README.md +++ b/src/apps/cli/README.md @@ -137,7 +137,7 @@ Usage: livesync-cli [database-path] [options] [command] [command-args] Arguments: - database-path Path to the local database directory (required) + database-path Path to the local database directory (required except for init-settings) Options: --settings, -s Path to settings file (default: .livesync/settings.json in local database directory) @@ -155,7 +155,7 @@ Commands: put Read text from standard input and write to local database cat Write latest file content from local database to standard output cat-rev Write specific revision content from local database to standard output - ls List files as pathsizemtimerevision[*] + ls [prefix] List files as pathsizemtimerevision[*] info Show file metadata including current and past revisions, conflicts, and chunk list rm Mark file as deleted in local database resolve Resolve conflict by keeping the specified revision @@ -176,8 +176,8 @@ npm run cli -- [database-path] [options] [command] [command-args] - `path`: Vault-relative path - `size`: Size in bytes - `revisions`: Available non-current revisions -- `Chunks`: Number of chunk IDs -- `child: ...`: Chunk ID list +- `chunks`: Number of chunk IDs +- `children`: Chunk ID list ### Planned options: @@ -245,13 +245,16 @@ npm run cli -- /data/vault --settings /data/livesync-settings.json cat ci/test.m src/apps/cli/ ├── commands/ # Command dispatcher and command utilities │ ├── runCommand.ts +│ ├── runCommand.unit.spec.ts │ ├── types.ts -│ └── utils.ts +│ ├── utils.ts +│ └── utils.unit.spec.ts ├── adapters/ # Node.js FileSystem Adapter │ ├── NodeConversionAdapter.ts │ ├── NodeFileSystemAdapter.ts │ ├── NodePathAdapter.ts │ ├── NodeStorageAdapter.ts +│ ├── NodeStorageAdapter.unit.spec.ts │ ├── NodeTypeGuardAdapter.ts │ ├── NodeTypes.ts │ └── NodeVaultAdapter.ts @@ -270,13 +273,19 @@ src/apps/cli/ │ ├── NodeServiceHub.ts │ └── NodeSettingService.ts ├── test/ +│ ├── test-e2e-two-vaults-common.sh +│ ├── test-e2e-two-vaults-matrix.sh +│ ├── test-e2e-two-vaults-with-docker-linux.sh │ ├── test-push-pull-linux.sh │ ├── test-setup-put-cat-linux.sh │ └── test-sync-two-local-databases-linux.sh ├── .gitignore +├── entrypoint.ts # CLI executable entry point (shebang) ├── main.ts # CLI entry point +├── main.unit.spec.ts ├── package.json ├── README.md # This file ├── tsconfig.json +├── util/ # Test and local utility scripts └── vite.config.ts ``` From 6ae1d5d6a55ee0ec0b0073633ac41fa5f4f1867c Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 12 Mar 2026 12:07:05 +0100 Subject: [PATCH 071/339] update readme --- updates.md | 56 +++++++++++++++++------------------------------------- 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/updates.md b/updates.md index 6b0edc1..6bd0585 100644 --- a/updates.md +++ b/updates.md @@ -3,7 +3,7 @@ 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. -## Unnamed 12th March, 2026 +## 0.25.52-patched-1 12th March, 2026 @@ -11,56 +11,34 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid - Fixed Journal Sync had not been working on some timing, due to a compatibility issue (for a long time). - ServiceFileAccessBase now correctly handles the reading of binary files. - -### Internal behaviour change (or fix) - -- Journal Replicator now yields true after the replication is done. - -### CLI - -- Add more tests. -- Object Storage support has also been confirmed (and fixed) in CLI. - - Yes, we have finally managed to 'get one file'. -- Now binary files are also supported in the CLI. - - -## Unnamed 11th March, 2026 - -11th March, 2026 (second commit). - -### Refactored - -- Offline change scanner and the local database preparation has been separated. -- Set default priority for processFileEvent and processSynchroniseResult for the place for adding hooks. -- ControlService now provides the readiness for processing operations. -- DatabaseService now able to modify database opening options on derived classes. -- Now `useOfflineScanner`, `useCheckRemoteSize`, and `useRedFlagFeatures` are set from `main.ts`, instead of `LiveSyncBaseCore`. - -### Fixed - - HeadlessAPIService now correctly provides the online status (always online) to the plug-in. - Non-worker version of bgWorker now correctly handles some functions. -### New something -- Add `self-hosted-livesync-cli` to `src/apps/cli` as a headless, and a dedicated version. - -## Unnamed 11th March, 2026 - -11th March, 2026 - -Now, Self-hosted LiveSync has finally begun to be split into the Self-hosted LiveSync plugin for Obsidian, and a properly abstracted version of it. -This may not offer much benefit to Obsidian plugin users, or might even cause a slight inconvenience, but I believe it will certainly help improve testability and make the ecosystem better. -However, I do not see the point in putting something with little benefit into beta, so I am handling this on the alpha branch. I would actually preferred to create an R&D branch, but I was not keen on the ampersand, and I feel it will eventually become a proper beta anyway. - ### Refactored - Separated `ObsidianLiveSyncPlugin` into `ObsidianLiveSyncPlugin` and `LiveSyncBaseCore`. - Now `LiveSyncCore` indicates the type specified version of `LiveSyncBaseCore`. - Referencing `plugin.xxx` has been rewritten to referencing the corresponding service or `core.xxx`. +- Offline change scanner and the local database preparation has been separated. +- Set default priority for processFileEvent and processSynchroniseResult for the place for adding hooks. +- ControlService now provides the readiness for processing operations. +- DatabaseService now able to modify database opening options on derived classes. +- Now `useOfflineScanner`, `useCheckRemoteSize`, and `useRedFlagFeatures` are set from `main.ts`, instead of `LiveSyncBaseCore`. ### Internal API changes - Storage Access APIs are now yielding Promises. This is to allow more limited storage platforms to be supported. +- Journal Replicator now yields true after the replication is done. + +### CLI + +We have previously developed FileSystem LiveSync and various other components in a separate repository, but updates have been significantly delayed and we have been plagued by compatibility issues. Now, a CLI tool using the same core logic is emerging. This does not directly manipulate the file system, but it offers a more convenient way of working and can also communicate with Object Storage. We can also resolve conflicts. Please refer to the code in `src/apps/cli` for the [self-hosted-livesync-cli](./src/apps/cli/README.md) for more details. + +- Add `self-hosted-livesync-cli` to `src/apps/cli` as a headless, and a dedicated version. +- Add more tests. +- Object Storage support has also been confirmed (and fixed) in CLI. + - Yes, we have finally managed to 'get one file'. +- Now binary files are also supported in the CLI. ### R&D From ad0a6b458fa262eb943ef450ce948bd2409c9c47 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 12 Mar 2026 12:16:00 +0100 Subject: [PATCH 072/339] bump --- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- src/lib | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/manifest.json b/manifest.json index cdbae18..a0f197d 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.52", + "version": "0.25.52-patched-1", "minAppVersion": "0.9.12", "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 d89d159..27d8650 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.52", + "version": "0.25.52-patched-1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.52", + "version": "0.25.52-patched-1", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 4b8eb28..458fbf8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.52", + "version": "0.25.52-patched-1", "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 d94c9b3..35df9a1 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit d94c9b3ed74f10ca1ce704f54518485f646aa225 +Subproject commit 35df9a1192b527e3fbb200c69ec243bdbc3835af From 4cc0a11d8683f965df418cc889435e14c1690b82 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 12 Mar 2026 12:23:13 +0100 Subject: [PATCH 073/339] Add ci cli-e2e --- .github/workflows/cli-e2e.yml | 84 +++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 .github/workflows/cli-e2e.yml diff --git a/.github/workflows/cli-e2e.yml b/.github/workflows/cli-e2e.yml new file mode 100644 index 0000000..0371a0d --- /dev/null +++ b/.github/workflows/cli-e2e.yml @@ -0,0 +1,84 @@ +# Run CLI E2E tests +name: cli-e2e + +on: + workflow_dispatch: + inputs: + suite: + description: 'CLI E2E suite to run' + type: choice + options: + - two-vaults-matrix + - two-vaults-couchdb + - two-vaults-minio + default: two-vaults-matrix + push: + branches: + - main + - beta + paths: + - '.github/workflows/cli-e2e.yml' + - 'src/apps/cli/**' + - 'src/lib/src/API/processSetting.ts' + - 'package.json' + - 'package-lock.json' + pull_request: + paths: + - '.github/workflows/cli-e2e.yml' + - 'src/apps/cli/**' + - 'src/lib/src/API/processSetting.ts' + - 'package.json' + - 'package-lock.json' + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '24.x' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run CLI E2E suite + working-directory: src/apps/cli + env: + CI: true + TEST_SUITE: ${{ github.event_name == 'workflow_dispatch' && inputs.suite || 'two-vaults-matrix' }} + run: | + set -euo pipefail + echo "[INFO] Running CLI E2E suite: $TEST_SUITE" + case "$TEST_SUITE" in + two-vaults-matrix) + npm run test:e2e:two-vaults:matrix + ;; + two-vaults-couchdb) + REMOTE_TYPE=COUCHDB ENCRYPT=0 npm run test:e2e:two-vaults + ;; + two-vaults-minio) + REMOTE_TYPE=MINIO ENCRYPT=0 npm run test:e2e:two-vaults + ;; + *) + echo "[ERROR] Unknown suite: $TEST_SUITE" >&2 + exit 1 + ;; + esac + + - name: Stop test containers + if: always() + working-directory: src/apps/cli + run: | + bash ./util/couchdb-stop.sh >/dev/null 2>&1 || true + bash ./util/minio-stop.sh >/dev/null 2>&1 || true \ No newline at end of file From d45f41500a207bd8e687f337532527c28686971d Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 12 Mar 2026 12:27:47 +0100 Subject: [PATCH 074/339] Fix: no longer duplicated addLog setHandler --- src/apps/webpeer/src/P2PReplicatorShim.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/webpeer/src/P2PReplicatorShim.ts b/src/apps/webpeer/src/P2PReplicatorShim.ts index 735e572..282c60d 100644 --- a/src/apps/webpeer/src/P2PReplicatorShim.ts +++ b/src/apps/webpeer/src/P2PReplicatorShim.ts @@ -89,7 +89,7 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { (this.services.API as BrowserAPIService).getSystemVaultName.setHandler( () => "p2p-livesync-web-peer" ); - this.services.API.addLog.setHandler(Logger); + // this.services.API.addLog.setHandler(Logger); const repStore = SimpleStoreIDBv2.open("p2p-livesync-web-peer"); this._simpleStore = repStore; let _settings = { ...P2P_DEFAULT_SETTINGS, additionalSuffixOfDatabaseName: "" } as ObsidianLiveSyncSettings; From 8aad3716d4f84622b435473133a245d0a6b70eee Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 12 Mar 2026 12:29:43 +0100 Subject: [PATCH 075/339] fix grammatical errors --- updates.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/updates.md b/updates.md index 6bd0585..e1abc50 100644 --- a/updates.md +++ b/updates.md @@ -19,10 +19,10 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid - Separated `ObsidianLiveSyncPlugin` into `ObsidianLiveSyncPlugin` and `LiveSyncBaseCore`. - Now `LiveSyncCore` indicates the type specified version of `LiveSyncBaseCore`. - Referencing `plugin.xxx` has been rewritten to referencing the corresponding service or `core.xxx`. -- Offline change scanner and the local database preparation has been separated. -- Set default priority for processFileEvent and processSynchroniseResult for the place for adding hooks. +- Offline change scanner and the local database preparation have been separated. +- Set default priority for processFileEvent and processSynchroniseResult for the place to add hooks. - ControlService now provides the readiness for processing operations. -- DatabaseService now able to modify database opening options on derived classes. +- DatabaseService is now able to modify database opening options on derived classes. - Now `useOfflineScanner`, `useCheckRemoteSize`, and `useRedFlagFeatures` are set from `main.ts`, instead of `LiveSyncBaseCore`. ### Internal API changes @@ -32,9 +32,9 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid ### CLI -We have previously developed FileSystem LiveSync and various other components in a separate repository, but updates have been significantly delayed and we have been plagued by compatibility issues. Now, a CLI tool using the same core logic is emerging. This does not directly manipulate the file system, but it offers a more convenient way of working and can also communicate with Object Storage. We can also resolve conflicts. Please refer to the code in `src/apps/cli` for the [self-hosted-livesync-cli](./src/apps/cli/README.md) for more details. +We have previously developed FileSystem LiveSync and various other components in a separate repository, but updates have been significantly delayed, and we have been plagued by compatibility issues. Now, a CLI tool using the same core logic is emerging. This does not directly manipulate the file system, but it offers a more convenient way of working and can also communicate with Object Storage. We can also resolve conflicts. Please refer to the code in `src/apps/cli` for the [self-hosted-livesync-cli](./src/apps/cli/README.md) for more details. -- Add `self-hosted-livesync-cli` to `src/apps/cli` as a headless, and a dedicated version. +- Add `self-hosted-livesync-cli` to `src/apps/cli` as a headless and dedicated version. - Add more tests. - Object Storage support has also been confirmed (and fixed) in CLI. - Yes, we have finally managed to 'get one file'. From 10f5cb8b4217193ccf318d45f74d5b1d9ac0a3bc Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 12 Mar 2026 12:31:53 +0100 Subject: [PATCH 076/339] add paths --- .github/workflows/unit-ci.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/unit-ci.yml b/.github/workflows/unit-ci.yml index a1e6455..c4a0ffa 100644 --- a/.github/workflows/unit-ci.yml +++ b/.github/workflows/unit-ci.yml @@ -7,6 +7,21 @@ on: branches: - main - beta + paths: + - 'src/**' + - 'test/**' + - 'lib/**' + - 'package.json' + - 'package-lock.json' + - 'tsconfig.json' + - 'vite.config.ts' + - 'vitest.config*.ts' + - 'esbuild.config.mjs' + - 'eslint.config.mjs' + - '.github/workflows/unit-ci.yml' + paths-ignore: + - '*.md' + - '**/*.md' permissions: contents: read From 29ce9a5df418a343885cc556f5ded545422de2c1 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 12 Mar 2026 12:45:39 +0100 Subject: [PATCH 077/339] remove todo --- src/apps/cli/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/apps/cli/README.md b/src/apps/cli/README.md index ea34a79..336d642 100644 --- a/src/apps/cli/README.md +++ b/src/apps/cli/README.md @@ -181,8 +181,6 @@ npm run cli -- [database-path] [options] [command] [command-args] ### Planned options: -TODO: Conflict and resolution checks for real local databases. - - `--immediate`: Perform sync after the command (e.g. `push`, `pull`, `put`, `rm`). - `serve`: Start CLI in server mode, exposing REST APIs for remote, and batch operations. - `cause-conflicted `: Mark a file as conflicted without changing its content, to trigger conflict resolution in Obsidian. From 0c65b5add9013f10cf8b0e707101d0a358304789 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Fri, 13 Mar 2026 12:55:46 +0900 Subject: [PATCH 078/339] Add: `mirror` command --- src/apps/cli/README.md | 98 +++++++--- src/apps/cli/commands/runCommand.ts | 9 + src/apps/cli/commands/types.ts | 2 + src/apps/cli/main.ts | 16 +- src/apps/cli/package.json | 3 +- .../cli/test/test-e2e-two-vaults-common.sh | 8 +- src/apps/cli/test/test-mirror-linux.sh | 176 ++++++++++++++++++ src/lib | 2 +- updates.md | 6 + 9 files changed, 288 insertions(+), 32 deletions(-) create mode 100755 src/apps/cli/test/test-mirror-linux.sh diff --git a/src/apps/cli/README.md b/src/apps/cli/README.md index 336d642..83bc7d9 100644 --- a/src/apps/cli/README.md +++ b/src/apps/cli/README.md @@ -63,43 +63,43 @@ As you know, the CLI is designed to be used in a headless environment. Hence all ```bash # Sync local database with CouchDB (no files will be changed). -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json sync +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json sync # Push files to local database -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json push /your/storage/file.md /vault/path/file.md +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json push /your/storage/file.md /vault/path/file.md # Pull files from local database -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json pull /vault/path/file.md /your/storage/file.md +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json pull /vault/path/file.md /your/storage/file.md # Verbose logging -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json --verbose +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json --verbose # Apply setup URI to settings file (settings only; does not run synchronisation) -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json setup "obsidian://setuplivesync?settings=..." +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json setup "obsidian://setuplivesync?settings=..." # Put text from stdin into local database -echo "Hello from stdin" | npm run cli -- /path/to/your-local-database --settings /path/to/settings.json put /vault/path/file.md +echo "Hello from stdin" | npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json put /vault/path/file.md # Output a file from local database to stdout -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json cat /vault/path/file.md +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json cat /vault/path/file.md # Output a specific revision of a file from local database -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json cat-rev /vault/path/file.md 3-abcdef +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json cat-rev /vault/path/file.md 3-abcdef # Pull a specific revision of a file from local database to local storage -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json pull-rev /vault/path/file.md /your/storage/file.old.md 3-abcdef +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json pull-rev /vault/path/file.md /your/storage/file.old.md 3-abcdef # List files in local database -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json ls /vault/path/ +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json ls /vault/path/ # Show metadata for a file in local database -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json info /vault/path/file.md +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json info /vault/path/file.md # Mark a file as deleted in local database -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json rm /vault/path/file.md +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json rm /vault/path/file.md # Resolve conflict by keeping a specific revision -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json resolve /vault/path/file.md 3-abcdef +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json resolve /vault/path/file.md 3-abcdef ``` ### Configuration @@ -159,14 +159,26 @@ Commands: info Show file metadata including current and past revisions, conflicts, and chunk list rm Mark file as deleted in local database resolve Resolve conflict by keeping the specified revision + mirror Mirror local file into local database. ``` Run via npm script: ```bash -npm run cli -- [database-path] [options] [command] [command-args] +npm run --silent cli -- [database-path] [options] [command] [command-args] ``` +#### Detailed Command Descriptions + +##### ls +`ls` lists files in the local database with optional prefix filtering. Output format is: + +```vault/path/file.mdsizemtimerevision[*] +``` +Note: `*` indicates if the file has conflicts. + +##### info + `info` output fields: - `id`: Document ID @@ -179,6 +191,38 @@ npm run cli -- [database-path] [options] [command] [command-args] - `chunks`: Number of chunk IDs - `children`: Chunk ID list +##### mirror + +`mirror` is a command that synchronises your storage with your local vault. It is essentially a process that runs upon startup in Obsidian. + +In other words, it performs the following actions: + +1. **Precondition checks** — Aborts early if any of the following conditions are not met: + - Settings must be configured (`isConfigured: true`). + - File watching must not be suspended (`suspendFileWatching: false`). + - Remediation mode must be inactive (`maxMTimeForReflectEvents: 0`). + +2. **State restoration** — On subsequent runs (after the first successful scan), restores the previous storage state before proceeding. + +3. **Expired deletion cleanup** — If `automaticallyDeleteMetadataOfDeletedFiles` is set to a positive number of days, any document that is marked deleted and whose `mtime` is older than the retention period is permanently removed from the local database. + +4. **File collection** — Enumerates files from two sources: + - **Storage**: all files under the vault path that pass `isTargetFile`. + - **Local database**: all normal documents (fetched with conflict information) whose paths are valid and pass `isTargetFile`. + - Both collections build case-insensitive ↔ case-sensitive path maps, controlled by `handleFilenameCaseSensitive`. + +5. **Categorisation and synchronisation** — The union of both file sets is split into three groups and processed concurrently (up to 10 files at a time): + + | Group | Condition | Action | + |---|---|---| + | **UPDATE DATABASE** | File exists in storage only | Store the file into the local database. | + | **UPDATE STORAGE** | File exists in database only | If the entry is active (not deleted) and not conflicted, restore the file from the database to storage. Deleted entries and conflicted entries are skipped. | + | **SYNC DATABASE AND STORAGE** | File exists in both | Compare `mtime` freshness. If storage is newer → write to database (`STORAGE → DB`). If database is newer → restore to storage (`STORAGE ← DB`). If equal → do nothing. Conflicted documents and files exceeding the size limit are always skipped. | + +6. **Initialisation flag** — On the very first successful run, writes `initialized = true` to the key-value database so that subsequent runs can restore state in step 2. + +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). + ### Planned options: - `--immediate`: Perform sync after the command (e.g. `push`, `pull`, `put`, `rm`). @@ -192,9 +236,9 @@ npm run cli -- [database-path] [options] [command] [command-args] Create default settings, apply a setup URI, then run one sync cycle. ```bash -npm run cli -- init-settings /data/livesync-settings.json -printf '%s\n' "$SETUP_PASSPHRASE" | npm run cli -- /data/vault --settings /data/livesync-settings.json setup "$SETUP_URI" -npm run cli -- /data/vault --settings /data/livesync-settings.json sync +npm run --silent cli -- init-settings /data/livesync-settings.json +printf '%s\n' "$SETUP_PASSPHRASE" | npm run --silent cli -- /data/vault --settings /data/livesync-settings.json setup "$SETUP_URI" +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json sync ``` ### 2. Scripted import and export @@ -202,8 +246,8 @@ npm run cli -- /data/vault --settings /data/livesync-settings.json sync Push local files into the database from automation, and pull them back for export or backup. ```bash -npm run cli -- /data/vault --settings /data/livesync-settings.json push ./note.md notes/note.md -npm run cli -- /data/vault --settings /data/livesync-settings.json pull notes/note.md ./exports/note.md +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json push ./note.md notes/note.md +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json pull notes/note.md ./exports/note.md ``` ### 3. Revision inspection and restore @@ -211,9 +255,9 @@ npm run cli -- /data/vault --settings /data/livesync-settings.json pull notes/no List metadata, find an older revision, then restore it by content (`cat-rev`) or file output (`pull-rev`). ```bash -npm run cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md -npm run cli -- /data/vault --settings /data/livesync-settings.json cat-rev notes/note.md 3-abcdef -npm run cli -- /data/vault --settings /data/livesync-settings.json pull-rev notes/note.md ./restore/note.old.md 3-abcdef +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json cat-rev notes/note.md 3-abcdef +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json pull-rev notes/note.md ./restore/note.old.md 3-abcdef ``` ### 4. Conflict and cleanup workflow @@ -221,9 +265,9 @@ npm run cli -- /data/vault --settings /data/livesync-settings.json pull-rev note Inspect conflicted revisions, resolve by keeping one revision, then delete obsolete files. ```bash -npm run cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md -npm run cli -- /data/vault --settings /data/livesync-settings.json resolve notes/note.md 3-abcdef -npm run cli -- /data/vault --settings /data/livesync-settings.json rm notes/obsolete.md +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json resolve notes/note.md 3-abcdef +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json rm notes/obsolete.md ``` ### 5. CI smoke test for content round-trip @@ -231,8 +275,8 @@ npm run cli -- /data/vault --settings /data/livesync-settings.json rm notes/obso Validate that `put`/`cat` is behaving as expected in a pipeline. ```bash -echo "hello-ci" | npm run cli -- /data/vault --settings /data/livesync-settings.json put ci/test.md -npm run cli -- /data/vault --settings /data/livesync-settings.json cat ci/test.md +echo "hello-ci" | npm run --silent cli -- /data/vault --settings /data/livesync-settings.json put ci/test.md +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json cat ci/test.md ``` ## Development diff --git a/src/apps/cli/commands/runCommand.ts b/src/apps/cli/commands/runCommand.ts index 18ccb35..0b68dc8 100644 --- a/src/apps/cli/commands/runCommand.ts +++ b/src/apps/cli/commands/runCommand.ts @@ -6,6 +6,8 @@ import { DEFAULT_SETTINGS, type FilePathWithPrefix, type ObsidianLiveSyncSetting import { stripAllPrefixes } from "@lib/string_and_binary/path"; import type { CLICommandContext, CLIOptions } from "./types"; import { promptForPassphrase, readStdinAsUtf8, toArrayBuffer, toVaultRelativePath } from "./utils"; +import { performFullScan } from "@lib/serviceFeatures/offlineScanner"; +import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager"; export async function runCommand(options: CLIOptions, context: CLICommandContext): Promise { const { vaultPath, core, settingsPath } = context; @@ -309,5 +311,12 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext return true; } + if (options.command === "mirror") { + console.error("[Command] mirror"); + const log = (msg: unknown) => console.error(`[Mirror] ${msg}`); + const errorManager = new UnresolvedErrorManager(core.services.appLifecycle); + return await performFullScan(core as any, log, errorManager, false, true); + } + throw new Error(`Unsupported command: ${options.command}`); } diff --git a/src/apps/cli/commands/types.ts b/src/apps/cli/commands/types.ts index 9182fd7..ca1b312 100644 --- a/src/apps/cli/commands/types.ts +++ b/src/apps/cli/commands/types.ts @@ -15,6 +15,7 @@ export type CLICommand = | "info" | "rm" | "resolve" + | "mirror" | "init-settings"; export interface CLIOptions { @@ -45,5 +46,6 @@ export const VALID_COMMANDS = new Set([ "info", "rm", "resolve", + "mirror", "init-settings", ] as const); diff --git a/src/apps/cli/main.ts b/src/apps/cli/main.ts index 43d3109..858df01 100644 --- a/src/apps/cli/main.ts +++ b/src/apps/cli/main.ts @@ -31,6 +31,8 @@ import { LOG_LEVEL_DEBUG, setGlobalLogFunction, defaultLoggerEnv } from "octagon import { runCommand } from "./commands/runCommand"; 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"; const SETTINGS_FILE = ".livesync/settings.json"; defaultLoggerEnv.minLogLevel = LOG_LEVEL_DEBUG; @@ -254,6 +256,7 @@ export async function main() { 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; @@ -298,7 +301,18 @@ export async function main() { }, () => [], // No extra modules () => [], // No add-ons - () => [] // No serviceFeatures + (core) => { + // Add target filter to prevent internal files are handled + core.services.vault.isTargetFile.addHandler(async (target) => { + const vaultPath = stripAllPrefixes(getPathFromUXFileInfo(target)); + const parts = vaultPath.split(path.sep); + // if some part of the path starts with dot, treat it as internal file and ignore. + if (parts.some((part) => part.startsWith("."))) { + return await Promise.resolve(false); + } + return await Promise.resolve(true); + }, -1 /* highest priority */); + } ); // Setup signal handlers for graceful shutdown diff --git a/src/apps/cli/package.json b/src/apps/cli/package.json index 611f54d..78e8a0e 100644 --- a/src/apps/cli/package.json +++ b/src/apps/cli/package.json @@ -18,7 +18,8 @@ "test:e2e:push-pull": "bash test/test-push-pull-linux.sh", "test:e2e:setup-put-cat": "bash test/test-setup-put-cat-linux.sh", "test:e2e:sync-two-local": "bash test/test-sync-two-local-databases-linux.sh", - "test:e2e:all": "npm run test:e2e:two-vaults && npm run test:e2e:push-pull && npm run test:e2e:setup-put-cat && npm run test:e2e:sync-two-local" + "test:e2e:mirror": "bash test/test-mirror-linux.sh", + "test:e2e:all": "npm run test:e2e:two-vaults && npm run test:e2e:push-pull && npm run test:e2e:setup-put-cat && npm run test:e2e:sync-two-local && npm run test:e2e:mirror" }, "dependencies": {}, "devDependencies": {} diff --git a/src/apps/cli/test/test-e2e-two-vaults-common.sh b/src/apps/cli/test/test-e2e-two-vaults-common.sh index 0745275..21864c5 100755 --- a/src/apps/cli/test/test-e2e-two-vaults-common.sh +++ b/src/apps/cli/test/test-e2e-two-vaults-common.sh @@ -4,8 +4,12 @@ set -euo pipefail SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" CLI_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)" cd "$CLI_DIR" - -CLI_CMD=(npm --silent run cli -- -v) +VERBOSE_TEST_LOGGING="${VERBOSE_TEST_LOGGING:-0}" +if [[ "$VERBOSE_TEST_LOGGING" == "1" ]]; then + CLI_CMD=(npm --silent run cli -- -v) +else + CLI_CMD=(npm --silent run cli --) +fi RUN_BUILD="${RUN_BUILD:-1}" KEEP_TEST_DATA="${KEEP_TEST_DATA:-0}" TEST_ENV_FILE="${TEST_ENV_FILE:-$CLI_DIR/.test.env}" diff --git a/src/apps/cli/test/test-mirror-linux.sh b/src/apps/cli/test/test-mirror-linux.sh new file mode 100755 index 0000000..24b8645 --- /dev/null +++ b/src/apps/cli/test/test-mirror-linux.sh @@ -0,0 +1,176 @@ +#!/usr/bin/env bash +# Test: mirror command — storage <-> local database synchronisation +# +# Covered cases: +# 1. Storage-only file → synced into DB (UPDATE DATABASE) +# 2. DB-only file → restored to storage (UPDATE STORAGE) +# 3. DB-deleted file → NOT restored to storage (UPDATE STORAGE skip) +# 4. Both, storage newer → DB updated (SYNC: STORAGE → DB) +# 5. Both, DB newer → storage updated (SYNC: DB → STORAGE) +# +# Not covered (require precise mtime control or artificial conflict injection): +# - Both, equal mtime → no-op (EVEN) +# - Conflicted entry → skipped +# +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +CLI_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)" +cd "$CLI_DIR" + +CLI_CMD=(npm run cli --) +RUN_BUILD="${RUN_BUILD:-1}" + +WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-test.XXXXXX")" +trap 'rm -rf "$WORK_DIR"' EXIT + +SETTINGS_FILE="$WORK_DIR/data.json" +VAULT_DIR="$WORK_DIR/vault" +mkdir -p "$VAULT_DIR/test" + +if [[ "$RUN_BUILD" == "1" ]]; then + echo "[INFO] building CLI..." + npm run build +fi + +run_cli() { + "${CLI_CMD[@]}" "$@" +} + +echo "[INFO] generating settings -> $SETTINGS_FILE" +run_cli init-settings --force "$SETTINGS_FILE" + +# isConfigured=true is required for mirror (canProceedScan checks this) +SETTINGS_FILE="$SETTINGS_FILE" node -e " +const fs = require('node:fs'); +const s = JSON.parse(fs.readFileSync(process.env.SETTINGS_FILE, 'utf-8')); +s.isConfigured = true; +fs.writeFileSync(process.env.SETTINGS_FILE, JSON.stringify(s, null, 2)); +" + +PASS=0 +FAIL=0 + +assert_pass() { echo "[PASS] $1"; PASS=$((PASS + 1)); } +assert_fail() { echo "[FAIL] $1" >&2; FAIL=$((FAIL + 1)); } + +# ───────────────────────────────────────────────────────────────────────────── +# Case 1: File exists only in storage → should be synced into DB after mirror +# ───────────────────────────────────────────────────────────────────────────── +echo "" +echo "=== Case 1: storage-only → DB ===" + +printf 'storage-only content\n' > "$VAULT_DIR/test/storage-only.md" + +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror + +RESULT_FILE="$WORK_DIR/case1-cat.txt" +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" pull test/storage-only.md "$RESULT_FILE" + +if cmp -s "$VAULT_DIR/test/storage-only.md" "$RESULT_FILE"; then + assert_pass "storage-only file was synced into DB" +else + assert_fail "storage-only file NOT synced into DB" + echo "--- storage ---" >&2; cat "$VAULT_DIR/test/storage-only.md" >&2 + echo "--- cat ---" >&2; cat "$RESULT_FILE" >&2 +fi + +# ───────────────────────────────────────────────────────────────────────────── +# Case 2: File exists only in DB → should be restored to storage after mirror +# ───────────────────────────────────────────────────────────────────────────── +echo "" +echo "=== Case 2: DB-only → storage ===" + +printf 'db-only content\n' | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" put test/db-only.md + +if [[ -f "$VAULT_DIR/test/db-only.md" ]]; then + assert_fail "db-only.md unexpectedly exists in storage before mirror" +else + echo "[INFO] confirmed: test/db-only.md not in storage before mirror" +fi + +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror + +if [[ -f "$VAULT_DIR/test/db-only.md" ]]; then + STORAGE_CONTENT="$(cat "$VAULT_DIR/test/db-only.md")" + if [[ "$STORAGE_CONTENT" == "db-only content" ]]; then + assert_pass "DB-only file was restored to storage" + else + assert_fail "DB-only file restored but content mismatch (got: '${STORAGE_CONTENT}')" + fi +else + assert_fail "DB-only file was NOT restored to storage" +fi + +# ───────────────────────────────────────────────────────────────────────────── +# Case 3: File deleted in DB → should NOT be created in storage +# ───────────────────────────────────────────────────────────────────────────── +echo "" +echo "=== Case 3: DB-deleted → storage untouched ===" + +printf 'to-be-deleted\n' | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" put test/deleted.md +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" rm test/deleted.md + +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror + +if [[ ! -f "$VAULT_DIR/test/deleted.md" ]]; then + assert_pass "deleted DB entry was not restored to storage" +else + assert_fail "deleted DB entry was incorrectly restored to storage" +fi + +# ───────────────────────────────────────────────────────────────────────────── +# Case 4: Both exist, storage is newer → DB should be updated +# ───────────────────────────────────────────────────────────────────────────── +echo "" +echo "=== Case 4: storage newer → DB updated ===" + +# Seed DB with old content (mtime ≈ now) +printf 'old content\n' | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" put test/sync-storage-newer.md + +# Write new content to storage with a timestamp 1 hour in the future +printf 'new content\n' > "$VAULT_DIR/test/sync-storage-newer.md" +touch -t "$(date -d '+1 hour' +%Y%m%d%H%M)" "$VAULT_DIR/test/sync-storage-newer.md" + +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror + +DB_RESULT_FILE="$WORK_DIR/case4-pull.txt" +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" pull test/sync-storage-newer.md "$DB_RESULT_FILE" +if cmp -s "$VAULT_DIR/test/sync-storage-newer.md" "$DB_RESULT_FILE"; then + assert_pass "DB updated to match newer storage file" +else + assert_fail "DB NOT updated to match newer storage file" + echo "--- expected(storage) ---" >&2; cat "$VAULT_DIR/test/sync-storage-newer.md" >&2 + echo "--- pulled(from db) ---" >&2; cat "$DB_RESULT_FILE" >&2 +fi + +# ───────────────────────────────────────────────────────────────────────────── +# Case 5: Both exist, DB is newer → storage should be updated +# ───────────────────────────────────────────────────────────────────────────── +echo "" +echo "=== Case 5: DB newer → storage updated ===" + +# Write old content to storage with a timestamp 1 hour in the past +printf 'old storage content\n' > "$VAULT_DIR/test/sync-db-newer.md" +touch -t "$(date -d '-1 hour' +%Y%m%d%H%M)" "$VAULT_DIR/test/sync-db-newer.md" + +# Write new content to DB only (mtime ≈ now, newer than the storage file) +printf 'new db content\n' | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" put test/sync-db-newer.md + +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror + +STORAGE_CONTENT="$(cat "$VAULT_DIR/test/sync-db-newer.md")" +if [[ "$STORAGE_CONTENT" == "new db content" ]]; then + assert_pass "storage updated to match newer DB entry" +else + assert_fail "storage NOT updated to match newer DB entry (got: '${STORAGE_CONTENT}')" +fi + +# ───────────────────────────────────────────────────────────────────────────── +# Summary +# ───────────────────────────────────────────────────────────────────────────── +echo "" +echo "Results: PASS=$PASS FAIL=$FAIL" +if [[ "$FAIL" -gt 0 ]]; then + exit 1 +fi diff --git a/src/lib b/src/lib index 35df9a1..423f6ee 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 35df9a1192b527e3fbb200c69ec243bdbc3835af +Subproject commit 423f6ee3a6c693367f9d893a6f7ec79717fb7514 diff --git a/updates.md b/updates.md index e1abc50..e218314 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,12 @@ 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 -- + +### New features + +- `mirror` command has been added to the CLI. This command is intended to mirror the storage to the local database. + ## 0.25.52-patched-1 12th March, 2026 From 338a9ba9fadf2972fb35d49e1cec72021cb6de2f Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Fri, 13 Mar 2026 18:01:38 +0900 Subject: [PATCH 079/339] Add: `mirror` command Tidy: test --- src/apps/cli/README.md | 98 ++++-- src/apps/cli/commands/runCommand.ts | 9 + src/apps/cli/commands/types.ts | 3 + src/apps/cli/main.ts | 73 ++++- .../managers/CLIStorageEventManagerAdapter.ts | 8 +- src/apps/cli/package.json | 4 +- .../cli/services/NodeKeyValueDBService.ts | 81 ++++- .../cli/test/test-e2e-two-vaults-common.sh | 282 ++++------------- src/apps/cli/test/test-helpers.sh | 295 ++++++++++++++++++ src/apps/cli/test/test-mirror-linux.sh | 169 ++++++++++ src/apps/cli/test/test-push-pull-linux.sh | 22 +- src/apps/cli/test/test-setup-put-cat-linux.sh | 120 ++++--- .../test-sync-two-local-databases-linux.sh | 179 +++++++---- src/lib | 2 +- updates.md | 6 + 15 files changed, 952 insertions(+), 399 deletions(-) create mode 100644 src/apps/cli/test/test-helpers.sh create mode 100755 src/apps/cli/test/test-mirror-linux.sh diff --git a/src/apps/cli/README.md b/src/apps/cli/README.md index 336d642..83bc7d9 100644 --- a/src/apps/cli/README.md +++ b/src/apps/cli/README.md @@ -63,43 +63,43 @@ As you know, the CLI is designed to be used in a headless environment. Hence all ```bash # Sync local database with CouchDB (no files will be changed). -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json sync +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json sync # Push files to local database -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json push /your/storage/file.md /vault/path/file.md +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json push /your/storage/file.md /vault/path/file.md # Pull files from local database -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json pull /vault/path/file.md /your/storage/file.md +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json pull /vault/path/file.md /your/storage/file.md # Verbose logging -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json --verbose +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json --verbose # Apply setup URI to settings file (settings only; does not run synchronisation) -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json setup "obsidian://setuplivesync?settings=..." +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json setup "obsidian://setuplivesync?settings=..." # Put text from stdin into local database -echo "Hello from stdin" | npm run cli -- /path/to/your-local-database --settings /path/to/settings.json put /vault/path/file.md +echo "Hello from stdin" | npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json put /vault/path/file.md # Output a file from local database to stdout -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json cat /vault/path/file.md +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json cat /vault/path/file.md # Output a specific revision of a file from local database -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json cat-rev /vault/path/file.md 3-abcdef +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json cat-rev /vault/path/file.md 3-abcdef # Pull a specific revision of a file from local database to local storage -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json pull-rev /vault/path/file.md /your/storage/file.old.md 3-abcdef +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json pull-rev /vault/path/file.md /your/storage/file.old.md 3-abcdef # List files in local database -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json ls /vault/path/ +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json ls /vault/path/ # Show metadata for a file in local database -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json info /vault/path/file.md +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json info /vault/path/file.md # Mark a file as deleted in local database -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json rm /vault/path/file.md +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json rm /vault/path/file.md # Resolve conflict by keeping a specific revision -npm run cli -- /path/to/your-local-database --settings /path/to/settings.json resolve /vault/path/file.md 3-abcdef +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json resolve /vault/path/file.md 3-abcdef ``` ### Configuration @@ -159,14 +159,26 @@ Commands: info Show file metadata including current and past revisions, conflicts, and chunk list rm Mark file as deleted in local database resolve Resolve conflict by keeping the specified revision + mirror Mirror local file into local database. ``` Run via npm script: ```bash -npm run cli -- [database-path] [options] [command] [command-args] +npm run --silent cli -- [database-path] [options] [command] [command-args] ``` +#### Detailed Command Descriptions + +##### ls +`ls` lists files in the local database with optional prefix filtering. Output format is: + +```vault/path/file.mdsizemtimerevision[*] +``` +Note: `*` indicates if the file has conflicts. + +##### info + `info` output fields: - `id`: Document ID @@ -179,6 +191,38 @@ npm run cli -- [database-path] [options] [command] [command-args] - `chunks`: Number of chunk IDs - `children`: Chunk ID list +##### mirror + +`mirror` is a command that synchronises your storage with your local vault. It is essentially a process that runs upon startup in Obsidian. + +In other words, it performs the following actions: + +1. **Precondition checks** — Aborts early if any of the following conditions are not met: + - Settings must be configured (`isConfigured: true`). + - File watching must not be suspended (`suspendFileWatching: false`). + - Remediation mode must be inactive (`maxMTimeForReflectEvents: 0`). + +2. **State restoration** — On subsequent runs (after the first successful scan), restores the previous storage state before proceeding. + +3. **Expired deletion cleanup** — If `automaticallyDeleteMetadataOfDeletedFiles` is set to a positive number of days, any document that is marked deleted and whose `mtime` is older than the retention period is permanently removed from the local database. + +4. **File collection** — Enumerates files from two sources: + - **Storage**: all files under the vault path that pass `isTargetFile`. + - **Local database**: all normal documents (fetched with conflict information) whose paths are valid and pass `isTargetFile`. + - Both collections build case-insensitive ↔ case-sensitive path maps, controlled by `handleFilenameCaseSensitive`. + +5. **Categorisation and synchronisation** — The union of both file sets is split into three groups and processed concurrently (up to 10 files at a time): + + | Group | Condition | Action | + |---|---|---| + | **UPDATE DATABASE** | File exists in storage only | Store the file into the local database. | + | **UPDATE STORAGE** | File exists in database only | If the entry is active (not deleted) and not conflicted, restore the file from the database to storage. Deleted entries and conflicted entries are skipped. | + | **SYNC DATABASE AND STORAGE** | File exists in both | Compare `mtime` freshness. If storage is newer → write to database (`STORAGE → DB`). If database is newer → restore to storage (`STORAGE ← DB`). If equal → do nothing. Conflicted documents and files exceeding the size limit are always skipped. | + +6. **Initialisation flag** — On the very first successful run, writes `initialized = true` to the key-value database so that subsequent runs can restore state in step 2. + +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). + ### Planned options: - `--immediate`: Perform sync after the command (e.g. `push`, `pull`, `put`, `rm`). @@ -192,9 +236,9 @@ npm run cli -- [database-path] [options] [command] [command-args] Create default settings, apply a setup URI, then run one sync cycle. ```bash -npm run cli -- init-settings /data/livesync-settings.json -printf '%s\n' "$SETUP_PASSPHRASE" | npm run cli -- /data/vault --settings /data/livesync-settings.json setup "$SETUP_URI" -npm run cli -- /data/vault --settings /data/livesync-settings.json sync +npm run --silent cli -- init-settings /data/livesync-settings.json +printf '%s\n' "$SETUP_PASSPHRASE" | npm run --silent cli -- /data/vault --settings /data/livesync-settings.json setup "$SETUP_URI" +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json sync ``` ### 2. Scripted import and export @@ -202,8 +246,8 @@ npm run cli -- /data/vault --settings /data/livesync-settings.json sync Push local files into the database from automation, and pull them back for export or backup. ```bash -npm run cli -- /data/vault --settings /data/livesync-settings.json push ./note.md notes/note.md -npm run cli -- /data/vault --settings /data/livesync-settings.json pull notes/note.md ./exports/note.md +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json push ./note.md notes/note.md +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json pull notes/note.md ./exports/note.md ``` ### 3. Revision inspection and restore @@ -211,9 +255,9 @@ npm run cli -- /data/vault --settings /data/livesync-settings.json pull notes/no List metadata, find an older revision, then restore it by content (`cat-rev`) or file output (`pull-rev`). ```bash -npm run cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md -npm run cli -- /data/vault --settings /data/livesync-settings.json cat-rev notes/note.md 3-abcdef -npm run cli -- /data/vault --settings /data/livesync-settings.json pull-rev notes/note.md ./restore/note.old.md 3-abcdef +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json cat-rev notes/note.md 3-abcdef +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json pull-rev notes/note.md ./restore/note.old.md 3-abcdef ``` ### 4. Conflict and cleanup workflow @@ -221,9 +265,9 @@ npm run cli -- /data/vault --settings /data/livesync-settings.json pull-rev note Inspect conflicted revisions, resolve by keeping one revision, then delete obsolete files. ```bash -npm run cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md -npm run cli -- /data/vault --settings /data/livesync-settings.json resolve notes/note.md 3-abcdef -npm run cli -- /data/vault --settings /data/livesync-settings.json rm notes/obsolete.md +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json resolve notes/note.md 3-abcdef +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json rm notes/obsolete.md ``` ### 5. CI smoke test for content round-trip @@ -231,8 +275,8 @@ npm run cli -- /data/vault --settings /data/livesync-settings.json rm notes/obso Validate that `put`/`cat` is behaving as expected in a pipeline. ```bash -echo "hello-ci" | npm run cli -- /data/vault --settings /data/livesync-settings.json put ci/test.md -npm run cli -- /data/vault --settings /data/livesync-settings.json cat ci/test.md +echo "hello-ci" | npm run --silent cli -- /data/vault --settings /data/livesync-settings.json put ci/test.md +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json cat ci/test.md ``` ## Development diff --git a/src/apps/cli/commands/runCommand.ts b/src/apps/cli/commands/runCommand.ts index 18ccb35..0b68dc8 100644 --- a/src/apps/cli/commands/runCommand.ts +++ b/src/apps/cli/commands/runCommand.ts @@ -6,6 +6,8 @@ import { DEFAULT_SETTINGS, type FilePathWithPrefix, type ObsidianLiveSyncSetting import { stripAllPrefixes } from "@lib/string_and_binary/path"; import type { CLICommandContext, CLIOptions } from "./types"; import { promptForPassphrase, readStdinAsUtf8, toArrayBuffer, toVaultRelativePath } from "./utils"; +import { performFullScan } from "@lib/serviceFeatures/offlineScanner"; +import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager"; export async function runCommand(options: CLIOptions, context: CLICommandContext): Promise { const { vaultPath, core, settingsPath } = context; @@ -309,5 +311,12 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext return true; } + if (options.command === "mirror") { + console.error("[Command] mirror"); + const log = (msg: unknown) => console.error(`[Mirror] ${msg}`); + const errorManager = new UnresolvedErrorManager(core.services.appLifecycle); + return await performFullScan(core as any, log, errorManager, false, true); + } + throw new Error(`Unsupported command: ${options.command}`); } diff --git a/src/apps/cli/commands/types.ts b/src/apps/cli/commands/types.ts index 9182fd7..a4d7fe1 100644 --- a/src/apps/cli/commands/types.ts +++ b/src/apps/cli/commands/types.ts @@ -15,12 +15,14 @@ export type CLICommand = | "info" | "rm" | "resolve" + | "mirror" | "init-settings"; export interface CLIOptions { databasePath?: string; settingsPath?: string; verbose?: boolean; + debug?: boolean; force?: boolean; command: CLICommand; commandArgs: string[]; @@ -45,5 +47,6 @@ export const VALID_COMMANDS = new Set([ "info", "rm", "resolve", + "mirror", "init-settings", ] as const); diff --git a/src/apps/cli/main.ts b/src/apps/cli/main.ts index 43d3109..460c90e 100644 --- a/src/apps/cli/main.ts +++ b/src/apps/cli/main.ts @@ -27,10 +27,12 @@ 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"; import type { InjectableSettingService } from "@/lib/src/services/implements/injectable/InjectableSettingService"; -import { LOG_LEVEL_DEBUG, setGlobalLogFunction, defaultLoggerEnv } from "octagonal-wheels/common/logger"; +import { LOG_LEVEL_DEBUG, setGlobalLogFunction, defaultLoggerEnv, LOG_LEVEL_INFO, LOG_LEVEL_URGENT, LOG_LEVEL_NOTICE } from "octagonal-wheels/common/logger"; import { runCommand } from "./commands/runCommand"; 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"; const SETTINGS_FILE = ".livesync/settings.json"; defaultLoggerEnv.minLogLevel = LOG_LEVEL_DEBUG; @@ -45,12 +47,12 @@ defaultLoggerEnv.minLogLevel = LOG_LEVEL_DEBUG; // recentLogEntries.value = [...recentLogEntries.value, entry]; // }; -setGlobalLogFunction((msg, level) => { - console.error(`[${level}] ${typeof msg === "string" ? msg : JSON.stringify(msg)}`); - if (msg instanceof Error) { - console.error(msg); - } -}); +// setGlobalLogFunction((msg, level) => { +// console.error(`[${level}] ${typeof msg === "string" ? msg : JSON.stringify(msg)}`); +// if (msg instanceof Error) { +// console.error(msg); +// } +// }); function printHelp(): void { console.log(` Self-hosted LiveSync CLI @@ -103,6 +105,7 @@ export function parseArgs(): CLIOptions { let databasePath: string | undefined; let settingsPath: string | undefined; let verbose = false; + let debug = false; let force = false; let command: CLICommand = "daemon"; const commandArgs: string[] = []; @@ -120,6 +123,10 @@ export function parseArgs(): CLIOptions { settingsPath = args[i]; break; } + case "--debug": + case "-d": + // debugging automatically enables verbose logging, as it is intended for debugging issues. + debug = true; case "--verbose": case "-v": verbose = true; @@ -165,6 +172,7 @@ export function parseArgs(): CLIOptions { databasePath, settingsPath, verbose, + debug, force, command, commandArgs, @@ -209,7 +217,18 @@ export async function main() { options.command === "rm" || options.command === "resolve"; const infoLog = avoidStdoutNoise ? console.error : console.log; - + if(options.debug){ + setGlobalLogFunction((msg, level) => { + console.error(`[${level}] ${typeof msg === "string" ? msg : JSON.stringify(msg)}`); + if (msg instanceof Error) { + console.error(msg); + } + }); + }else{ + setGlobalLogFunction((msg, level) => { + // NO OP, leave it to logFunction + }) + } if (options.command === "init-settings") { await createDefaultSettingsFile(options); return; @@ -243,8 +262,28 @@ export async function main() { const context = new NodeServiceContext(vaultPath); const serviceHubInstance = new NodeServiceHub(vaultPath, context); serviceHubInstance.API.addLog.setHandler((message: string, level: LOG_LEVEL) => { - const prefix = `[${level}]`; - if (level <= LOG_LEVEL_VERBOSE) { + let levelStr = ""; + switch (level) { + case LOG_LEVEL_DEBUG: + levelStr = "debug"; + break; + case LOG_LEVEL_VERBOSE: + levelStr = "Verbose"; + break; + case LOG_LEVEL_INFO: + levelStr = "Info"; + break; + case LOG_LEVEL_NOTICE: + levelStr = "Notice"; + break; + case LOG_LEVEL_URGENT: + levelStr = "Urgent"; + break; + default: + levelStr = `${level}`; + } + const prefix = `(${levelStr})`; + if (level <= LOG_LEVEL_INFO) { if (!options.verbose) return; } console.error(`${prefix} ${message}`); @@ -254,6 +293,7 @@ export async function main() { 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; @@ -298,7 +338,18 @@ export async function main() { }, () => [], // No extra modules () => [], // No add-ons - () => [] // No serviceFeatures + (core) => { + // Add target filter to prevent internal files are handled + core.services.vault.isTargetFile.addHandler(async (target) => { + const vaultPath = stripAllPrefixes(getPathFromUXFileInfo(target)); + const parts = vaultPath.split(path.sep); + // if some part of the path starts with dot, treat it as internal file and ignore. + if (parts.some((part) => part.startsWith("."))) { + return await Promise.resolve(false); + } + return await Promise.resolve(true); + }, -1 /* highest priority */); + } ); // Setup signal handlers for graceful shutdown diff --git a/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts b/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts index 61d214b..1334b6a 100644 --- a/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts +++ b/src/apps/cli/managers/CLIStorageEventManagerAdapter.ts @@ -66,9 +66,9 @@ class CLIStatusAdapter implements IStorageEventStatusAdapter { 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}` - ); + // console.log( + // `[StorageEventManager] Batched: ${status.batched}, Processing: ${status.processing}, Total Queued: ${status.totalQueued}` + // ); } this.lastUpdate = now; } @@ -108,7 +108,7 @@ class CLIWatchAdapter implements IStorageEventWatchAdapter { 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"); + // console.error("[CLIWatchAdapter] File watching is not enabled in CLI version"); return Promise.resolve(); } } diff --git a/src/apps/cli/package.json b/src/apps/cli/package.json index 611f54d..af2b296 100644 --- a/src/apps/cli/package.json +++ b/src/apps/cli/package.json @@ -18,7 +18,9 @@ "test:e2e:push-pull": "bash test/test-push-pull-linux.sh", "test:e2e:setup-put-cat": "bash test/test-setup-put-cat-linux.sh", "test:e2e:sync-two-local": "bash test/test-sync-two-local-databases-linux.sh", - "test:e2e:all": "npm run test:e2e:two-vaults && npm run test:e2e:push-pull && npm run test:e2e:setup-put-cat && npm run test:e2e:sync-two-local" + "test:e2e:mirror": "bash test/test-mirror-linux.sh", + "pretest:e2e:all": "npm run build", + "test:e2e:all": " export RUN_BUILD=0 && npm run test:e2e:setup-put-cat && npm run test:e2e:push-pull && npm run test:e2e:sync-two-local && npm run test:e2e:mirror && npm run test:e2e:two-vaults" }, "dependencies": {}, "devDependencies": {} diff --git a/src/apps/cli/services/NodeKeyValueDBService.ts b/src/apps/cli/services/NodeKeyValueDBService.ts index 349cd4e..5799bf6 100644 --- a/src/apps/cli/services/NodeKeyValueDBService.ts +++ b/src/apps/cli/services/NodeKeyValueDBService.ts @@ -10,6 +10,78 @@ import { createInstanceLogFunction } from "@lib/services/lib/logUtils"; import * as nodeFs from "node:fs"; import * as nodePath from "node:path"; +const NODE_KV_TYPED_KEY = "__nodeKvType"; +const NODE_KV_VALUES_KEY = "values"; + +type SerializableContainer = + | { + [NODE_KV_TYPED_KEY]: "Set"; + [NODE_KV_VALUES_KEY]: unknown[]; + } + | { + [NODE_KV_TYPED_KEY]: "Uint8Array"; + [NODE_KV_VALUES_KEY]: number[]; + } + | { + [NODE_KV_TYPED_KEY]: "ArrayBuffer"; + [NODE_KV_VALUES_KEY]: number[]; + }; + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} + +function serializeForNodeKV(value: unknown): unknown { + if (value instanceof Set) { + return { + [NODE_KV_TYPED_KEY]: "Set", + [NODE_KV_VALUES_KEY]: [...value].map((entry) => serializeForNodeKV(entry)), + } satisfies SerializableContainer; + } + if (value instanceof Uint8Array) { + return { + [NODE_KV_TYPED_KEY]: "Uint8Array", + [NODE_KV_VALUES_KEY]: Array.from(value), + } satisfies SerializableContainer; + } + if (value instanceof ArrayBuffer) { + return { + [NODE_KV_TYPED_KEY]: "ArrayBuffer", + [NODE_KV_VALUES_KEY]: Array.from(new Uint8Array(value)), + } satisfies SerializableContainer; + } + if (Array.isArray(value)) { + return value.map((entry) => serializeForNodeKV(entry)); + } + if (isRecord(value)) { + return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, serializeForNodeKV(v)])); + } + return value; +} + +function deserializeFromNodeKV(value: unknown): unknown { + if (Array.isArray(value)) { + return value.map((entry) => deserializeFromNodeKV(entry)); + } + if (!isRecord(value)) { + return value; + } + + const taggedType = value[NODE_KV_TYPED_KEY]; + const taggedValues = value[NODE_KV_VALUES_KEY]; + if (taggedType === "Set" && Array.isArray(taggedValues)) { + return new Set(taggedValues.map((entry) => deserializeFromNodeKV(entry))); + } + if (taggedType === "Uint8Array" && Array.isArray(taggedValues)) { + return Uint8Array.from(taggedValues); + } + if (taggedType === "ArrayBuffer" && Array.isArray(taggedValues)) { + return Uint8Array.from(taggedValues).buffer; + } + + return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, deserializeFromNodeKV(v)])); +} + class NodeFileKeyValueDatabase implements KeyValueDatabase { private filePath: string; private data = new Map(); @@ -29,7 +101,9 @@ class NodeFileKeyValueDatabase implements KeyValueDatabase { private load() { try { const loaded = JSON.parse(nodeFs.readFileSync(this.filePath, "utf-8")) as Record; - this.data = new Map(Object.entries(loaded)); + this.data = new Map( + Object.entries(loaded).map(([key, value]) => [key, deserializeFromNodeKV(value)]) + ); } catch { this.data = new Map(); } @@ -37,7 +111,10 @@ class NodeFileKeyValueDatabase implements KeyValueDatabase { private flush() { nodeFs.mkdirSync(nodePath.dirname(this.filePath), { recursive: true }); - nodeFs.writeFileSync(this.filePath, JSON.stringify(Object.fromEntries(this.data), null, 2), "utf-8"); + const serializable = Object.fromEntries( + [...this.data.entries()].map(([key, value]) => [key, serializeForNodeKV(value)]) + ); + nodeFs.writeFileSync(this.filePath, JSON.stringify(serializable, null, 2), "utf-8"); } async get(key: IDBValidKey): Promise { diff --git a/src/apps/cli/test/test-e2e-two-vaults-common.sh b/src/apps/cli/test/test-e2e-two-vaults-common.sh index 0745275..812d290 100755 --- a/src/apps/cli/test/test-e2e-two-vaults-common.sh +++ b/src/apps/cli/test/test-e2e-two-vaults-common.sh @@ -4,8 +4,9 @@ set -euo pipefail SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" CLI_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)" cd "$CLI_DIR" - -CLI_CMD=(npm --silent run cli -- -v) +source "$SCRIPT_DIR/test-helpers.sh" +VERBOSE_TEST_LOGGING="${VERBOSE_TEST_LOGGING:-0}" +cli_test_init_cli_cmd RUN_BUILD="${RUN_BUILD:-1}" KEEP_TEST_DATA="${KEEP_TEST_DATA:-0}" TEST_ENV_FILE="${TEST_ENV_FILE:-$CLI_DIR/.test.env}" @@ -36,27 +37,24 @@ COUCHDB_URI="" COUCHDB_DBNAME="" MINIO_BUCKET="" -require_env() { - local var_name="$1" - if [[ -z "${!var_name:-}" ]]; then - echo "[ERROR] required variable '$var_name' is missing in $TEST_ENV_FILE" >&2 - exit 1 - fi -} - if [[ "$REMOTE_TYPE" == "COUCHDB" ]]; then - require_env hostname - require_env dbname - require_env username - require_env password + cli_test_require_env hostname "$TEST_ENV_FILE" + cli_test_require_env dbname "$TEST_ENV_FILE" + cli_test_require_env username "$TEST_ENV_FILE" + cli_test_require_env password "$TEST_ENV_FILE" COUCHDB_URI="${hostname%/}" COUCHDB_DBNAME="${dbname}-${DB_SUFFIX}" + COUCHDB_USER="${username:-}" + COUCHDB_PASSWORD="${password:-}" elif [[ "$REMOTE_TYPE" == "MINIO" ]]; then - require_env accessKey - require_env secretKey - require_env minioEndpoint - require_env bucketName + cli_test_require_env accessKey "$TEST_ENV_FILE" + cli_test_require_env secretKey "$TEST_ENV_FILE" + cli_test_require_env minioEndpoint "$TEST_ENV_FILE" + cli_test_require_env bucketName "$TEST_ENV_FILE" MINIO_BUCKET="${bucketName}-${DB_SUFFIX}" + MINIO_ENDPOINT="${minioEndpoint:-}" + MINIO_ACCESS_KEY="${accessKey:-}" + MINIO_SECRET_KEY="${secretKey:-}" else echo "[ERROR] unsupported REMOTE_TYPE: $REMOTE_TYPE (use COUCHDB or MINIO)" >&2 exit 1 @@ -65,9 +63,9 @@ fi cleanup() { local exit_code=$? if [[ "$REMOTE_TYPE" == "COUCHDB" ]]; then - bash "$CLI_DIR/util/couchdb-stop.sh" >/dev/null 2>&1 || true + cli_test_stop_couchdb else - bash "$CLI_DIR/util/minio-stop.sh" >/dev/null 2>&1 || true + cli_test_stop_minio fi if [[ "$KEEP_TEST_DATA" != "1" ]]; then @@ -83,10 +81,6 @@ cleanup() { } trap cleanup EXIT -run_cli() { - "${CLI_CMD[@]}" "$@" -} - run_cli_a() { run_cli "$VAULT_A" --settings "$SETTINGS_A" "$@" } @@ -95,191 +89,28 @@ run_cli_b() { run_cli "$VAULT_B" --settings "$SETTINGS_B" "$@" } -assert_contains() { - local haystack="$1" - local needle="$2" - local message="$3" - if ! grep -Fq "$needle" <<< "$haystack"; then - echo "[FAIL] $message" >&2 - echo "[FAIL] expected to find: $needle" >&2 - echo "[FAIL] actual output:" >&2 - echo "$haystack" >&2 - exit 1 - fi -} - -assert_equal() { - local expected="$1" - local actual="$2" - local message="$3" - if [[ "$expected" != "$actual" ]]; then - echo "[FAIL] $message" >&2 - echo "[FAIL] expected: $expected" >&2 - echo "[FAIL] actual: $actual" >&2 - exit 1 - fi -} - -assert_command_fails() { - local message="$1" - shift - set +e - "$@" >"$WORK_DIR/failed-command.log" 2>&1 - local exit_code=$? - set -e - if [[ "$exit_code" -eq 0 ]]; then - echo "[FAIL] $message" >&2 - cat "$WORK_DIR/failed-command.log" >&2 - exit 1 - fi -} - -assert_files_equal() { - local expected_file="$1" - local actual_file="$2" - local message="$3" - if ! cmp -s "$expected_file" "$actual_file"; then - echo "[FAIL] $message" >&2 - echo "[FAIL] expected sha256: $(sha256sum "$expected_file" | awk '{print $1}')" >&2 - echo "[FAIL] actual sha256: $(sha256sum "$actual_file" | awk '{print $1}')" >&2 - exit 1 - fi -} - -sanitise_cat_stdout() { - sed '/^\[CLIWatchAdapter\] File watching is not enabled in CLI version$/d' -} - -extract_json_string_field() { - local field_name="$1" - node -e ' -const fs = require("node:fs"); -const fieldName = process.argv[1]; -const data = JSON.parse(fs.readFileSync(0, "utf-8")); -const value = data[fieldName]; -if (typeof value === "string") { - process.stdout.write(value); -} -' "$field_name" -} - sync_both() { run_cli_a sync >/dev/null run_cli_b sync >/dev/null } -curl_json() { - curl -4 -sS --fail --connect-timeout 3 --max-time 15 "$@" -} - configure_remote_settings() { local settings_file="$1" - SETTINGS_FILE="$settings_file" \ - REMOTE_TYPE="$REMOTE_TYPE" \ - COUCHDB_URI="$COUCHDB_URI" \ - COUCHDB_USER="${username:-}" \ - COUCHDB_PASSWORD="${password:-}" \ - COUCHDB_DBNAME="$COUCHDB_DBNAME" \ - MINIO_ENDPOINT="${minioEndpoint:-}" \ - MINIO_BUCKET="$MINIO_BUCKET" \ - MINIO_ACCESS_KEY="${accessKey:-}" \ - MINIO_SECRET_KEY="${secretKey:-}" \ - ENCRYPT="$ENCRYPT" \ - E2E_PASSPHRASE="$E2E_PASSPHRASE" \ - node <<'NODE' -const fs = require("node:fs"); -const settingsPath = process.env.SETTINGS_FILE; -const data = JSON.parse(fs.readFileSync(settingsPath, "utf-8")); - -const remoteType = process.env.REMOTE_TYPE; -if (remoteType === "COUCHDB") { - data.remoteType = ""; - data.couchDB_URI = process.env.COUCHDB_URI; - data.couchDB_USER = process.env.COUCHDB_USER; - data.couchDB_PASSWORD = process.env.COUCHDB_PASSWORD; - data.couchDB_DBNAME = process.env.COUCHDB_DBNAME; -} else if (remoteType === "MINIO") { - data.remoteType = "MINIO"; - data.bucket = process.env.MINIO_BUCKET; - data.endpoint = process.env.MINIO_ENDPOINT; - data.accessKey = process.env.MINIO_ACCESS_KEY; - data.secretKey = process.env.MINIO_SECRET_KEY; - data.region = "auto"; - data.forcePathStyle = true; -} - -data.liveSync = true; -data.syncOnStart = false; -data.syncOnSave = false; -data.usePluginSync = false; - -data.encrypt = process.env.ENCRYPT === "1"; -data.passphrase = data.encrypt ? process.env.E2E_PASSPHRASE : ""; - -data.isConfigured = true; - -fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8"); -NODE + cli_test_apply_remote_sync_settings "$settings_file" } init_settings() { local settings_file="$1" - run_cli init-settings --force "$settings_file" >/dev/null + cli_test_init_settings_file "$settings_file" configure_remote_settings "$settings_file" cat "$settings_file" } -wait_for_minio_bucket() { - local retries=30 - local delay_sec=2 - local i - for ((i = 1; i <= retries; i++)); do - if docker run --rm --network host --entrypoint=/bin/sh minio/mc -c "mc alias set myminio $minioEndpoint $accessKey $secretKey >/dev/null 2>&1 && mc ls myminio/$MINIO_BUCKET >/dev/null 2>&1"; then - return 0 - fi - bucketName="$MINIO_BUCKET" bash "$CLI_DIR/util/minio-init.sh" >/dev/null 2>&1 || true - sleep "$delay_sec" - done - return 1 -} - start_remote() { if [[ "$REMOTE_TYPE" == "COUCHDB" ]]; then - echo "[INFO] stopping leftover CouchDB container if present" - bash "$CLI_DIR/util/couchdb-stop.sh" >/dev/null 2>&1 || true - - echo "[INFO] starting CouchDB test container" - bash "$CLI_DIR/util/couchdb-start.sh" - - echo "[INFO] initialising CouchDB test container" - bash "$CLI_DIR/util/couchdb-init.sh" - - echo "[INFO] CouchDB create test database: $COUCHDB_DBNAME" - until (curl_json -X PUT --user "${username}:${password}" "${hostname}/${COUCHDB_DBNAME}"); do sleep 5; done + cli_test_start_couchdb "$COUCHDB_URI" "$COUCHDB_USER" "$COUCHDB_PASSWORD" "$COUCHDB_DBNAME" else - echo "[INFO] stopping leftover MinIO container if present" - bash "$CLI_DIR/util/minio-stop.sh" >/dev/null 2>&1 || true - - echo "[INFO] starting MinIO test container" - bucketName="$MINIO_BUCKET" bash "$CLI_DIR/util/minio-start.sh" - - echo "[INFO] initialising MinIO test bucket: $MINIO_BUCKET" - local minio_init_ok=0 - for _ in 1 2 3 4 5; do - if bucketName="$MINIO_BUCKET" bash "$CLI_DIR/util/minio-init.sh"; then - minio_init_ok=1 - break - fi - sleep 2 - done - if [[ "$minio_init_ok" != "1" ]]; then - echo "[FAIL] could not initialise MinIO bucket after retries: $MINIO_BUCKET" >&2 - exit 1 - fi - if ! wait_for_minio_bucket; then - echo "[FAIL] MinIO bucket not ready: $MINIO_BUCKET" >&2 - exit 1 - fi + cli_test_start_minio "$MINIO_ENDPOINT" "$MINIO_ACCESS_KEY" "$MINIO_SECRET_KEY" "$MINIO_BUCKET" fi } @@ -313,14 +144,14 @@ TARGET_CONFLICT="e2e/conflict.md" echo "[CASE] A puts and A can get info" printf 'alpha-from-a\n' | run_cli_a put "$TARGET_A_ONLY" >/dev/null INFO_A_ONLY="$(run_cli_a info "$TARGET_A_ONLY")" -assert_contains "$INFO_A_ONLY" "\"path\": \"$TARGET_A_ONLY\"" "A info should include path after put" +cli_test_assert_contains "$INFO_A_ONLY" "\"path\": \"$TARGET_A_ONLY\"" "A info should include path after put" echo "[PASS] A put/info" echo "[CASE] A puts, both sync, and B can get info" printf 'visible-after-sync\n' | run_cli_a put "$TARGET_SYNC" >/dev/null sync_both INFO_B_SYNC="$(run_cli_b info "$TARGET_SYNC")" -assert_contains "$INFO_B_SYNC" "\"path\": \"$TARGET_SYNC\"" "B info should include path after sync" +cli_test_assert_contains "$INFO_B_SYNC" "\"path\": \"$TARGET_SYNC\"" "B info should include path after sync" echo "[PASS] sync A->B and B info" echo "[CASE] A pushes and puts, both sync, and B can pull and cat" @@ -331,9 +162,9 @@ run_cli_a push "$PUSH_SRC" "$TARGET_PUSH" >/dev/null printf 'put-content-%s\n' "$DB_SUFFIX" | run_cli_a put "$TARGET_PUT" >/dev/null sync_both run_cli_b pull "$TARGET_PUSH" "$PULL_DST" >/dev/null -assert_files_equal "$PUSH_SRC" "$PULL_DST" "B pull result does not match pushed source" -CAT_B_PUT="$(run_cli_b cat "$TARGET_PUT" | sanitise_cat_stdout)" -assert_equal "put-content-$DB_SUFFIX" "$CAT_B_PUT" "B cat should return A put content" +cli_test_assert_files_equal "$PUSH_SRC" "$PULL_DST" "B pull result does not match pushed source" +CAT_B_PUT="$(run_cli_b cat "$TARGET_PUT" | cli_test_sanitise_cat_stdout)" +cli_test_assert_equal "put-content-$DB_SUFFIX" "$CAT_B_PUT" "B cat should return A put content" echo "[PASS] push/pull and put/cat across vaults" echo "[CASE] A pushes binary, both sync, and B can pull identical bytes" @@ -343,31 +174,44 @@ head -c 4096 /dev/urandom > "$PUSH_BINARY_SRC" run_cli_a push "$PUSH_BINARY_SRC" "$TARGET_PUSH_BINARY" >/dev/null sync_both run_cli_b pull "$TARGET_PUSH_BINARY" "$PULL_BINARY_DST" >/dev/null -assert_files_equal "$PUSH_BINARY_SRC" "$PULL_BINARY_DST" "B pull result does not match pushed binary source" +cli_test_assert_files_equal "$PUSH_BINARY_SRC" "$PULL_BINARY_DST" "B pull result does not match pushed binary source" echo "[PASS] binary push/pull across vaults" echo "[CASE] A removes, both sync, and B can no longer cat" run_cli_a rm "$TARGET_PUT" >/dev/null sync_both -assert_command_fails "B cat should fail after A removed the file and synced" run_cli_b cat "$TARGET_PUT" +cli_test_assert_command_fails "B cat should fail after A removed the file and synced" "$WORK_DIR/failed-command.log" run_cli_b cat "$TARGET_PUT" echo "[PASS] rm is replicated" echo "[CASE] verify conflict detection" printf 'conflict-base\n' | run_cli_a put "$TARGET_CONFLICT" >/dev/null sync_both INFO_B_BASE="$(run_cli_b info "$TARGET_CONFLICT")" -assert_contains "$INFO_B_BASE" "\"path\": \"$TARGET_CONFLICT\"" "B should be able to info before creating conflict" +cli_test_assert_contains "$INFO_B_BASE" "\"path\": \"$TARGET_CONFLICT\"" "B should be able to info before creating conflict" printf 'conflict-from-a-%s\n' "$DB_SUFFIX" | run_cli_a put "$TARGET_CONFLICT" >/dev/null printf 'conflict-from-b-%s\n' "$DB_SUFFIX" | run_cli_b put "$TARGET_CONFLICT" >/dev/null -run_cli_a sync >/dev/null -run_cli_b sync >/dev/null -run_cli_a sync >/dev/null +INFO_A_CONFLICT="" +INFO_B_CONFLICT="" +CONFLICT_DETECTED=0 -INFO_A_CONFLICT="$(run_cli_a info "$TARGET_CONFLICT")" -INFO_B_CONFLICT="$(run_cli_b info "$TARGET_CONFLICT")" -if grep -qF '"conflicts": "N/A"' <<< "$INFO_A_CONFLICT" && grep -qF '"conflicts": "N/A"' <<< "$INFO_B_CONFLICT"; then +for side in a b a; do + if [[ "$side" == "a" ]]; then + run_cli_a sync >/dev/null + else + run_cli_b sync >/dev/null + fi + + INFO_A_CONFLICT="$(run_cli_a info "$TARGET_CONFLICT")" + INFO_B_CONFLICT="$(run_cli_b info "$TARGET_CONFLICT")" + if ! grep -qF '"conflicts": "N/A"' <<< "$INFO_A_CONFLICT" || ! grep -qF '"conflicts": "N/A"' <<< "$INFO_B_CONFLICT"; then + CONFLICT_DETECTED=1 + break + fi +done + +if [[ "$CONFLICT_DETECTED" != "1" ]]; then echo "[FAIL] conflict was expected but both A and B show Conflicts: N/A" >&2 echo "--- A info ---" >&2 echo "$INFO_A_CONFLICT" >&2 @@ -399,7 +243,7 @@ fi echo "[PASS] ls marks conflicts" echo "[CASE] resolve conflict on A and verify both vaults are clean" -KEEP_REVISION="$(printf '%s' "$INFO_A_CONFLICT" | extract_json_string_field revision)" +KEEP_REVISION="$(printf '%s' "$INFO_A_CONFLICT" | cli_test_json_string_field_from_stdin revision)" if [[ -z "$KEEP_REVISION" ]]; then echo "[FAIL] could not extract current revision from A info output" >&2 echo "$INFO_A_CONFLICT" >&2 @@ -411,7 +255,7 @@ run_cli_a resolve "$TARGET_CONFLICT" "$KEEP_REVISION" >/dev/null INFO_A_RESOLVED="" INFO_B_RESOLVED="" RESOLVE_PROPAGATED=0 -for _ in 1 2 3 4 5; do +for _ in 1 2 3 4 5 6; do sync_both INFO_A_RESOLVED="$(run_cli_a info "$TARGET_CONFLICT")" INFO_B_RESOLVED="$(run_cli_b info "$TARGET_CONFLICT")" @@ -419,19 +263,15 @@ for _ in 1 2 3 4 5; do RESOLVE_PROPAGATED=1 break fi -done -if [[ "$RESOLVE_PROPAGATED" != "1" ]]; then - KEEP_REVISION_B="$(printf '%s' "$INFO_B_RESOLVED" | extract_json_string_field revision)" - if [[ -n "$KEEP_REVISION_B" ]]; then - run_cli_b resolve "$TARGET_CONFLICT" "$KEEP_REVISION_B" >/dev/null - sync_both - INFO_A_RESOLVED="$(run_cli_a info "$TARGET_CONFLICT")" - INFO_B_RESOLVED="$(run_cli_b info "$TARGET_CONFLICT")" - if grep -qF '"conflicts": "N/A"' <<< "$INFO_A_RESOLVED" && grep -qF '"conflicts": "N/A"' <<< "$INFO_B_RESOLVED"; then - RESOLVE_PROPAGATED=1 + + # Retry from A only when conflict remains due to eventual consistency. + if ! grep -qF '"conflicts": "N/A"' <<< "$INFO_A_RESOLVED"; then + KEEP_REVISION_A="$(printf '%s' "$INFO_A_RESOLVED" | cli_test_json_string_field_from_stdin revision)" + if [[ -n "$KEEP_REVISION_A" ]]; then + run_cli_a resolve "$TARGET_CONFLICT" "$KEEP_REVISION_A" >/dev/null || true fi fi -fi +done if [[ "$RESOLVE_PROPAGATED" != "1" ]]; then echo "[FAIL] conflicts should be resolved on both vaults" >&2 @@ -453,9 +293,9 @@ if [[ "$LS_A_RESOLVED_REV" == *"*" || "$LS_B_RESOLVED_REV" == *"*" ]]; then exit 1 fi -CAT_A_RESOLVED="$(run_cli_a cat "$TARGET_CONFLICT" | sanitise_cat_stdout)" -CAT_B_RESOLVED="$(run_cli_b cat "$TARGET_CONFLICT" | sanitise_cat_stdout)" -assert_equal "$CAT_A_RESOLVED" "$CAT_B_RESOLVED" "resolved content should match across both vaults" +CAT_A_RESOLVED="$(run_cli_a cat "$TARGET_CONFLICT" | cli_test_sanitise_cat_stdout)" +CAT_B_RESOLVED="$(run_cli_b cat "$TARGET_CONFLICT" | cli_test_sanitise_cat_stdout)" +cli_test_assert_equal "$CAT_A_RESOLVED" "$CAT_B_RESOLVED" "resolved content should match across both vaults" echo "[PASS] resolve is replicated and ls reflects resolved state" echo "[PASS] all requested E2E scenarios completed (${TEST_LABEL})" diff --git a/src/apps/cli/test/test-helpers.sh b/src/apps/cli/test/test-helpers.sh new file mode 100644 index 0000000..a508f95 --- /dev/null +++ b/src/apps/cli/test/test-helpers.sh @@ -0,0 +1,295 @@ +#!/usr/bin/env bash + +cli_test_init_cli_cmd() { + if [[ "${VERBOSE_TEST_LOGGING:-0}" == "1" ]]; then + CLI_CMD=(npm --silent run cli -- -v) + else + CLI_CMD=(npm --silent run cli --) + fi +} + +run_cli() { + "${CLI_CMD[@]}" "$@" +} + +cli_test_require_env() { + local var_name="$1" + local env_file="${2:-${TEST_ENV_FILE:-environment}}" + if [[ -z "${!var_name:-}" ]]; then + echo "[ERROR] required variable '$var_name' is missing in $env_file" >&2 + exit 1 + fi +} + +cli_test_assert_contains() { + local haystack="$1" + local needle="$2" + local message="$3" + if ! grep -Fq "$needle" <<< "$haystack"; then + echo "[FAIL] $message" >&2 + echo "[FAIL] expected to find: $needle" >&2 + echo "[FAIL] actual output:" >&2 + echo "$haystack" >&2 + exit 1 + fi +} + +cli_test_assert_equal() { + local expected="$1" + local actual="$2" + local message="$3" + if [[ "$expected" != "$actual" ]]; then + echo "[FAIL] $message" >&2 + echo "[FAIL] expected: $expected" >&2 + echo "[FAIL] actual: $actual" >&2 + exit 1 + fi +} + +cli_test_assert_command_fails() { + local message="$1" + local log_file="$2" + shift 2 + set +e + "$@" >"$log_file" 2>&1 + local exit_code=$? + set -e + if [[ "$exit_code" -eq 0 ]]; then + echo "[FAIL] $message" >&2 + cat "$log_file" >&2 + exit 1 + fi +} + +cli_test_assert_files_equal() { + local expected_file="$1" + local actual_file="$2" + local message="$3" + if ! cmp -s "$expected_file" "$actual_file"; then + echo "[FAIL] $message" >&2 + echo "[FAIL] expected sha256: $(sha256sum "$expected_file" | awk '{print $1}')" >&2 + echo "[FAIL] actual sha256: $(sha256sum "$actual_file" | awk '{print $1}')" >&2 + exit 1 + fi +} + +cli_test_sanitise_cat_stdout() { + sed '/^\[CLIWatchAdapter\] File watching is not enabled in CLI version$/d' +} + +cli_test_json_string_field_from_stdin() { + local field_name="$1" + node -e ' +const fs = require("node:fs"); +const fieldName = process.argv[1]; +const data = JSON.parse(fs.readFileSync(0, "utf-8")); +const value = data[fieldName]; +if (typeof value === "string") { + process.stdout.write(value); +} +' "$field_name" +} + +cli_test_json_string_field_from_file() { + local json_file="$1" + local field_name="$2" + node -e ' +const fs = require("node:fs"); +const jsonFile = process.argv[1]; +const fieldName = process.argv[2]; +const data = JSON.parse(fs.readFileSync(jsonFile, "utf-8")); +const value = data[fieldName]; +if (typeof value === "string") { + process.stdout.write(value); +} +' "$json_file" "$field_name" +} + +cli_test_json_field_is_na() { + local json_file="$1" + local field_name="$2" + [[ "$(cli_test_json_string_field_from_file "$json_file" "$field_name")" == "N/A" ]] +} + +cli_test_curl_json() { + curl -4 -sS --fail --connect-timeout 3 --max-time 15 "$@" +} + +cli_test_init_settings_file() { + local settings_file="$1" + run_cli init-settings --force "$settings_file" >/dev/null +} + +cli_test_mark_settings_configured() { + local settings_file="$1" + SETTINGS_FILE="$settings_file" node <<'NODE' +const fs = require("node:fs"); +const settingsPath = process.env.SETTINGS_FILE; +const data = JSON.parse(fs.readFileSync(settingsPath, "utf-8")); +data.isConfigured = true; +fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8"); +NODE +} + +cli_test_apply_couchdb_settings() { + local settings_file="$1" + local couchdb_uri="$2" + local couchdb_user="$3" + local couchdb_password="$4" + local couchdb_dbname="$5" + local live_sync="${6:-0}" + SETTINGS_FILE="$settings_file" \ + COUCHDB_URI="$couchdb_uri" \ + COUCHDB_USER="$couchdb_user" \ + COUCHDB_PASSWORD="$couchdb_password" \ + COUCHDB_DBNAME="$couchdb_dbname" \ + LIVE_SYNC="$live_sync" \ + node <<'NODE' +const fs = require("node:fs"); +const settingsPath = process.env.SETTINGS_FILE; +const data = JSON.parse(fs.readFileSync(settingsPath, "utf-8")); +data.couchDB_URI = process.env.COUCHDB_URI; +data.couchDB_USER = process.env.COUCHDB_USER; +data.couchDB_PASSWORD = process.env.COUCHDB_PASSWORD; +data.couchDB_DBNAME = process.env.COUCHDB_DBNAME; +if (process.env.LIVE_SYNC === "1") { + data.liveSync = true; + data.syncOnStart = false; + data.syncOnSave = false; + data.usePluginSync = false; +} +data.isConfigured = true; +fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8"); +NODE +} + +cli_test_apply_remote_sync_settings() { + local settings_file="$1" + SETTINGS_FILE="$settings_file" \ + REMOTE_TYPE="$REMOTE_TYPE" \ + COUCHDB_URI="$COUCHDB_URI" \ + COUCHDB_USER="${COUCHDB_USER:-}" \ + COUCHDB_PASSWORD="${COUCHDB_PASSWORD:-}" \ + COUCHDB_DBNAME="$COUCHDB_DBNAME" \ + MINIO_ENDPOINT="${MINIO_ENDPOINT:-}" \ + MINIO_BUCKET="$MINIO_BUCKET" \ + MINIO_ACCESS_KEY="${MINIO_ACCESS_KEY:-}" \ + MINIO_SECRET_KEY="${MINIO_SECRET_KEY:-}" \ + ENCRYPT="${ENCRYPT:-0}" \ + E2E_PASSPHRASE="${E2E_PASSPHRASE:-}" \ + node <<'NODE' +const fs = require("node:fs"); +const settingsPath = process.env.SETTINGS_FILE; +const data = JSON.parse(fs.readFileSync(settingsPath, "utf-8")); + +const remoteType = process.env.REMOTE_TYPE; +if (remoteType === "COUCHDB") { + data.remoteType = ""; + data.couchDB_URI = process.env.COUCHDB_URI; + data.couchDB_USER = process.env.COUCHDB_USER; + data.couchDB_PASSWORD = process.env.COUCHDB_PASSWORD; + data.couchDB_DBNAME = process.env.COUCHDB_DBNAME; +} else if (remoteType === "MINIO") { + data.remoteType = "MINIO"; + data.bucket = process.env.MINIO_BUCKET; + data.endpoint = process.env.MINIO_ENDPOINT; + data.accessKey = process.env.MINIO_ACCESS_KEY; + data.secretKey = process.env.MINIO_SECRET_KEY; + data.region = "auto"; + data.forcePathStyle = true; +} + +data.liveSync = true; +data.syncOnStart = false; +data.syncOnSave = false; +data.usePluginSync = false; +data.encrypt = process.env.ENCRYPT === "1"; +data.passphrase = data.encrypt ? process.env.E2E_PASSPHRASE : ""; +data.isConfigured = true; + +fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8"); +NODE +} + +cli_test_stop_couchdb() { + bash "$CLI_DIR/util/couchdb-stop.sh" >/dev/null 2>&1 || true +} + +cli_test_start_couchdb() { + local couchdb_uri="$1" + local couchdb_user="$2" + local couchdb_password="$3" + local couchdb_dbname="$4" + echo "[INFO] stopping leftover CouchDB container if present" + cli_test_stop_couchdb + + echo "[INFO] starting CouchDB test container" + bash "$CLI_DIR/util/couchdb-start.sh" + + echo "[INFO] initialising CouchDB test container" + bash "$CLI_DIR/util/couchdb-init.sh" + + echo "[INFO] CouchDB create test database: $couchdb_dbname" + until (cli_test_curl_json -X PUT --user "${couchdb_user}:${couchdb_password}" "${couchdb_uri}/${couchdb_dbname}"); do sleep 5; done +} + +cli_test_stop_minio() { + bash "$CLI_DIR/util/minio-stop.sh" >/dev/null 2>&1 || true +} + +cli_test_wait_for_minio_bucket() { + local minio_endpoint="$1" + local minio_access_key="$2" + local minio_secret_key="$3" + local minio_bucket="$4" + local retries=30 + local delay_sec=2 + local i + for ((i = 1; i <= retries; i++)); do + if docker run --rm --network host --entrypoint=/bin/sh minio/mc -c "mc alias set myminio $minio_endpoint $minio_access_key $minio_secret_key >/dev/null 2>&1 && mc ls myminio/$minio_bucket >/dev/null 2>&1"; then + return 0 + fi + bucketName="$minio_bucket" bash "$CLI_DIR/util/minio-init.sh" >/dev/null 2>&1 || true + sleep "$delay_sec" + done + return 1 +} + +cli_test_start_minio() { + local minio_endpoint="$1" + local minio_access_key="$2" + local minio_secret_key="$3" + local minio_bucket="$4" + local minio_init_ok=0 + + echo "[INFO] stopping leftover MinIO container if present" + cli_test_stop_minio + + echo "[INFO] starting MinIO test container" + bucketName="$minio_bucket" bash "$CLI_DIR/util/minio-start.sh" + + echo "[INFO] initialising MinIO test bucket: $minio_bucket" + for _ in 1 2 3 4 5; do + if bucketName="$minio_bucket" bash "$CLI_DIR/util/minio-init.sh"; then + minio_init_ok=1 + break + fi + sleep 2 + done + if [[ "$minio_init_ok" != "1" ]]; then + echo "[FAIL] could not initialise MinIO bucket after retries: $minio_bucket" >&2 + exit 1 + fi + if ! cli_test_wait_for_minio_bucket "$minio_endpoint" "$minio_access_key" "$minio_secret_key" "$minio_bucket"; then + echo "[FAIL] MinIO bucket not ready: $minio_bucket" >&2 + exit 1 + fi +} + +display_test_info(){ + echo "======================" + echo "Script: ${BASH_SOURCE[1]:-$0}" + echo "Date: $(date -u +%Y-%m-%dT%H:%M:%SZ)" + echo "Git commit: $(git -C "$SCRIPT_DIR/.." rev-parse --short HEAD 2>/dev/null || echo "N/A")" + echo "======================" +} \ No newline at end of file diff --git a/src/apps/cli/test/test-mirror-linux.sh b/src/apps/cli/test/test-mirror-linux.sh new file mode 100755 index 0000000..3ccb56a --- /dev/null +++ b/src/apps/cli/test/test-mirror-linux.sh @@ -0,0 +1,169 @@ +#!/usr/bin/env bash +# Test: mirror command — storage <-> local database synchronisation +# +# Covered cases: +# 1. Storage-only file → synced into DB (UPDATE DATABASE) +# 2. DB-only file → restored to storage (UPDATE STORAGE) +# 3. DB-deleted file → NOT restored to storage (UPDATE STORAGE skip) +# 4. Both, storage newer → DB updated (SYNC: STORAGE → DB) +# 5. Both, DB newer → storage updated (SYNC: DB → STORAGE) +# +# Not covered (require precise mtime control or artificial conflict injection): +# - Both, equal mtime → no-op (EVEN) +# - Conflicted entry → skipped +# +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-test.XXXXXX")" +trap 'rm -rf "$WORK_DIR"' EXIT + +SETTINGS_FILE="$WORK_DIR/data.json" +VAULT_DIR="$WORK_DIR/vault" +mkdir -p "$VAULT_DIR/test" + +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" + +# isConfigured=true is required for mirror (canProceedScan checks this) +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: File exists only in storage → should be synced into DB after mirror +# ───────────────────────────────────────────────────────────────────────────── +echo "" +echo "=== Case 1: storage-only → DB ===" + +printf 'storage-only content\n' > "$VAULT_DIR/test/storage-only.md" + +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror + +RESULT_FILE="$WORK_DIR/case1-cat.txt" +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" pull test/storage-only.md "$RESULT_FILE" + +if cmp -s "$VAULT_DIR/test/storage-only.md" "$RESULT_FILE"; then + assert_pass "storage-only file was synced into DB" +else + assert_fail "storage-only file NOT synced into DB" + echo "--- storage ---" >&2; cat "$VAULT_DIR/test/storage-only.md" >&2 + echo "--- cat ---" >&2; cat "$RESULT_FILE" >&2 +fi + +# ───────────────────────────────────────────────────────────────────────────── +# Case 2: File exists only in DB → should be restored to storage after mirror +# ───────────────────────────────────────────────────────────────────────────── +echo "" +echo "=== Case 2: DB-only → storage ===" + +printf 'db-only content\n' | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" put test/db-only.md + +if [[ -f "$VAULT_DIR/test/db-only.md" ]]; then + assert_fail "db-only.md unexpectedly exists in storage before mirror" +else + echo "[INFO] confirmed: test/db-only.md not in storage before mirror" +fi + +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror + +if [[ -f "$VAULT_DIR/test/db-only.md" ]]; then + STORAGE_CONTENT="$(cat "$VAULT_DIR/test/db-only.md")" + if [[ "$STORAGE_CONTENT" == "db-only content" ]]; then + assert_pass "DB-only file was restored to storage" + else + assert_fail "DB-only file restored but content mismatch (got: '${STORAGE_CONTENT}')" + fi +else + assert_fail "DB-only file was NOT restored to storage" +fi + +# ───────────────────────────────────────────────────────────────────────────── +# Case 3: File deleted in DB → should NOT be created in storage +# ───────────────────────────────────────────────────────────────────────────── +echo "" +echo "=== Case 3: DB-deleted → storage untouched ===" + +printf 'to-be-deleted\n' | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" put test/deleted.md +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" rm test/deleted.md + +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror + +if [[ ! -f "$VAULT_DIR/test/deleted.md" ]]; then + assert_pass "deleted DB entry was not restored to storage" +else + assert_fail "deleted DB entry was incorrectly restored to storage" +fi + +# ───────────────────────────────────────────────────────────────────────────── +# Case 4: Both exist, storage is newer → DB should be updated +# ───────────────────────────────────────────────────────────────────────────── +echo "" +echo "=== Case 4: storage newer → DB updated ===" + +# Seed DB with old content (mtime ≈ now) +printf 'old content\n' | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" put test/sync-storage-newer.md + +# Write new content to storage with a timestamp 1 hour in the future +printf 'new content\n' > "$VAULT_DIR/test/sync-storage-newer.md" +touch -t "$(date -d '+1 hour' +%Y%m%d%H%M)" "$VAULT_DIR/test/sync-storage-newer.md" + +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror + +DB_RESULT_FILE="$WORK_DIR/case4-pull.txt" +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" pull test/sync-storage-newer.md "$DB_RESULT_FILE" +if cmp -s "$VAULT_DIR/test/sync-storage-newer.md" "$DB_RESULT_FILE"; then + assert_pass "DB updated to match newer storage file" +else + assert_fail "DB NOT updated to match newer storage file" + echo "--- expected(storage) ---" >&2; cat "$VAULT_DIR/test/sync-storage-newer.md" >&2 + echo "--- pulled(from db) ---" >&2; cat "$DB_RESULT_FILE" >&2 +fi + +# ───────────────────────────────────────────────────────────────────────────── +# Case 5: Both exist, DB is newer → storage should be updated +# ───────────────────────────────────────────────────────────────────────────── +echo "" +echo "=== Case 5: DB newer → storage updated ===" + +# Write old content to storage with a timestamp 1 hour in the past +printf 'old storage content\n' > "$VAULT_DIR/test/sync-db-newer.md" +touch -t "$(date -d '-1 hour' +%Y%m%d%H%M)" "$VAULT_DIR/test/sync-db-newer.md" + +# Write new content to DB only (mtime ≈ now, newer than the storage file) +printf 'new db content\n' | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" put test/sync-db-newer.md + +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror + +STORAGE_CONTENT="$(cat "$VAULT_DIR/test/sync-db-newer.md")" +if [[ "$STORAGE_CONTENT" == "new db content" ]]; then + assert_pass "storage updated to match newer DB entry" +else + assert_fail "storage NOT updated to match newer DB entry (got: '${STORAGE_CONTENT}')" +fi + +# ───────────────────────────────────────────────────────────────────────────── +# Summary +# ───────────────────────────────────────────────────────────────────────────── +echo "" +echo "Results: PASS=$PASS FAIL=$FAIL" +if [[ "$FAIL" -gt 0 ]]; then + exit 1 +fi diff --git a/src/apps/cli/test/test-push-pull-linux.sh b/src/apps/cli/test/test-push-pull-linux.sh index ffa73ef..8ba1e40 100644 --- a/src/apps/cli/test/test-push-pull-linux.sh +++ b/src/apps/cli/test/test-push-pull-linux.sh @@ -4,10 +4,12 @@ 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 -CLI_CMD=(npm run cli --) RUN_BUILD="${RUN_BUILD:-1}" REMOTE_PATH="${REMOTE_PATH:-test/push-pull.txt}" +cli_test_init_cli_cmd WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-test.XXXXXX")" trap 'rm -rf "$WORK_DIR"' EXIT @@ -19,26 +21,12 @@ if [[ "$RUN_BUILD" == "1" ]]; then npm run build fi -run_cli() { - "${CLI_CMD[@]}" "$@" -} - echo "[INFO] generating settings from DEFAULT_SETTINGS -> $SETTINGS_FILE" -run_cli init-settings --force "$SETTINGS_FILE" +cli_test_init_settings_file "$SETTINGS_FILE" if [[ -n "${COUCHDB_URI:-}" && -n "${COUCHDB_USER:-}" && -n "${COUCHDB_PASSWORD:-}" && -n "${COUCHDB_DBNAME:-}" ]]; then echo "[INFO] applying CouchDB env vars to generated settings" - SETTINGS_FILE="$SETTINGS_FILE" node <<'NODE' -const fs = require("node:fs"); -const settingsPath = process.env.SETTINGS_FILE; -const data = JSON.parse(fs.readFileSync(settingsPath, "utf-8")); -data.couchDB_URI = process.env.COUCHDB_URI; -data.couchDB_USER = process.env.COUCHDB_USER; -data.couchDB_PASSWORD = process.env.COUCHDB_PASSWORD; -data.couchDB_DBNAME = process.env.COUCHDB_DBNAME; -data.isConfigured = true; -fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8"); -NODE + cli_test_apply_couchdb_settings "$SETTINGS_FILE" "$COUCHDB_URI" "$COUCHDB_USER" "$COUCHDB_PASSWORD" "$COUCHDB_DBNAME" else echo "[WARN] CouchDB env vars are not fully set. push/pull may fail unless generated settings are updated." fi diff --git a/src/apps/cli/test/test-setup-put-cat-linux.sh b/src/apps/cli/test/test-setup-put-cat-linux.sh index 0e4be1e..f24d1d0 100755 --- a/src/apps/cli/test/test-setup-put-cat-linux.sh +++ b/src/apps/cli/test/test-setup-put-cat-linux.sh @@ -5,11 +5,13 @@ SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" CLI_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)" REPO_ROOT="$(cd -- "$CLI_DIR/../../.." && pwd)" cd "$CLI_DIR" +source "$SCRIPT_DIR/test-helpers.sh" +display_test_info -CLI_CMD=(npm run cli --) RUN_BUILD="${RUN_BUILD:-1}" REMOTE_PATH="${REMOTE_PATH:-test/setup-put-cat.txt}" SETUP_PASSPHRASE="${SETUP_PASSPHRASE:-setup-passphrase}" +cli_test_init_cli_cmd WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-test.XXXXXX")" trap 'rm -rf "$WORK_DIR"' EXIT @@ -21,12 +23,8 @@ if [[ "$RUN_BUILD" == "1" ]]; then npm run build fi -run_cli() { - "${CLI_CMD[@]}" "$@" -} - echo "[INFO] generating settings from DEFAULT_SETTINGS -> $SETTINGS_FILE" -run_cli init-settings --force "$SETTINGS_FILE" +cli_test_init_settings_file "$SETTINGS_FILE" echo "[INFO] creating setup URI from settings" SETUP_URI="$( @@ -84,7 +82,7 @@ CAT_OUTPUT="$WORK_DIR/cat-output.txt" run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" cat "$REMOTE_PATH" > "$CAT_OUTPUT" CAT_OUTPUT_CLEAN="$WORK_DIR/cat-output-clean.txt" -grep -v '^\[CLIWatchAdapter\] File watching is not enabled in CLI version$' "$CAT_OUTPUT" > "$CAT_OUTPUT_CLEAN" || true +cli_test_sanitise_cat_stdout < "$CAT_OUTPUT" > "$CAT_OUTPUT_CLEAN" if cmp -s "$SRC_FILE" "$CAT_OUTPUT_CLEAN"; then echo "[PASS] setup/put/cat roundtrip matched" @@ -175,48 +173,52 @@ echo "[INFO] info $REMOTE_PATH" INFO_OUTPUT="$WORK_DIR/info-output.txt" run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" info "$REMOTE_PATH" > "$INFO_OUTPUT" -# Check required label lines -for label in "ID:" "Revision:" "Conflicts:" "Filename:" "Path:" "Size:" "Chunks:"; do - if ! grep -q "^$label" "$INFO_OUTPUT"; then - echo "[FAIL] info output missing label: $label" >&2 - cat "$INFO_OUTPUT" >&2 - exit 1 - fi -done - -# Path value must match -INFO_PATH="$(grep '^Path:' "$INFO_OUTPUT" | sed 's/^Path:[[:space:]]*//')" -if [[ "$INFO_PATH" != "$REMOTE_PATH" ]]; then - echo "[FAIL] info Path mismatch: $INFO_PATH" >&2 - exit 1 -fi - -# Filename must be the basename -INFO_FILENAME="$(grep '^Filename:' "$INFO_OUTPUT" | sed 's/^Filename:[[:space:]]*//')" EXPECTED_FILENAME="$(basename "$REMOTE_PATH")" -if [[ "$INFO_FILENAME" != "$EXPECTED_FILENAME" ]]; then - echo "[FAIL] info Filename mismatch: $INFO_FILENAME != $EXPECTED_FILENAME" >&2 - exit 1 -fi +set +e +INFO_JSON_CHECK="$( + INFO_OUTPUT="$INFO_OUTPUT" REMOTE_PATH="$REMOTE_PATH" EXPECTED_FILENAME="$EXPECTED_FILENAME" node - <<'NODE' +const fs = require("node:fs"); -# Size must be numeric -INFO_SIZE="$(grep '^Size:' "$INFO_OUTPUT" | sed 's/^Size:[[:space:]]*//')" -if [[ ! "$INFO_SIZE" =~ ^[0-9]+$ ]]; then - echo "[FAIL] info Size is not numeric: $INFO_SIZE" >&2 - exit 1 -fi +const content = fs.readFileSync(process.env.INFO_OUTPUT, "utf-8"); +let data; +try { + data = JSON.parse(content); +} catch (ex) { + console.error("invalid-json"); + process.exit(1); +} -# Chunks count must be numeric and ≥1 -INFO_CHUNKS="$(grep '^Chunks:' "$INFO_OUTPUT" | sed 's/^Chunks:[[:space:]]*//')" -if [[ ! "$INFO_CHUNKS" =~ ^[0-9]+$ ]] || [[ "$INFO_CHUNKS" -lt 1 ]]; then - echo "[FAIL] info Chunks is not a positive integer: $INFO_CHUNKS" >&2 - exit 1 -fi - -# Conflicts should be N/A (no live CouchDB) -INFO_CONFLICTS="$(grep '^Conflicts:' "$INFO_OUTPUT" | sed 's/^Conflicts:[[:space:]]*//')" -if [[ "$INFO_CONFLICTS" != "N/A" ]]; then - echo "[FAIL] info Conflicts expected N/A, got: $INFO_CONFLICTS" >&2 +if (!data || typeof data !== "object") { + console.error("invalid-payload"); + process.exit(1); +} +if (data.path !== process.env.REMOTE_PATH) { + console.error(`path-mismatch:${String(data.path)}`); + process.exit(1); +} +if (data.filename !== process.env.EXPECTED_FILENAME) { + console.error(`filename-mismatch:${String(data.filename)}`); + process.exit(1); +} +if (!Number.isInteger(data.size) || data.size < 0) { + console.error(`size-invalid:${String(data.size)}`); + process.exit(1); +} +if (!Number.isInteger(data.chunks) || data.chunks < 1) { + console.error(`chunks-invalid:${String(data.chunks)}`); + process.exit(1); +} +if (data.conflicts !== "N/A") { + console.error(`conflicts-invalid:${String(data.conflicts)}`); + process.exit(1); +} +NODE +)" +INFO_JSON_EXIT=$? +set -e +if [[ "$INFO_JSON_EXIT" -ne 0 ]]; then + echo "[FAIL] info JSON output validation failed: $INFO_JSON_CHECK" >&2 + cat "$INFO_OUTPUT" >&2 exit 1 fi @@ -292,8 +294,30 @@ echo "[INFO] info $REV_PATH (past revisions)" REV_INFO_OUTPUT="$WORK_DIR/rev-info-output.txt" run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" info "$REV_PATH" > "$REV_INFO_OUTPUT" -PAST_REV="$(grep '^ rev: ' "$REV_INFO_OUTPUT" | head -n 1 | sed 's/^ rev: //')" -if [[ -z "$PAST_REV" ]]; then +set +e +PAST_REV="$( + REV_INFO_OUTPUT="$REV_INFO_OUTPUT" node - <<'NODE' +const fs = require("node:fs"); + +const content = fs.readFileSync(process.env.REV_INFO_OUTPUT, "utf-8"); +let data; +try { + data = JSON.parse(content); +} catch { + process.exit(1); +} + +const revisions = Array.isArray(data?.revisions) ? data.revisions : []; +const revision = revisions.find((rev) => typeof rev === "string" && rev !== "N/A"); +if (!revision) { + process.exit(1); +} +process.stdout.write(revision); +NODE +)" +PAST_REV_EXIT=$? +set -e +if [[ "$PAST_REV_EXIT" -ne 0 ]] || [[ -z "$PAST_REV" ]]; then echo "[FAIL] info output did not include any past revision" >&2 cat "$REV_INFO_OUTPUT" >&2 exit 1 diff --git a/src/apps/cli/test/test-sync-two-local-databases-linux.sh b/src/apps/cli/test/test-sync-two-local-databases-linux.sh index 8875943..119c170 100755 --- a/src/apps/cli/test/test-sync-two-local-databases-linux.sh +++ b/src/apps/cli/test/test-sync-two-local-databases-linux.sh @@ -1,39 +1,66 @@ #!/usr/bin/env bash -## TODO: test this script. I would love to go to my bed today (3a.m.) However, I am so excited about the new CLI that I want to at least get this skeleton in place. Delightful days! 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 -CLI_CMD=(npm run cli --) RUN_BUILD="${RUN_BUILD:-1}" -COUCHDB_URI="${COUCHDB_URI:-}" -COUCHDB_USER="${COUCHDB_USER:-}" -COUCHDB_PASSWORD="${COUCHDB_PASSWORD:-}" -COUCHDB_DBNAME_BASE="${COUCHDB_DBNAME:-livesync-cli-e2e}" +TEST_ENV_FILE="${TEST_ENV_FILE:-$CLI_DIR/.test.env}" +cli_test_init_cli_cmd + +if [[ ! -f "$TEST_ENV_FILE" ]]; then + echo "[ERROR] test env file not found: $TEST_ENV_FILE" >&2 + exit 1 +fi + +set -a +source "$TEST_ENV_FILE" +set +a + + +WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-two-db-test.XXXXXX")" + +if [[ "$RUN_BUILD" == "1" ]]; then + echo "[INFO] building CLI..." + npm run build +fi +DB_SUFFIX="$(date +%s)-$RANDOM" + +COUCHDB_URI="${hostname%/}" +COUCHDB_DBNAME="${dbname}-${DB_SUFFIX}" +COUCHDB_USER="${username:-}" +COUCHDB_PASSWORD="${password:-}" if [[ -z "$COUCHDB_URI" || -z "$COUCHDB_USER" || -z "$COUCHDB_PASSWORD" ]]; then echo "[ERROR] COUCHDB_URI, COUCHDB_USER, COUCHDB_PASSWORD are required" >&2 exit 1 fi -WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-two-db-test.XXXXXX")" -trap 'rm -rf "$WORK_DIR"' EXIT -if [[ "$RUN_BUILD" == "1" ]]; then - echo "[INFO] building CLI..." - npm run build -fi +cleanup() { + local exit_code=$? + cli_test_stop_couchdb -run_cli() { - "${CLI_CMD[@]}" "$@" + rm -rf "$WORK_DIR" + + # Note: we do not attempt to delete the test database, as it may cause issues if the test failed in a way that leaves the database in an inconsistent state. The test database is named with a unique suffix, so it should not interfere with other tests. + echo "[INFO] test completed with exit code $exit_code. Test database '$COUCHDB_DBNAME' is not deleted for debugging purposes." + exit "$exit_code" +} +trap cleanup EXIT + + +start_remote() { + cli_test_start_couchdb "$COUCHDB_URI" "$COUCHDB_USER" "$COUCHDB_PASSWORD" "$COUCHDB_DBNAME" } -DB_SUFFIX="$(date +%s)-$RANDOM" -COUCHDB_DBNAME="${COUCHDB_DBNAME_BASE}-${DB_SUFFIX}" + echo "[INFO] using CouchDB database: $COUCHDB_DBNAME" +start_remote VAULT_A="$WORK_DIR/vault-a" VAULT_B="$WORK_DIR/vault-b" @@ -41,31 +68,12 @@ SETTINGS_A="$WORK_DIR/a-settings.json" SETTINGS_B="$WORK_DIR/b-settings.json" mkdir -p "$VAULT_A" "$VAULT_B" -run_cli init-settings --force "$SETTINGS_A" >/dev/null -run_cli init-settings --force "$SETTINGS_B" >/dev/null +cli_test_init_settings_file "$SETTINGS_A" +cli_test_init_settings_file "$SETTINGS_B" apply_settings() { local settings_file="$1" - SETTINGS_FILE="$settings_file" \ - COUCHDB_URI="$COUCHDB_URI" \ - COUCHDB_USER="$COUCHDB_USER" \ - COUCHDB_PASSWORD="$COUCHDB_PASSWORD" \ - COUCHDB_DBNAME="$COUCHDB_DBNAME" \ - node <<'NODE' -const fs = require("node:fs"); -const settingsPath = process.env.SETTINGS_FILE; -const data = JSON.parse(fs.readFileSync(settingsPath, "utf-8")); -data.couchDB_URI = process.env.COUCHDB_URI; -data.couchDB_USER = process.env.COUCHDB_USER; -data.couchDB_PASSWORD = process.env.COUCHDB_PASSWORD; -data.couchDB_DBNAME = process.env.COUCHDB_DBNAME; -data.liveSync = true; -data.syncOnStart = false; -data.syncOnSave = false; -data.usePluginSync = false; -data.isConfigured = true; -fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8"); -NODE + cli_test_apply_couchdb_settings "$settings_file" "$COUCHDB_URI" "$COUCHDB_USER" "$COUCHDB_PASSWORD" "$COUCHDB_DBNAME" 1 } apply_settings "$SETTINGS_A" @@ -95,24 +103,12 @@ cat_b() { run_cli_b cat "$1" } -assert_equal() { - local expected="$1" - local actual="$2" - local message="$3" - if [[ "$expected" != "$actual" ]]; then - echo "[FAIL] $message" >&2 - echo "expected: $expected" >&2 - echo "actual: $actual" >&2 - exit 1 - fi -} - echo "[INFO] case1: A creates file, B can read after sync" printf 'from-a\n' | run_cli_a put shared/from-a.txt >/dev/null sync_a sync_b VALUE_FROM_B="$(cat_b shared/from-a.txt)" -assert_equal "from-a" "$VALUE_FROM_B" "B could not read file created on A" +cli_test_assert_equal "from-a" "$VALUE_FROM_B" "B could not read file created on A" echo "[PASS] case1 passed" echo "[INFO] case2: B creates file, A can read after sync" @@ -120,7 +116,7 @@ printf 'from-b\n' | run_cli_b put shared/from-b.txt >/dev/null sync_b sync_a VALUE_FROM_A="$(cat_a shared/from-b.txt)" -assert_equal "from-b" "$VALUE_FROM_A" "A could not read file created on B" +cli_test_assert_equal "from-b" "$VALUE_FROM_A" "A could not read file created on B" echo "[PASS] case2 passed" echo "[INFO] case3: concurrent edits create conflict" @@ -131,15 +127,25 @@ sync_b printf 'edit-from-a\n' | run_cli_a put shared/conflicted.txt >/dev/null printf 'edit-from-b\n' | run_cli_b put shared/conflicted.txt >/dev/null -sync_a -sync_b - INFO_A="$WORK_DIR/info-a.txt" INFO_B="$WORK_DIR/info-b.txt" -run_cli_a info shared/conflicted.txt > "$INFO_A" -run_cli_b info shared/conflicted.txt > "$INFO_B" +CONFLICT_DETECTED=0 +for side in a b; do + if [[ "$side" == "a" ]]; then + sync_a + else + sync_b + fi -if grep -q '^Conflicts: N/A$' "$INFO_A" && grep -q '^Conflicts: N/A$' "$INFO_B"; then + run_cli_a info shared/conflicted.txt > "$INFO_A" + run_cli_b info shared/conflicted.txt > "$INFO_B" + if ! cli_test_json_field_is_na "$INFO_A" conflicts || ! cli_test_json_field_is_na "$INFO_B" conflicts; then + CONFLICT_DETECTED=1 + break + fi +done + +if [[ "$CONFLICT_DETECTED" != "1" ]]; then echo "[FAIL] expected conflict after concurrent edits, but both sides show N/A" >&2 echo "--- A info ---" >&2 cat "$INFO_A" >&2 @@ -150,21 +156,60 @@ fi echo "[PASS] case3 conflict detected" echo "[INFO] case4: resolve on A, sync, and verify B has no conflict" -KEEP_REV="$(sed -n 's/^Revision:[[:space:]]*//p' "$INFO_A" | head -n 1)" +INFO_A_AFTER="$WORK_DIR/info-a-after-resolve.txt" +INFO_B_AFTER="$WORK_DIR/info-b-after-resolve.txt" + +# Ensure A sees the conflict before resolving; otherwise resolve may be a no-op. +for _ in 1 2 3 4 5; do + run_cli_a info shared/conflicted.txt > "$INFO_A_AFTER" + if ! cli_test_json_field_is_na "$INFO_A_AFTER" conflicts; then + break + fi + sync_b + sync_a +done + +run_cli_a info shared/conflicted.txt > "$INFO_A_AFTER" +if cli_test_json_field_is_na "$INFO_A_AFTER" conflicts; then + echo "[FAIL] A does not see conflict, cannot resolve from A only" >&2 + cat "$INFO_A_AFTER" >&2 + exit 1 +fi + +KEEP_REV="$(cli_test_json_string_field_from_file "$INFO_A_AFTER" revision)" if [[ -z "$KEEP_REV" ]]; then - echo "[FAIL] could not read Revision from A info output" >&2 - cat "$INFO_A" >&2 + echo "[FAIL] could not read revision from A info output" >&2 + cat "$INFO_A_AFTER" >&2 exit 1 fi run_cli_a resolve shared/conflicted.txt "$KEEP_REV" >/dev/null -sync_a -sync_b -INFO_B_AFTER="$WORK_DIR/info-b-after-resolve.txt" -run_cli_b info shared/conflicted.txt > "$INFO_B_AFTER" -if ! grep -q '^Conflicts: N/A$' "$INFO_B_AFTER"; then - echo "[FAIL] B still has conflicts after resolving on A and syncing" >&2 +RESOLVE_PROPAGATED=0 +for _ in 1 2 3 4 5 6; do + sync_a + sync_b + run_cli_a info shared/conflicted.txt > "$INFO_A_AFTER" + run_cli_b info shared/conflicted.txt > "$INFO_B_AFTER" + if cli_test_json_field_is_na "$INFO_A_AFTER" conflicts && cli_test_json_field_is_na "$INFO_B_AFTER" conflicts; then + RESOLVE_PROPAGATED=1 + break + fi + + # Retry resolve from A only when conflict remains due to eventual consistency. + if ! cli_test_json_field_is_na "$INFO_A_AFTER" conflicts; then + KEEP_REV_A="$(cli_test_json_string_field_from_file "$INFO_A_AFTER" revision)" + if [[ -n "$KEEP_REV_A" ]]; then + run_cli_a resolve shared/conflicted.txt "$KEEP_REV_A" >/dev/null || true + fi + fi +done + +if [[ "$RESOLVE_PROPAGATED" != "1" ]]; then + echo "[FAIL] conflicts should be resolved on both A and B" >&2 + echo "--- A info after resolve ---" >&2 + cat "$INFO_A_AFTER" >&2 + echo "--- B info after resolve ---" >&2 cat "$INFO_B_AFTER" >&2 exit 1 fi diff --git a/src/lib b/src/lib index 35df9a1..423f6ee 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 35df9a1192b527e3fbb200c69ec243bdbc3835af +Subproject commit 423f6ee3a6c693367f9d893a6f7ec79717fb7514 diff --git a/updates.md b/updates.md index e1abc50..e218314 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,12 @@ 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 -- + +### New features + +- `mirror` command has been added to the CLI. This command is intended to mirror the storage to the local database. + ## 0.25.52-patched-1 12th March, 2026 From 44890a34e899d12f814ecbc9b4b45d24c029f44b Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Fri, 13 Mar 2026 23:08:05 +0900 Subject: [PATCH 080/339] remove conflicting option. --- .github/workflows/unit-ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/unit-ci.yml b/.github/workflows/unit-ci.yml index c4a0ffa..2307e82 100644 --- a/.github/workflows/unit-ci.yml +++ b/.github/workflows/unit-ci.yml @@ -19,9 +19,6 @@ on: - 'esbuild.config.mjs' - 'eslint.config.mjs' - '.github/workflows/unit-ci.yml' - paths-ignore: - - '*.md' - - '**/*.md' permissions: contents: read From bf93bddbdd96d45941e89209bb4b2583d51b46be Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Fri, 13 Mar 2026 23:34:38 +0900 Subject: [PATCH 081/339] Fix: prevent transfer twice. --- src/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib b/src/lib index 423f6ee..f77404c 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 423f6ee3a6c693367f9d893a6f7ec79717fb7514 +Subproject commit f77404c926e06e1320984158680c19b1541f1655 From dfe13b1abd653b98631c2bafdaedeee8899f028a Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 14 Mar 2026 15:08:31 +0900 Subject: [PATCH 082/339] Fixed: - No longer unexpected `Unhandled Rejections` during P2P operations (waiting acceptance). CLI new features - P2P sync has been implemented. --- package-lock.json | 503 +++++++++++++++++- package.json | 1 + src/apps/cli/README.md | 29 + src/apps/cli/commands/p2p.ts | 149 ++++++ src/apps/cli/commands/p2p.unit.spec.ts | 18 + src/apps/cli/commands/runCommand.ts | 37 ++ src/apps/cli/commands/types.ts | 6 + src/apps/cli/entrypoint.ts | 6 + src/apps/cli/main.ts | 26 +- src/apps/cli/main.unit.spec.ts | 27 + src/apps/cli/package.json | 8 +- .../cli/services/NodeKeyValueDBService.ts | 4 +- src/apps/cli/test/test-helpers.sh | 51 ++ src/apps/cli/test/test-p2p-host-linux.sh | 64 +++ .../cli/test/test-p2p-peers-local-relay.sh | 86 +++ src/apps/cli/test/test-p2p-sync-linux.sh | 115 ++++ .../test-p2p-three-nodes-conflict-linux.sh | 242 +++++++++ src/apps/cli/util/p2p-init.sh | 2 + src/apps/cli/util/p2p-start.sh | 2 + src/apps/cli/util/p2p-stop.sh | 3 + src/apps/cli/vite.config.ts | 12 +- src/lib | 2 +- updates.md | 4 +- 23 files changed, 1373 insertions(+), 24 deletions(-) create mode 100644 src/apps/cli/commands/p2p.ts create mode 100644 src/apps/cli/commands/p2p.unit.spec.ts create mode 100644 src/apps/cli/test/test-p2p-host-linux.sh create mode 100644 src/apps/cli/test/test-p2p-peers-local-relay.sh create mode 100644 src/apps/cli/test/test-p2p-sync-linux.sh create mode 100644 src/apps/cli/test/test-p2p-three-nodes-conflict-linux.sh create mode 100755 src/apps/cli/util/p2p-init.sh create mode 100755 src/apps/cli/util/p2p-start.sh create mode 100755 src/apps/cli/util/p2p-stop.sh diff --git a/package-lock.json b/package-lock.json index 27d8650..2da4406 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "fflate": "^0.8.2", "idb": "^8.0.3", "minimatch": "^10.2.2", + "node-datachannel": "^0.32.1", "octagonal-wheels": "^0.1.45", "pouchdb-adapter-leveldb": "^9.0.0", "qrcode-generator": "^1.4.4", @@ -7245,6 +7246,12 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, "node_modules/cjs-module-lexer": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", @@ -7595,6 +7602,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/deep-eql": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", @@ -7604,6 +7626,15 @@ "node": ">=6" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -8092,7 +8123,6 @@ "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, "license": "MIT", "dependencies": { "once": "^1.4.0" @@ -8890,6 +8920,15 @@ "bare-events": "^2.7.0" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -9204,6 +9243,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, "node_modules/fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -9468,6 +9513,12 @@ "node": ">= 14" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, "node_modules/glob": { "version": "13.0.6", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", @@ -9909,6 +9960,12 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, "node_modules/interface-datastore": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-8.3.2.tgz", @@ -11649,6 +11706,18 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "10.2.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", @@ -11711,6 +11780,12 @@ "dev": true, "license": "MIT" }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, "node_modules/modern-tar": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.5.tgz", @@ -11834,6 +11909,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, "node_modules/napi-macros": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", @@ -11855,6 +11936,31 @@ "node": ">= 0.4.0" } }, + "node_modules/node-abi": { + "version": "3.88.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.88.0.tgz", + "integrity": "sha512-At6b4UqIEVudaqPsXjmUO1r/N5BUr4yhDGs5PkBE8/oG5+TfLPhFechiskFsnT6Ql0VfUXbalUUCbfXxtj7K+w==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-datachannel": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/node-datachannel/-/node-datachannel-0.32.1.tgz", + "integrity": "sha512-r4UdtA0lCsz6XrG84pJ6lntAyw/MHpmBOhEkg5UQcmWTEpANqCPkMos6rj/QZDdq3GBUsdI/wst5acwWUiibCA==", + "hasInstallScript": true, + "license": "MPL 2.0", + "dependencies": { + "prebuild-install": "^7.1.3" + }, + "engines": { + "node": ">=18.20.0" + } + }, "node_modules/node-fetch": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", @@ -12053,7 +12159,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -13028,6 +13133,119 @@ "integrity": "sha512-fXqsVn+rmlPtxaAIGaQP5TkiaT39OMwvMk+ScLLtHrmfXD2KBO6fe/qBl38N/rpTn0h/A058dPN4fLAHt550zA==", "dev": true }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/prebuild-install/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/prebuild-install/node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/prebuild-install/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -13171,7 +13389,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "dev": true, "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", @@ -13251,6 +13468,30 @@ "integrity": "sha512-Mt2NznMgepLfORijhQMncE26IhkmjEphig+/1fKC0OtaKwys/gpvpmswSjoN01SS+VO951mj0L4VIDXdXsjnfA==", "license": "Apache-2.0 OR MIT" }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/readable-stream": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", @@ -13682,7 +13923,6 @@ "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -13899,6 +14139,51 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/sirv": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", @@ -15221,6 +15506,18 @@ "@esbuild/win32-x64": "0.27.3" } }, + "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==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -16621,7 +16918,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/write-stream": { @@ -21716,6 +22012,11 @@ "readdirp": "^4.0.1" } }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, "cjs-module-lexer": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", @@ -21960,11 +22261,24 @@ "integrity": "sha512-G7Cqgaelq68XHJNGlZ7lrNQyhZGsFqpwtGFexqUv4IQdjKoSYF7ipZ9UuTJZUSQXFj/XaoBLuEVIVqr8EJngEQ==", "dev": true }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + } + }, "deep-eql": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==" }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -22280,7 +22594,6 @@ "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, "requires": { "once": "^1.4.0" } @@ -22840,6 +23153,11 @@ "bare-events": "^2.7.0" } }, + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" + }, "expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -23072,6 +23390,11 @@ "signal-exit": "^4.0.1" } }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, "fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -23245,6 +23568,11 @@ "debug": "^4.3.4" } }, + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, "glob": { "version": "13.0.6", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", @@ -23533,6 +23861,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, "interface-datastore": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-8.3.2.tgz", @@ -24747,6 +25080,11 @@ "mime-db": "1.52.0" } }, + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + }, "minimatch": { "version": "10.2.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", @@ -24787,6 +25125,11 @@ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "dev": true }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, "modern-tar": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.5.tgz", @@ -24870,6 +25213,11 @@ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true }, + "napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==" + }, "napi-macros": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", @@ -24886,6 +25234,22 @@ "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==" }, + "node-abi": { + "version": "3.88.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.88.0.tgz", + "integrity": "sha512-At6b4UqIEVudaqPsXjmUO1r/N5BUr4yhDGs5PkBE8/oG5+TfLPhFechiskFsnT6Ql0VfUXbalUUCbfXxtj7K+w==", + "requires": { + "semver": "^7.3.5" + } + }, + "node-datachannel": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/node-datachannel/-/node-datachannel-0.32.1.tgz", + "integrity": "sha512-r4UdtA0lCsz6XrG84pJ6lntAyw/MHpmBOhEkg5UQcmWTEpANqCPkMos6rj/QZDdq3GBUsdI/wst5acwWUiibCA==", + "requires": { + "prebuild-install": "^7.1.3" + } + }, "node-fetch": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", @@ -25012,7 +25376,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "requires": { "wrappy": "1" } @@ -25699,6 +26062,84 @@ "integrity": "sha512-fXqsVn+rmlPtxaAIGaQP5TkiaT39OMwvMk+ScLLtHrmfXD2KBO6fe/qBl38N/rpTn0h/A058dPN4fLAHt550zA==", "dev": true }, + "prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "requires": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "dependencies": { + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==" + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + } + } + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -25805,7 +26246,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -25855,6 +26295,24 @@ "resolved": "https://registry.npmjs.org/race-signal/-/race-signal-1.1.3.tgz", "integrity": "sha512-Mt2NznMgepLfORijhQMncE26IhkmjEphig+/1fKC0OtaKwys/gpvpmswSjoN01SS+VO951mj0L4VIDXdXsjnfA==" }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" + } + } + }, "readable-stream": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", @@ -26139,8 +26597,7 @@ "semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==" }, "serialize-error": { "version": "12.0.0", @@ -26282,6 +26739,21 @@ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" + }, + "simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "requires": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "sirv": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", @@ -27057,6 +27529,14 @@ } } }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -27856,8 +28336,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "write-stream": { "version": "0.4.3", diff --git a/package.json b/package.json index 458fbf8..5086786 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,7 @@ "fflate": "^0.8.2", "idb": "^8.0.3", "minimatch": "^10.2.2", + "node-datachannel": "^0.32.1", "octagonal-wheels": "^0.1.45", "pouchdb-adapter-leveldb": "^9.0.0", "qrcode-generator": "^1.4.4", diff --git a/src/apps/cli/README.md b/src/apps/cli/README.md index 83bc7d9..9f53639 100644 --- a/src/apps/cli/README.md +++ b/src/apps/cli/README.md @@ -148,6 +148,9 @@ Options: Commands: init-settings [path] Create settings JSON from DEFAULT_SETTINGS sync Run one replication cycle and exit + p2p-peers Show discovered peers as [peer] + p2p-sync Synchronise with specified peer-id or peer-name + p2p-host Start P2P host mode and wait until interrupted (Ctrl+C) push Push local file into local database path pull Pull file from local database into local file pull-rev Pull specific revision into local file @@ -177,6 +180,32 @@ npm run --silent cli -- [database-path] [options] [command] [command-args] ``` Note: `*` indicates if the file has conflicts. +##### p2p-peers + +`p2p-peers ` waits for the specified number of seconds, then prints each discovered peer on a separate line: + +```text +[peer] +``` + +Use this command to select a target for `p2p-sync`. + +##### p2p-sync + +`p2p-sync ` discovers peers up to the specified timeout and synchronises with the selected peer. + +- `` accepts either `peer-id` or `peer-name` from `p2p-peers` output. +- On success, the command prints a completion message to standard error and exits with status code `0`. +- On failure, the command prints an error message and exits non-zero. + +##### p2p-host + +`p2p-host` starts the local P2P host and keeps running until interrupted. + +- Other peers can discover and synchronise with this host while it is running. +- Stop the host with `Ctrl+C`. +- In CLI mode, behaviour is non-interactive and acceptance follows settings. + ##### info `info` output fields: diff --git a/src/apps/cli/commands/p2p.ts b/src/apps/cli/commands/p2p.ts new file mode 100644 index 0000000..41013e0 --- /dev/null +++ b/src/apps/cli/commands/p2p.ts @@ -0,0 +1,149 @@ +import type { LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; +import { P2P_DEFAULT_SETTINGS, SETTING_KEY_P2P_DEVICE_NAME, type EntryDoc } from "@lib/common/types"; +import type { ServiceContext } from "@lib/services/base/ServiceBase"; +import { TrysteroReplicator } from "@lib/replication/trystero/TrysteroReplicator"; + +type CLIP2PPeer = { + peerId: string; + name: string; +}; + +function delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export function parseTimeoutSeconds(value: string, commandName: string): number { + const timeoutSec = Number(value); + if (!Number.isFinite(timeoutSec) || timeoutSec < 0) { + throw new Error(`${commandName} requires a non-negative timeout in seconds`); + } + return timeoutSec; +} + +function validateP2PSettings(core: LiveSyncBaseCore) { + const settings = core.services.setting.currentSettings(); + if (!settings.P2P_Enabled) { + throw new Error("P2P is disabled in settings (P2P_Enabled=false)"); + } + if (!settings.P2P_AppID) { + settings.P2P_AppID = P2P_DEFAULT_SETTINGS.P2P_AppID; + } + // CLI mode is non-interactive. + settings.P2P_IsHeadless = true; +} + +async function createReplicator(core: LiveSyncBaseCore): Promise { + validateP2PSettings(core); + const getSettings = () => core.services.setting.currentSettings(); + const getDB = () => core.services.database.localDatabase.localDatabase; + const getSimpleStore = () => core.services.keyValueDB.openSimpleStore("p2p-sync"); + const getDeviceName = () => + core.services.config.getSmallConfig(SETTING_KEY_P2P_DEVICE_NAME) || core.services.vault.getVaultName(); + + const env = { + get settings() { + return getSettings(); + }, + get db() { + return getDB(); + }, + get simpleStore() { + return getSimpleStore(); + }, + get deviceName() { + return getDeviceName(); + }, + get platform() { + return core.services.API.getPlatform(); + }, + get confirm() { + return core.services.API.confirm; + }, + processReplicatedDocs: async (docs: EntryDoc[]) => { + await core.services.replication.parseSynchroniseResult(docs as any); + }, + }; + + return new TrysteroReplicator(env as any); +} + +function getSortedPeers(replicator: TrysteroReplicator): CLIP2PPeer[] { + return [...replicator.knownAdvertisements] + .map((peer) => ({ peerId: peer.peerId, name: peer.name })) + .sort((a, b) => a.peerId.localeCompare(b.peerId)); +} + +export async function collectPeers( + core: LiveSyncBaseCore, + timeoutSec: number +): Promise { + const replicator = await createReplicator(core); + await replicator.open(); + try { + await delay(timeoutSec * 1000); + return getSortedPeers(replicator); + } finally { + await replicator.close(); + } +} + +function resolvePeer(peers: CLIP2PPeer[], peerToken: string): CLIP2PPeer | undefined { + const byId = peers.find((peer) => peer.peerId === peerToken); + if (byId) { + return byId; + } + const byName = peers.filter((peer) => peer.name === peerToken); + if (byName.length > 1) { + throw new Error(`Multiple peers matched by name '${peerToken}'. Use peer-id instead.`); + } + if (byName.length === 1) { + return byName[0]; + } + return undefined; +} + +export async function syncWithPeer( + core: LiveSyncBaseCore, + peerToken: string, + timeoutSec: number +): Promise { + const replicator = await createReplicator(core); + await replicator.open(); + try { + const timeoutMs = timeoutSec * 1000; + const start = Date.now(); + let targetPeer: CLIP2PPeer | undefined; + + while (Date.now() - start <= timeoutMs) { + const peers = getSortedPeers(replicator); + targetPeer = resolvePeer(peers, peerToken); + if (targetPeer) { + break; + } + await delay(200); + } + + if (!targetPeer) { + throw new Error(`Peer '${peerToken}' was not found within ${timeoutSec} seconds`); + } + + const pullResult = await replicator.replicateFrom(targetPeer.peerId, false); + if (pullResult && "error" in pullResult && pullResult.error) { + throw pullResult.error; + } + const pushResult = (await replicator.requestSynchroniseToPeer(targetPeer.peerId)) as any; + if (!pushResult || pushResult.ok !== true) { + throw pushResult?.error ?? new Error("P2P sync failed while requesting remote sync"); + } + + return targetPeer; + } finally { + await replicator.close(); + } +} + +export async function openP2PHost(core: LiveSyncBaseCore): Promise { + const replicator = await createReplicator(core); + await replicator.open(); + return replicator; +} diff --git a/src/apps/cli/commands/p2p.unit.spec.ts b/src/apps/cli/commands/p2p.unit.spec.ts new file mode 100644 index 0000000..d0c2342 --- /dev/null +++ b/src/apps/cli/commands/p2p.unit.spec.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from "vitest"; +import { parseTimeoutSeconds } from "./p2p"; + +describe("p2p command helpers", () => { + it("accepts non-negative timeout", () => { + expect(parseTimeoutSeconds("0", "p2p-peers")).toBe(0); + expect(parseTimeoutSeconds("2.5", "p2p-sync")).toBe(2.5); + }); + + it("rejects invalid timeout values", () => { + expect(() => parseTimeoutSeconds("-1", "p2p-peers")).toThrow( + "p2p-peers requires a non-negative timeout in seconds" + ); + expect(() => parseTimeoutSeconds("abc", "p2p-sync")).toThrow( + "p2p-sync requires a non-negative timeout in seconds" + ); + }); +}); diff --git a/src/apps/cli/commands/runCommand.ts b/src/apps/cli/commands/runCommand.ts index 0b68dc8..b005acb 100644 --- a/src/apps/cli/commands/runCommand.ts +++ b/src/apps/cli/commands/runCommand.ts @@ -6,6 +6,7 @@ import { DEFAULT_SETTINGS, type FilePathWithPrefix, type ObsidianLiveSyncSetting import { stripAllPrefixes } from "@lib/string_and_binary/path"; import type { CLICommandContext, CLIOptions } from "./types"; import { promptForPassphrase, readStdinAsUtf8, toArrayBuffer, toVaultRelativePath } from "./utils"; +import { collectPeers, openP2PHost, parseTimeoutSeconds, syncWithPeer } from "./p2p"; import { performFullScan } from "@lib/serviceFeatures/offlineScanner"; import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager"; @@ -23,6 +24,42 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext return !!result; } + if (options.command === "p2p-peers") { + if (options.commandArgs.length < 1) { + throw new Error("p2p-peers requires one argument: "); + } + const timeoutSec = parseTimeoutSeconds(options.commandArgs[0], "p2p-peers"); + console.error(`[Command] p2p-peers timeout=${timeoutSec}s`); + const peers = await collectPeers(core as any, timeoutSec); + if (peers.length > 0) { + process.stdout.write(peers.map((peer) => `[peer]\t${peer.peerId}\t${peer.name}`).join("\n") + "\n"); + } + return true; + } + + if (options.command === "p2p-sync") { + if (options.commandArgs.length < 2) { + throw new Error("p2p-sync requires two arguments: "); + } + const peerToken = options.commandArgs[0].trim(); + if (!peerToken) { + throw new Error("p2p-sync requires a non-empty "); + } + const timeoutSec = parseTimeoutSeconds(options.commandArgs[1], "p2p-sync"); + console.error(`[Command] p2p-sync peer=${peerToken} timeout=${timeoutSec}s`); + const peer = await syncWithPeer(core as any, peerToken, timeoutSec); + console.error(`[Done] P2P sync completed with ${peer.name} (${peer.peerId})`); + return true; + } + + if (options.command === "p2p-host") { + console.error("[Command] p2p-host"); + await openP2PHost(core as any); + console.error("[Ready] P2P host is running. Press Ctrl+C to stop."); + await new Promise(() => {}); + return true; + } + if (options.command === "push") { if (options.commandArgs.length < 2) { throw new Error("push requires two arguments: "); diff --git a/src/apps/cli/commands/types.ts b/src/apps/cli/commands/types.ts index a4d7fe1..01ea118 100644 --- a/src/apps/cli/commands/types.ts +++ b/src/apps/cli/commands/types.ts @@ -4,6 +4,9 @@ import { ServiceContext } from "@lib/services/base/ServiceBase"; export type CLICommand = | "daemon" | "sync" + | "p2p-peers" + | "p2p-sync" + | "p2p-host" | "push" | "pull" | "pull-rev" @@ -36,6 +39,9 @@ export interface CLICommandContext { export const VALID_COMMANDS = new Set([ "sync", + "p2p-peers", + "p2p-sync", + "p2p-host", "push", "pull", "pull-rev", diff --git a/src/apps/cli/entrypoint.ts b/src/apps/cli/entrypoint.ts index b8a1177..8da7104 100644 --- a/src/apps/cli/entrypoint.ts +++ b/src/apps/cli/entrypoint.ts @@ -1,6 +1,12 @@ #!/usr/bin/env node +import polyfill from "node-datachannel/polyfill"; import { main } from "./main"; +for (const prop in polyfill) { + // @ts-ignore Applying polyfill to globalThis + globalThis[prop] = (polyfill as any)[prop]; +} + main().catch((error) => { console.error(`[Fatal Error]`, error); process.exit(1); diff --git a/src/apps/cli/main.ts b/src/apps/cli/main.ts index 460c90e..264d14d 100644 --- a/src/apps/cli/main.ts +++ b/src/apps/cli/main.ts @@ -27,7 +27,14 @@ 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"; import type { InjectableSettingService } from "@/lib/src/services/implements/injectable/InjectableSettingService"; -import { LOG_LEVEL_DEBUG, setGlobalLogFunction, defaultLoggerEnv, LOG_LEVEL_INFO, LOG_LEVEL_URGENT, LOG_LEVEL_NOTICE } from "octagonal-wheels/common/logger"; +import { + LOG_LEVEL_DEBUG, + setGlobalLogFunction, + defaultLoggerEnv, + LOG_LEVEL_INFO, + LOG_LEVEL_URGENT, + LOG_LEVEL_NOTICE, +} from "octagonal-wheels/common/logger"; import { runCommand } from "./commands/runCommand"; import { VALID_COMMANDS } from "./commands/types"; import type { CLICommand, CLIOptions } from "./commands/types"; @@ -36,6 +43,7 @@ import { stripAllPrefixes } from "@lib/string_and_binary/path"; const SETTINGS_FILE = ".livesync/settings.json"; defaultLoggerEnv.minLogLevel = LOG_LEVEL_DEBUG; + // DI the log again. // const recentLogEntries = reactiveSource([]); // const globalLogFunction = (message: any, level?: number, key?: string) => { @@ -65,6 +73,10 @@ Arguments: Commands: sync Run one replication cycle and exit + p2p-peers Show discovered peers as [peer] + p2p-sync + Sync with the specified peer-id or peer-name + p2p-host Start P2P host mode and wait until interrupted push Push local file into local database path pull Pull file from local database into local file pull-rev Pull file at specific revision into local file @@ -78,6 +90,9 @@ Commands: resolve Resolve conflicts by keeping and deleting others 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 @@ -213,21 +228,22 @@ export async function main() { options.command === "cat" || options.command === "cat-rev" || options.command === "ls" || + options.command === "p2p-peers" || options.command === "info" || options.command === "rm" || options.command === "resolve"; const infoLog = avoidStdoutNoise ? console.error : console.log; - if(options.debug){ + if (options.debug) { setGlobalLogFunction((msg, level) => { console.error(`[${level}] ${typeof msg === "string" ? msg : JSON.stringify(msg)}`); if (msg instanceof Error) { console.error(msg); } }); - }else{ + } else { setGlobalLogFunction((msg, level) => { // NO OP, leave it to logFunction - }) + }); } if (options.command === "init-settings") { await createDefaultSettingsFile(options); @@ -421,4 +437,6 @@ export async function main() { console.error(`[Error] Failed to start:`, error); process.exit(1); } + // To prevent unexpected hanging in webRTC connections. + process.exit(0); } diff --git a/src/apps/cli/main.unit.spec.ts b/src/apps/cli/main.unit.spec.ts index 032a2b4..8206f03 100644 --- a/src/apps/cli/main.unit.spec.ts +++ b/src/apps/cli/main.unit.spec.ts @@ -58,4 +58,31 @@ describe("CLI parseArgs", () => { expect(combined).toContain("Usage:"); expect(combined).toContain("livesync-cli [database-path]"); }); + + it("parses p2p-peers command and timeout", () => { + process.argv = ["node", "livesync-cli", "./vault", "p2p-peers", "5"]; + const parsed = parseArgs(); + + expect(parsed.databasePath).toBe("./vault"); + expect(parsed.command).toBe("p2p-peers"); + expect(parsed.commandArgs).toEqual(["5"]); + }); + + it("parses p2p-sync command with peer and timeout", () => { + process.argv = ["node", "livesync-cli", "./vault", "p2p-sync", "peer-1", "12"]; + const parsed = parseArgs(); + + expect(parsed.databasePath).toBe("./vault"); + expect(parsed.command).toBe("p2p-sync"); + expect(parsed.commandArgs).toEqual(["peer-1", "12"]); + }); + + it("parses p2p-host command", () => { + process.argv = ["node", "livesync-cli", "./vault", "p2p-host"]; + const parsed = parseArgs(); + + expect(parsed.databasePath).toBe("./vault"); + expect(parsed.command).toBe("p2p-host"); + expect(parsed.commandArgs).toEqual([]); + }); }); diff --git a/src/apps/cli/package.json b/src/apps/cli/package.json index af2b296..822c931 100644 --- a/src/apps/cli/package.json +++ b/src/apps/cli/package.json @@ -11,16 +11,20 @@ "cli": "node dist/index.cjs", "buildRun": "npm run build && npm run cli --", "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json", - "test:unit": "cd ../../.. && npx vitest run --config vitest.config.unit.ts src/apps/cli/main.unit.spec.ts src/apps/cli/commands/utils.unit.spec.ts src/apps/cli/commands/runCommand.unit.spec.ts", + "test:unit": "cd ../../.. && npx vitest run --config vitest.config.unit.ts src/apps/cli/main.unit.spec.ts src/apps/cli/commands/utils.unit.spec.ts src/apps/cli/commands/runCommand.unit.spec.ts src/apps/cli/commands/p2p.unit.spec.ts", "test:e2e:two-vaults": "bash test/test-e2e-two-vaults-with-docker-linux.sh", "test:e2e:two-vaults:common": "bash test/test-e2e-two-vaults-common.sh", "test:e2e:two-vaults:matrix": "bash test/test-e2e-two-vaults-matrix.sh", "test:e2e:push-pull": "bash test/test-push-pull-linux.sh", "test:e2e:setup-put-cat": "bash test/test-setup-put-cat-linux.sh", "test:e2e:sync-two-local": "bash test/test-sync-two-local-databases-linux.sh", + "test:e2e:p2p": "bash test/test-p2p-three-nodes-conflict-linux.sh", + "test:e2e:p2p-host": "bash test/test-p2p-host-linux.sh", + "test:e2e:p2p-sync": "bash test/test-p2p-sync-linux.sh", + "test:e2e:p2p-peers:local-relay": "bash test/test-p2p-peers-local-relay.sh", "test:e2e:mirror": "bash test/test-mirror-linux.sh", "pretest:e2e:all": "npm run build", - "test:e2e:all": " export RUN_BUILD=0 && npm run test:e2e:setup-put-cat && npm run test:e2e:push-pull && npm run test:e2e:sync-two-local && npm run test:e2e:mirror && npm run test:e2e:two-vaults" + "test:e2e:all": " export RUN_BUILD=0 && npm run test:e2e:setup-put-cat && npm run test:e2e:push-pull && npm run test:e2e:sync-two-local && npm run test:e2e:p2p && npm run test:e2e:mirror && npm run test:e2e:two-vaults && npm run test:e2e:p2p-host && npm run test:e2e:p2p" }, "dependencies": {}, "devDependencies": {} diff --git a/src/apps/cli/services/NodeKeyValueDBService.ts b/src/apps/cli/services/NodeKeyValueDBService.ts index 5799bf6..10b1626 100644 --- a/src/apps/cli/services/NodeKeyValueDBService.ts +++ b/src/apps/cli/services/NodeKeyValueDBService.ts @@ -101,9 +101,7 @@ class NodeFileKeyValueDatabase implements KeyValueDatabase { private load() { try { const loaded = JSON.parse(nodeFs.readFileSync(this.filePath, "utf-8")) as Record; - this.data = new Map( - Object.entries(loaded).map(([key, value]) => [key, deserializeFromNodeKV(value)]) - ); + this.data = new Map(Object.entries(loaded).map(([key, value]) => [key, deserializeFromNodeKV(value)])); } catch { this.data = new Map(); } diff --git a/src/apps/cli/test/test-helpers.sh b/src/apps/cli/test/test-helpers.sh index a508f95..0e0e236 100644 --- a/src/apps/cli/test/test-helpers.sh +++ b/src/apps/cli/test/test-helpers.sh @@ -211,6 +211,57 @@ fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8"); NODE } +cli_test_apply_p2p_settings() { + local settings_file="$1" + local room_id="$2" + local passphrase="$3" + local app_id="${4:-self-hosted-livesync-cli-tests}" + local relays="${5:-ws://localhost:4000/}" + local auto_accept="${6:-~.*}" + SETTINGS_FILE="$settings_file" \ + P2P_ROOM_ID="$room_id" \ + P2P_PASSPHRASE="$passphrase" \ + P2P_APP_ID="$app_id" \ + P2P_RELAYS="$relays" \ + P2P_AUTO_ACCEPT="$auto_accept" \ + node <<'NODE' +const fs = require("node:fs"); +const settingsPath = process.env.SETTINGS_FILE; +const data = JSON.parse(fs.readFileSync(settingsPath, "utf-8")); + +data.P2P_Enabled = true; +data.P2P_AutoStart = false; +data.P2P_AutoBroadcast = false; +data.P2P_AppID = process.env.P2P_APP_ID; +data.P2P_roomID = process.env.P2P_ROOM_ID; +data.P2P_passphrase = process.env.P2P_PASSPHRASE; +data.P2P_relays = process.env.P2P_RELAYS; +data.P2P_AutoAcceptingPeers = process.env.P2P_AUTO_ACCEPT; +data.P2P_AutoDenyingPeers = ""; +data.P2P_IsHeadless = true; +data.isConfigured = true; + +fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8"); +NODE +} + +cli_test_is_local_p2p_relay() { + local relay_url="$1" + [[ "$relay_url" == "ws://localhost:4000" || "$relay_url" == "ws://localhost:4000/" ]] +} + +cli_test_stop_p2p_relay() { + bash "$CLI_DIR/util/p2p-stop.sh" >/dev/null 2>&1 || true +} + +cli_test_start_p2p_relay() { + echo "[INFO] stopping leftover P2P relay container if present" + cli_test_stop_p2p_relay + + echo "[INFO] starting local P2P relay container" + bash "$CLI_DIR/util/p2p-start.sh" +} + cli_test_stop_couchdb() { bash "$CLI_DIR/util/couchdb-stop.sh" >/dev/null 2>&1 || true } diff --git a/src/apps/cli/test/test-p2p-host-linux.sh b/src/apps/cli/test/test-p2p-host-linux.sh new file mode 100644 index 0000000..cb31d85 --- /dev/null +++ b/src/apps/cli/test/test-p2p-host-linux.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +set -euo pipefail +# This test should be run with P2P client, please refer to the test-p2p-three-nodes-conflict-linux.sh test for more details. + +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}" +VERBOSE_TEST_LOGGING="${VERBOSE_TEST_LOGGING:-0}" +KEEP_TEST_DATA="${KEEP_TEST_DATA:-0}" + +RELAY="${RELAY:-ws://localhost:4000/}" +USE_INTERNAL_RELAY="${USE_INTERNAL_RELAY:-1}" +ROOM_ID="${ROOM_ID:-1}" +PASSPHRASE="${PASSPHRASE:-test}" +APP_ID="${APP_ID:-self-hosted-livesync-cli-tests}" + +cli_test_init_cli_cmd + +if [[ "$RUN_BUILD" == "1" ]]; then + echo "[INFO] building CLI" + npm run build +fi + +WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-p2p-host.XXXXXX")" +VAULT="$WORK_DIR/vault-host" +SETTINGS="$WORK_DIR/settings-host.json" +mkdir -p "$VAULT" + +cleanup() { + local exit_code=$? + if [[ "${P2P_RELAY_STARTED:-0}" == "1" ]]; then + cli_test_stop_p2p_relay + fi + + if [[ "$KEEP_TEST_DATA" != "1" ]]; then + rm -rf "$WORK_DIR" + else + echo "[INFO] KEEP_TEST_DATA=1, preserving artefacts at $WORK_DIR" + fi + exit "$exit_code" +} +trap cleanup EXIT + +if [[ "$USE_INTERNAL_RELAY" == "1" ]]; then + if cli_test_is_local_p2p_relay "$RELAY"; then + cli_test_start_p2p_relay + P2P_RELAY_STARTED=1 + else + echo "[INFO] USE_INTERNAL_RELAY=1 but RELAY is not local ($RELAY), skipping local relay startup" + fi +fi + +echo "[INFO] preparing settings" +echo "[INFO] relay=$RELAY room=$ROOM_ID app=$APP_ID" +cli_test_init_settings_file "$SETTINGS" +cli_test_apply_p2p_settings "$SETTINGS" "$ROOM_ID" "$PASSPHRASE" "$APP_ID" "$RELAY" + +echo "[CASE] start p2p-host" +echo "[INFO] press Ctrl+C to stop" +run_cli "$VAULT" --settings "$SETTINGS" p2p-host diff --git a/src/apps/cli/test/test-p2p-peers-local-relay.sh b/src/apps/cli/test/test-p2p-peers-local-relay.sh new file mode 100644 index 0000000..214a2ad --- /dev/null +++ b/src/apps/cli/test/test-p2p-peers-local-relay.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +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" + +RUN_BUILD="${RUN_BUILD:-0}" +KEEP_TEST_DATA="${KEEP_TEST_DATA:-0}" +RELAY="${RELAY:-ws://localhost:7777}" +ROOM_ID="${ROOM_ID:-1}" +PASSPHRASE="${PASSPHRASE:-test}" +TIMEOUT_SECONDS="${TIMEOUT_SECONDS:-8}" +DEBUG_FLAG="${DEBUG_FLAG:--d}" + +if [[ "$RUN_BUILD" == "1" ]]; then + echo "[INFO] building CLI" + npm run build +fi + +WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-p2p-peers-local-relay.XXXXXX")" +VAULT="$WORK_DIR/vault" +SETTINGS="$WORK_DIR/settings.json" +mkdir -p "$VAULT" + +cleanup() { + local exit_code=$? + if [[ "$KEEP_TEST_DATA" != "1" ]]; then + rm -rf "$WORK_DIR" + else + echo "[INFO] KEEP_TEST_DATA=1, preserving artefacts at $WORK_DIR" + fi + exit "$exit_code" +} +trap cleanup EXIT + +cli_test_init_cli_cmd + +echo "[INFO] creating settings at $SETTINGS" +run_cli init-settings --force "$SETTINGS" >/dev/null + +SETTINGS_FILE="$SETTINGS" \ +P2P_ROOM_ID="$ROOM_ID" \ +P2P_PASSPHRASE="$PASSPHRASE" \ +P2P_RELAYS="$RELAY" \ +node <<'NODE' +const fs = require("node:fs"); +const settingsPath = process.env.SETTINGS_FILE; +const data = JSON.parse(fs.readFileSync(settingsPath, "utf-8")); + +data.P2P_Enabled = true; +data.P2P_AutoStart = false; +data.P2P_AutoBroadcast = false; +data.P2P_roomID = process.env.P2P_ROOM_ID; +data.P2P_passphrase = process.env.P2P_PASSPHRASE; +data.P2P_relays = process.env.P2P_RELAYS; +data.P2P_AutoAcceptingPeers = "~.*"; +data.P2P_AutoDenyingPeers = ""; +data.P2P_IsHeadless = true; +data.isConfigured = true; + +fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8"); +NODE + +echo "[INFO] relay=$RELAY room=$ROOM_ID timeout=${TIMEOUT_SECONDS}s" +echo "[INFO] running p2p-peers" + +set +e +OUTPUT="$(run_cli "$DEBUG_FLAG" "$VAULT" --settings "$SETTINGS" p2p-peers "$TIMEOUT_SECONDS" 2>&1)" +EXIT_CODE=$? +set -e + +echo "$OUTPUT" + +if [[ "$EXIT_CODE" -ne 0 ]]; then + echo "[FAIL] p2p-peers exited with code $EXIT_CODE" >&2 + exit "$EXIT_CODE" +fi + +if [[ -z "$OUTPUT" ]]; then + echo "[WARN] command completed but output was empty" +fi + +echo "[PASS] p2p-peers finished" diff --git a/src/apps/cli/test/test-p2p-sync-linux.sh b/src/apps/cli/test/test-p2p-sync-linux.sh new file mode 100644 index 0000000..3960de1 --- /dev/null +++ b/src/apps/cli/test/test-p2p-sync-linux.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +# This test should be run with P2P client, please refer to the test-p2p-three-nodes-conflict-linux.sh test for more details. + +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}" +VERBOSE_TEST_LOGGING="${VERBOSE_TEST_LOGGING:-0}" +KEEP_TEST_DATA="${KEEP_TEST_DATA:-0}" + +RELAY="${RELAY:-ws://localhost:4000/}" +USE_INTERNAL_RELAY="${USE_INTERNAL_RELAY:-1}" +ROOM_ID="${ROOM_ID:-1}" +PASSPHRASE="${PASSPHRASE:-test}" +APP_ID="${APP_ID:-self-hosted-livesync-cli-tests}" +PEERS_TIMEOUT="${PEERS_TIMEOUT:-12}" +SYNC_TIMEOUT="${SYNC_TIMEOUT:-15}" +TARGET_PEER="${TARGET_PEER:-}" + +cli_test_init_cli_cmd + +if [[ "$RUN_BUILD" == "1" ]]; then + echo "[INFO] building CLI" + npm run build +fi + +WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-p2p-sync.XXXXXX")" +VAULT="$WORK_DIR/vault-sync" +SETTINGS="$WORK_DIR/settings-sync.json" +mkdir -p "$VAULT" + +cleanup() { + local exit_code=$? + if [[ "${P2P_RELAY_STARTED:-0}" == "1" ]]; then + cli_test_stop_p2p_relay + fi + + if [[ "$KEEP_TEST_DATA" != "1" ]]; then + rm -rf "$WORK_DIR" + else + echo "[INFO] KEEP_TEST_DATA=1, preserving artefacts at $WORK_DIR" + fi + exit "$exit_code" +} +trap cleanup EXIT + +if [[ "$USE_INTERNAL_RELAY" == "1" ]]; then + if cli_test_is_local_p2p_relay "$RELAY"; then + cli_test_start_p2p_relay + P2P_RELAY_STARTED=1 + else + echo "[INFO] USE_INTERNAL_RELAY=1 but RELAY is not local ($RELAY), skipping local relay startup" + fi +fi + +echo "[INFO] preparing settings" +echo "[INFO] relay=$RELAY room=$ROOM_ID app=$APP_ID" +cli_test_init_settings_file "$SETTINGS" +cli_test_apply_p2p_settings "$SETTINGS" "$ROOM_ID" "$PASSPHRASE" "$APP_ID" "$RELAY" + +echo "[CASE] discover peers" +PEER_LINES="$(run_cli "$VAULT" --settings "$SETTINGS" p2p-peers "$PEERS_TIMEOUT")" +if [[ -z "$PEER_LINES" ]]; then + echo "[FAIL] p2p-peers returned empty output" >&2 + exit 1 +fi + +if ! awk -F $'\t' 'NF>=3 && $1=="[peer]" { found=1 } END { exit(found ? 0 : 1) }' <<< "$PEER_LINES"; then + echo "[FAIL] p2p-peers output must include [peer]" >&2 + echo "$PEER_LINES" >&2 + exit 1 +fi + +SELECTED_PEER_ID="" +SELECTED_PEER_NAME="" + +if [[ -n "$TARGET_PEER" ]]; then + while IFS=$'\t' read -r marker peer_id peer_name _; do + if [[ "$marker" != "[peer]" ]]; then + continue + fi + if [[ "$peer_id" == "$TARGET_PEER" || "$peer_name" == "$TARGET_PEER" ]]; then + SELECTED_PEER_ID="$peer_id" + SELECTED_PEER_NAME="$peer_name" + break + fi + done <<< "$PEER_LINES" + + if [[ -z "$SELECTED_PEER_ID" ]]; then + echo "[FAIL] TARGET_PEER=$TARGET_PEER was not found" >&2 + echo "$PEER_LINES" >&2 + exit 1 + fi +else + SELECTED_PEER_ID="$(awk -F $'\t' 'NF>=3 && $1=="[peer]" {print $2; exit}' <<< "$PEER_LINES")" + SELECTED_PEER_NAME="$(awk -F $'\t' 'NF>=3 && $1=="[peer]" {print $3; exit}' <<< "$PEER_LINES")" +fi + +if [[ -z "$SELECTED_PEER_ID" ]]; then + echo "[FAIL] could not extract peer-id from p2p-peers output" >&2 + echo "$PEER_LINES" >&2 + exit 1 +fi + +echo "[PASS] selected peer: ${SELECTED_PEER_ID} (${SELECTED_PEER_NAME:-unknown})" + +echo "[CASE] run p2p-sync" +run_cli "$VAULT" --settings "$SETTINGS" p2p-sync "$SELECTED_PEER_ID" "$SYNC_TIMEOUT" >/dev/null + +echo "[PASS] p2p-sync completed" diff --git a/src/apps/cli/test/test-p2p-three-nodes-conflict-linux.sh b/src/apps/cli/test/test-p2p-three-nodes-conflict-linux.sh new file mode 100644 index 0000000..a4ce4af --- /dev/null +++ b/src/apps/cli/test/test-p2p-three-nodes-conflict-linux.sh @@ -0,0 +1,242 @@ +#!/usr/bin/env bash +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}" +KEEP_TEST_DATA="${KEEP_TEST_DATA:-0}" +VERBOSE_TEST_LOGGING="${VERBOSE_TEST_LOGGING:-0}" + +RELAY="${RELAY:-ws://localhost:4000/}" +USE_INTERNAL_RELAY="${USE_INTERNAL_RELAY:-1}" +ROOM_ID_PREFIX="${ROOM_ID_PREFIX:-p2p-room}" +PASSPHRASE_PREFIX="${PASSPHRASE_PREFIX:-p2p-pass}" +APP_ID="${APP_ID:-self-hosted-livesync-cli-tests}" +PEERS_TIMEOUT="${PEERS_TIMEOUT:-10}" +SYNC_TIMEOUT="${SYNC_TIMEOUT:-15}" + +ROOM_ID="${ROOM_ID_PREFIX}-$(date +%s)-$RANDOM-$RANDOM" +PASSPHRASE="${PASSPHRASE_PREFIX}-$(date +%s)-$RANDOM-$RANDOM" + +cli_test_init_cli_cmd + +if [[ "$RUN_BUILD" == "1" ]]; then + echo "[INFO] building CLI" + npm run build +fi + +WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-p2p-3nodes.XXXXXX")" +VAULT_A="$WORK_DIR/vault-a" +VAULT_B="$WORK_DIR/vault-b" +VAULT_C="$WORK_DIR/vault-c" +SETTINGS_A="$WORK_DIR/settings-a.json" +SETTINGS_B="$WORK_DIR/settings-b.json" +SETTINGS_C="$WORK_DIR/settings-c.json" +HOST_LOG="$WORK_DIR/p2p-host.log" + +mkdir -p "$VAULT_A" "$VAULT_B" "$VAULT_C" + +cleanup() { + local exit_code=$? + if [[ -n "${HOST_PID:-}" ]] && kill -0 "$HOST_PID" >/dev/null 2>&1; then + kill -TERM "$HOST_PID" >/dev/null 2>&1 || true + wait "$HOST_PID" >/dev/null 2>&1 || true + fi + + if [[ "${P2P_RELAY_STARTED:-0}" == "1" ]]; then + cli_test_stop_p2p_relay + fi + + if [[ "$KEEP_TEST_DATA" != "1" ]]; then + rm -rf "$WORK_DIR" + else + echo "[INFO] KEEP_TEST_DATA=1, preserving artefacts at $WORK_DIR" + fi + + exit "$exit_code" +} +trap cleanup EXIT + +if [[ "$USE_INTERNAL_RELAY" == "1" ]]; then + if cli_test_is_local_p2p_relay "$RELAY"; then + cli_test_start_p2p_relay + P2P_RELAY_STARTED=1 + else + echo "[INFO] USE_INTERNAL_RELAY=1 but RELAY is not local ($RELAY), skipping local relay startup" + fi +fi + +run_cli_a() { + run_cli "$VAULT_A" --settings "$SETTINGS_A" "$@" +} + +run_cli_b() { + run_cli "$VAULT_B" --settings "$SETTINGS_B" "$@" +} + +run_cli_c() { + run_cli "$VAULT_C" --settings "$SETTINGS_C" "$@" +} + +echo "[INFO] preparing settings" +echo "[INFO] relay=$RELAY room=$ROOM_ID app=$APP_ID" +cli_test_init_settings_file "$SETTINGS_A" +cli_test_init_settings_file "$SETTINGS_B" +cli_test_init_settings_file "$SETTINGS_C" +cli_test_apply_p2p_settings "$SETTINGS_A" "$ROOM_ID" "$PASSPHRASE" "$APP_ID" "$RELAY" +cli_test_apply_p2p_settings "$SETTINGS_B" "$ROOM_ID" "$PASSPHRASE" "$APP_ID" "$RELAY" +cli_test_apply_p2p_settings "$SETTINGS_C" "$ROOM_ID" "$PASSPHRASE" "$APP_ID" "$RELAY" + +echo "[CASE] start p2p-host on A" +run_cli_a p2p-host >"$HOST_LOG" 2>&1 & +HOST_PID=$! + +for _ in 1 2 3 4 5 6 7 8 9 10; do + echo "[INFO] waiting for p2p-host to start..." + if grep -Fq "P2P host is running" "$HOST_LOG"; then + break + fi + sleep 1 +done +if ! grep -Fq "P2P host is running" "$HOST_LOG"; then + echo "[FAIL] p2p-host did not become ready" >&2 + cat "$HOST_LOG" >&2 + exit 1 +fi +echo "[PASS] p2p-host started" + +echo "[CASE] discover host peer from B" +PEERS_FROM_B="$(run_cli_b p2p-peers "$PEERS_TIMEOUT")" +HOST_PEER_ID="$(awk -F $'\t' 'NF>=3 && $1=="[peer]" {print $2; exit}' <<< "$PEERS_FROM_B")" +if [[ -z "$HOST_PEER_ID" ]]; then + echo "[FAIL] B could not find host peer" >&2 + echo "$PEERS_FROM_B" >&2 + exit 1 +fi +echo "[PASS] B discovered host peer: $HOST_PEER_ID" + +echo "[CASE] discover host peer from C" +PEERS_FROM_C="$(run_cli_c p2p-peers "$PEERS_TIMEOUT")" +HOST_PEER_ID_FROM_C="$(awk -F $'\t' 'NF>=3 && $1=="[peer]" {print $2; exit}' <<< "$PEERS_FROM_C")" +if [[ -z "$HOST_PEER_ID_FROM_C" ]]; then + echo "[FAIL] C could not find host peer" >&2 + echo "$PEERS_FROM_C" >&2 + exit 1 +fi +echo "[PASS] C discovered host peer: $HOST_PEER_ID_FROM_C" + +TARGET_PATH="p2p/conflicted-from-two-clients.txt" + +echo "[CASE] B creates file and syncs" +printf 'from-client-b-v1\n' | run_cli_b put "$TARGET_PATH" >/dev/null +run_cli_b p2p-sync "$HOST_PEER_ID" "$SYNC_TIMEOUT" >/dev/null + +echo "[CASE] C syncs and can see B file" +run_cli_c p2p-sync "$HOST_PEER_ID_FROM_C" "$SYNC_TIMEOUT" >/dev/null +VISIBLE_ON_C="" +for _ in 1 2 3 4 5; do + if VISIBLE_ON_C="$(run_cli_c cat "$TARGET_PATH" 2>/dev/null | cli_test_sanitise_cat_stdout)"; then + if [[ "$VISIBLE_ON_C" == "from-client-b-v1" ]]; then + break + fi + fi + run_cli_c p2p-sync "$HOST_PEER_ID_FROM_C" "$SYNC_TIMEOUT" >/dev/null + sleep 1 +done +cli_test_assert_equal "from-client-b-v1" "$VISIBLE_ON_C" "C should see file created by B" + +echo "[CASE] B and C modify file independently" +printf 'from-client-b-v2\n' | run_cli_b put "$TARGET_PATH" >/dev/null +printf 'from-client-c-v2\n' | run_cli_c put "$TARGET_PATH" >/dev/null + +echo "[CASE] B and C sync to host concurrently" +set +e +run_cli_b p2p-sync "$HOST_PEER_ID" "$SYNC_TIMEOUT" >/dev/null & +SYNC_B_PID=$! +run_cli_c p2p-sync "$HOST_PEER_ID_FROM_C" "$SYNC_TIMEOUT" >/dev/null & +SYNC_C_PID=$! +wait "$SYNC_B_PID" +SYNC_B_EXIT=$? +wait "$SYNC_C_PID" +SYNC_C_EXIT=$? +set -e +if [[ "$SYNC_B_EXIT" -ne 0 || "$SYNC_C_EXIT" -ne 0 ]]; then + echo "[FAIL] concurrent sync failed: B=$SYNC_B_EXIT C=$SYNC_C_EXIT" >&2 + exit 1 +fi + +echo "[CASE] sync back to clients" +run_cli_b p2p-sync "$HOST_PEER_ID" "$SYNC_TIMEOUT" >/dev/null +run_cli_c p2p-sync "$HOST_PEER_ID_FROM_C" "$SYNC_TIMEOUT" >/dev/null + +echo "[CASE] B info shows conflict" +INFO_JSON_B_BEFORE="$(run_cli_b info "$TARGET_PATH")" +CONFLICTS_B_BEFORE="$(printf '%s' "$INFO_JSON_B_BEFORE" | cli_test_json_string_field_from_stdin conflicts)" +KEEP_REV_B="$(printf '%s' "$INFO_JSON_B_BEFORE" | cli_test_json_string_field_from_stdin revision)" +if [[ "$CONFLICTS_B_BEFORE" == "N/A" || -z "$CONFLICTS_B_BEFORE" ]]; then + echo "[FAIL] expected conflicts on B after two-client sync" >&2 + echo "$INFO_JSON_B_BEFORE" >&2 + exit 1 +fi +if [[ -z "$KEEP_REV_B" ]]; then + echo "[FAIL] could not read current revision on B for resolve" >&2 + echo "$INFO_JSON_B_BEFORE" >&2 + exit 1 +fi +echo "[PASS] conflict detected on B" + +echo "[CASE] C info shows conflict" +INFO_JSON_C_BEFORE="$(run_cli_c info "$TARGET_PATH")" +CONFLICTS_C_BEFORE="$(printf '%s' "$INFO_JSON_C_BEFORE" | cli_test_json_string_field_from_stdin conflicts)" +KEEP_REV_C="$(printf '%s' "$INFO_JSON_C_BEFORE" | cli_test_json_string_field_from_stdin revision)" +if [[ "$CONFLICTS_C_BEFORE" == "N/A" || -z "$CONFLICTS_C_BEFORE" ]]; then + echo "[FAIL] expected conflicts on C after two-client sync" >&2 + echo "$INFO_JSON_C_BEFORE" >&2 + exit 1 +fi +if [[ -z "$KEEP_REV_C" ]]; then + echo "[FAIL] could not read current revision on C for resolve" >&2 + echo "$INFO_JSON_C_BEFORE" >&2 + exit 1 +fi +echo "[PASS] conflict detected on C" + +echo "[CASE] resolve conflict on B and C" +run_cli_b resolve "$TARGET_PATH" "$KEEP_REV_B" >/dev/null +run_cli_c resolve "$TARGET_PATH" "$KEEP_REV_C" >/dev/null + +INFO_JSON_B_AFTER="$(run_cli_b info "$TARGET_PATH")" +CONFLICTS_B_AFTER="$(printf '%s' "$INFO_JSON_B_AFTER" | cli_test_json_string_field_from_stdin conflicts)" +if [[ "$CONFLICTS_B_AFTER" != "N/A" ]]; then + echo "[FAIL] conflict still remains on B after resolve" >&2 + echo "$INFO_JSON_B_AFTER" >&2 + exit 1 +fi + +INFO_JSON_C_AFTER="$(run_cli_c info "$TARGET_PATH")" +CONFLICTS_C_AFTER="$(printf '%s' "$INFO_JSON_C_AFTER" | cli_test_json_string_field_from_stdin conflicts)" +if [[ "$CONFLICTS_C_AFTER" != "N/A" ]]; then + echo "[FAIL] conflict still remains on C after resolve" >&2 + echo "$INFO_JSON_C_AFTER" >&2 + exit 1 +fi + +FINAL_CONTENT_B="$(run_cli_b cat "$TARGET_PATH" | cli_test_sanitise_cat_stdout)" +FINAL_CONTENT_C="$(run_cli_c cat "$TARGET_PATH" | cli_test_sanitise_cat_stdout)" +if [[ "$FINAL_CONTENT_B" != "from-client-b-v2" && "$FINAL_CONTENT_B" != "from-client-c-v2" ]]; then + echo "[FAIL] unexpected final content on B after resolve" >&2 + echo "[FAIL] final content on B: $FINAL_CONTENT_B" >&2 + exit 1 +fi +if [[ "$FINAL_CONTENT_C" != "from-client-b-v2" && "$FINAL_CONTENT_C" != "from-client-c-v2" ]]; then + echo "[FAIL] unexpected final content on C after resolve" >&2 + echo "[FAIL] final content on C: $FINAL_CONTENT_C" >&2 + exit 1 +fi + +echo "[PASS] conflicts resolved on B and C" +echo "[PASS] all 3-node P2P conflict scenarios passed" diff --git a/src/apps/cli/util/p2p-init.sh b/src/apps/cli/util/p2p-init.sh new file mode 100755 index 0000000..dd865c9 --- /dev/null +++ b/src/apps/cli/util/p2p-init.sh @@ -0,0 +1,2 @@ +#!/bin/bash +echo "P2P Init - No additional initialization required." \ No newline at end of file diff --git a/src/apps/cli/util/p2p-start.sh b/src/apps/cli/util/p2p-start.sh new file mode 100755 index 0000000..90b3e3e --- /dev/null +++ b/src/apps/cli/util/p2p-start.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker run -d --name relay-test -p 4000:8080 scsibug/nostr-rs-relay:latest diff --git a/src/apps/cli/util/p2p-stop.sh b/src/apps/cli/util/p2p-stop.sh new file mode 100755 index 0000000..c83c8c9 --- /dev/null +++ b/src/apps/cli/util/p2p-stop.sh @@ -0,0 +1,3 @@ +#!/bin/bash +docker stop relay-test +docker rm relay-test \ No newline at end of file diff --git a/src/apps/cli/vite.config.ts b/src/apps/cli/vite.config.ts index d3bdea0..77d8591 100644 --- a/src/apps/cli/vite.config.ts +++ b/src/apps/cli/vite.config.ts @@ -5,7 +5,16 @@ import { readFileSync } from "node:fs"; const packageJson = JSON.parse(readFileSync("../../../package.json", "utf-8")); const manifestJson = JSON.parse(readFileSync("../../../manifest.json", "utf-8")); // https://vite.dev/config/ -const defaultExternal = ["obsidian", "electron", "crypto", "pouchdb-adapter-leveldb", "commander", "punycode"]; +const defaultExternal = [ + "obsidian", + "electron", + "crypto", + "pouchdb-adapter-leveldb", + "commander", + "punycode", + "node-datachannel", + "node-datachannel/polyfill", +]; export default defineConfig({ plugins: [svelte()], resolve: { @@ -43,6 +52,7 @@ export default defineConfig({ if (id === "fs" || id === "fs/promises" || id === "path" || id === "crypto" || id === "worker_threads") return true; if (id.startsWith("pouchdb-")) return true; + if (id.startsWith("node-datachannel")) return true; if (id.startsWith("node:")) return true; return false; }, diff --git a/src/lib b/src/lib index f77404c..90da1d4 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit f77404c926e06e1320984158680c19b1541f1655 +Subproject commit 90da1d4fb414f08d7ec12a8585bbc111566fbe06 diff --git a/updates.md b/updates.md index e218314..662c8a0 100644 --- a/updates.md +++ b/updates.md @@ -5,9 +5,11 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid ## -- unreleased -- -### New features +### CLI new features - `mirror` command has been added to the CLI. This command is intended to mirror the storage to the local database. +- `p2p-sync`, `p2p-peers`, and `p2p-host` commands have been added to the CLI. These commands are intended for P2P synchronisation. + - Yes, no more need for a [LiveSync PeerServer](https://github.com/vrtmrz/livesync-serverpeer) for virtual environments! The CLI can handle it by itself. ## 0.25.52-patched-1 From beced219c74446ce73d44c511999ead5d3aeec89 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 14 Mar 2026 16:13:14 +0900 Subject: [PATCH 083/339] Fix: exit code --- src/apps/cli/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/cli/main.ts b/src/apps/cli/main.ts index 264d14d..d153c88 100644 --- a/src/apps/cli/main.ts +++ b/src/apps/cli/main.ts @@ -438,5 +438,5 @@ export async function main() { process.exit(1); } // To prevent unexpected hanging in webRTC connections. - process.exit(0); + process.exit(process.exitCode ?? 0); } From 8cad4cdf80695a57743d1e90b5d10ce997d4b709 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 14 Mar 2026 16:50:43 +0900 Subject: [PATCH 084/339] Add workaround for my mac --- src/apps/cli/test/test-mirror-linux.sh | 31 ++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/apps/cli/test/test-mirror-linux.sh b/src/apps/cli/test/test-mirror-linux.sh index 3ccb56a..389cf00 100755 --- a/src/apps/cli/test/test-mirror-linux.sh +++ b/src/apps/cli/test/test-mirror-linux.sh @@ -47,6 +47,33 @@ FAIL=0 assert_pass() { echo "[PASS] $1"; PASS=$((PASS + 1)); } assert_fail() { echo "[FAIL] $1" >&2; FAIL=$((FAIL + 1)); } +# Return timestamp for touch -t in YYYYMMDDHHMM format. +# Accepts offsets such as "+1 hour" or "-1 hour". +portable_touch_timestamp() { + local offset="$1" + if command -v gdate >/dev/null 2>&1; then + gdate -d "$offset" +%Y%m%d%H%M + return + fi + if date -d "$offset" +%Y%m%d%H%M >/dev/null 2>&1; then + date -d "$offset" +%Y%m%d%H%M + return + fi + + case "$offset" in + "+1 hour") + date -v+1H +%Y%m%d%H%M + ;; + "-1 hour") + date -v-1H +%Y%m%d%H%M + ;; + *) + echo "[FAIL] Unsupported date offset on this platform: $offset" >&2 + exit 1 + ;; + esac +} + # ───────────────────────────────────────────────────────────────────────────── # Case 1: File exists only in storage → should be synced into DB after mirror # ───────────────────────────────────────────────────────────────────────────── @@ -123,7 +150,7 @@ printf 'old content\n' | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" put te # Write new content to storage with a timestamp 1 hour in the future printf 'new content\n' > "$VAULT_DIR/test/sync-storage-newer.md" -touch -t "$(date -d '+1 hour' +%Y%m%d%H%M)" "$VAULT_DIR/test/sync-storage-newer.md" +touch -t "$(portable_touch_timestamp '+1 hour')" "$VAULT_DIR/test/sync-storage-newer.md" run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror @@ -145,7 +172,7 @@ echo "=== Case 5: DB newer → storage updated ===" # Write old content to storage with a timestamp 1 hour in the past printf 'old storage content\n' > "$VAULT_DIR/test/sync-db-newer.md" -touch -t "$(date -d '-1 hour' +%Y%m%d%H%M)" "$VAULT_DIR/test/sync-db-newer.md" +touch -t "$(portable_touch_timestamp '-1 hour')" "$VAULT_DIR/test/sync-db-newer.md" # Write new content to DB only (mtime ≈ now, newer than the storage file) printf 'new db content\n' | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" put test/sync-db-newer.md From 9dd479e597d5584c3a92e936228577557e693971 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 14 Mar 2026 16:51:30 +0900 Subject: [PATCH 085/339] Fix for an issue where conflicts cannot be resolved in Journal Sync Remove unnecessary test calling in CLI --- src/apps/cli/package.json | 2 +- src/lib | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/cli/package.json b/src/apps/cli/package.json index 822c931..17669f3 100644 --- a/src/apps/cli/package.json +++ b/src/apps/cli/package.json @@ -24,7 +24,7 @@ "test:e2e:p2p-peers:local-relay": "bash test/test-p2p-peers-local-relay.sh", "test:e2e:mirror": "bash test/test-mirror-linux.sh", "pretest:e2e:all": "npm run build", - "test:e2e:all": " export RUN_BUILD=0 && npm run test:e2e:setup-put-cat && npm run test:e2e:push-pull && npm run test:e2e:sync-two-local && npm run test:e2e:p2p && npm run test:e2e:mirror && npm run test:e2e:two-vaults && npm run test:e2e:p2p-host && npm run test:e2e:p2p" + "test:e2e:all": " export RUN_BUILD=0 && npm run test:e2e:setup-put-cat && npm run test:e2e:push-pull && npm run test:e2e:sync-two-local && npm run test:e2e:p2p && npm run test:e2e:mirror && npm run test:e2e:two-vaults && npm run test:e2e:p2p" }, "dependencies": {}, "devDependencies": {} diff --git a/src/lib b/src/lib index 90da1d4..10aa321 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 90da1d4fb414f08d7ec12a8585bbc111566fbe06 +Subproject commit 10aa32108b4b6b2d78e7a45d8c4bd1150c947ac3 From 33338506cfe3a996228615a8a8174971c50e4e5f Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 14 Mar 2026 17:02:16 +0900 Subject: [PATCH 086/339] bump --- manifest.json | 2 +- package-lock.json | 1536 +++++++++++++++++++++++---------------------- package.json | 2 +- updates.md | 9 +- 4 files changed, 797 insertions(+), 752 deletions(-) diff --git a/manifest.json b/manifest.json index a0f197d..fb535d9 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.52-patched-1", + "version": "0.25.52-patched-2", "minAppVersion": "0.9.12", "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 2da4406..53266f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.52-patched-1", + "version": "0.25.52-patched-2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.52-patched-1", + "version": "0.25.52-patched-2", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", @@ -730,15 +730,15 @@ } }, "node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/util-endpoints": { - "version": "3.996.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.3.tgz", - "integrity": "sha512-yWIQSNiCjykLL+ezN5A+DfBb1gfXTytBxm57e64lYmwxDHNmInYHRJYYRAGWG1o77vKEiWaw4ui28e3yb1k5aQ==", + "version": "3.996.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz", + "integrity": "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.4", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.10", - "@smithy/util-endpoints": "^3.3.1", + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-endpoints": "^3.3.3", "tslib": "^2.6.2" }, "engines": { @@ -795,15 +795,15 @@ } }, "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": { - "version": "3.996.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.3.tgz", - "integrity": "sha512-yWIQSNiCjykLL+ezN5A+DfBb1gfXTytBxm57e64lYmwxDHNmInYHRJYYRAGWG1o77vKEiWaw4ui28e3yb1k5aQ==", + "version": "3.996.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz", + "integrity": "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.4", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.10", - "@smithy/util-endpoints": "^3.3.1", + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-endpoints": "^3.3.3", "tslib": "^2.6.2" }, "engines": { @@ -862,12 +862,12 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.973.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.4.tgz", - "integrity": "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q==", + "version": "3.973.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.6.tgz", + "integrity": "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -950,13 +950,13 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.8.tgz", - "integrity": "sha512-Ql8elcUdYCha83Ol7NznBsgN5GVZnv3vUd86fEc6waU6oUdY0T1O9NODkEEOS/Uaogr87avDrUC6DSeM4oXjZg==", + "version": "3.972.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.11.tgz", + "integrity": "sha512-iitV/gZKQMvY9d7ovmyFnFuTHbBAtrmLnvaSb/3X8vOKyevwtpmEtyc8AdhVWZe0pI/1GsHxlEvQeOePFzy7KQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", - "fast-xml-parser": "5.3.6", + "@smithy/types": "^4.13.1", + "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" }, "engines": { @@ -1547,9 +1547,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", "cpu": [ "arm64" ], @@ -1682,9 +1682,9 @@ } }, "node_modules/@eslint/compat/node_modules/@eslint/core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", - "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz", + "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -4335,9 +4335,9 @@ } }, "node_modules/@smithy/is-array-buffer": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.1.tgz", - "integrity": "sha512-Yfu664Qbf1B4IYIsYgKoABt010daZjkaCRvdU/sPnZG6TtHOB0md0RjNdLGzxe5UIdn9js4ftPICzmkRa9RJ4Q==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4456,14 +4456,14 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "4.3.10", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.10.tgz", - "integrity": "sha512-UALRbJtVX34AdP2VECKVlnNgidLHA2A7YgcJzwSBg1hzmnO/bZBHl/LDQQyYifzUwp1UOODnl9JJ3KNawpUJ9w==", + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.12.tgz", + "integrity": "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.10", - "@smithy/shared-ini-file-loader": "^4.4.5", - "@smithy/types": "^4.13.0", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -4487,12 +4487,12 @@ } }, "node_modules/@smithy/property-provider": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.10.tgz", - "integrity": "sha512-5jm60P0CU7tom0eNrZ7YrkgBaoLFXzmqB0wVS+4uK8PPGmosSrLNf6rRd50UBvukztawZ7zyA8TxlrKpF5z9jw==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.12.tgz", + "integrity": "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -4527,12 +4527,12 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.10.tgz", - "integrity": "sha512-4Mh18J26+ao1oX5wXJfWlTT+Q1OpDR8ssiC9PDOuEgVBGloqg18Fw7h5Ct8DyT9NBYwJgtJ2nLjKKFU6RP1G1Q==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.12.tgz", + "integrity": "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -4552,12 +4552,12 @@ } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.5.tgz", - "integrity": "sha512-pHgASxl50rrtOztgQCPmOXFjRW+mCd7ALr/3uXNzRrRoGV5G2+78GOsQ3HlQuBVHCh9o6xqMNvlIKZjWn4Euug==", + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.7.tgz", + "integrity": "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -4602,9 +4602,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.0.tgz", - "integrity": "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4614,13 +4614,13 @@ } }, "node_modules/@smithy/url-parser": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.10.tgz", - "integrity": "sha512-uypjF7fCDsRk26u3qHmFI/ePL7bxxB9vKkE+2WKEciHhz+4QtbzWiHRVNRJwU3cKhrYDYQE3b0MRFtqfLYdA4A==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.12.tgz", + "integrity": "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.2.10", - "@smithy/types": "^4.13.0", + "@smithy/querystring-parser": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -4666,12 +4666,12 @@ } }, "node_modules/@smithy/util-buffer-from": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.1.tgz", - "integrity": "sha512-/swhmt1qTiVkaejlmMPPDgZhEaWb/HWMGRBheaxwuVkusp/z+ErJyQxO6kaXumOciZSWlmq6Z5mNylCd33X7Ig==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.2.1", + "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -4724,13 +4724,13 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.1.tgz", - "integrity": "sha512-xyctc4klmjmieQiF9I1wssBWleRV0RhJ2DpO8+8yzi2LO1Z+4IWOZNGZGNj4+hq9kdo+nyfrRLmQTzc16Op2Vg==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.3.tgz", + "integrity": "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.10", - "@smithy/types": "^4.13.0", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -4808,12 +4808,12 @@ } }, "node_modules/@smithy/util-utf8": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.1.tgz", - "integrity": "sha512-DSIwNaWtmzrNQHv8g7DBGR9mulSit65KSj5ymGEIAknmIN8IpbZefEep10LaMG/P/xquwbmJ1h9ectz8z6mV6g==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.2.1", + "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -5115,9 +5115,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "24.10.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.15.tgz", - "integrity": "sha512-BgjLoRuSr0MTI5wA6gMw9Xy0sFudAaUuvrnjgGx9wZ522fYYLA5SYJ+1Y30vTcJEG+DRCyDHx/gzQVfofYzSdg==", + "version": "24.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", + "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -6134,15 +6134,15 @@ } }, "node_modules/@wdio/config": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.24.0.tgz", - "integrity": "sha512-rcHu0eG16rSEmHL0sEKDcr/vYFmGhQ5GOlmlx54r+1sgh6sf136q+kth4169s16XqviWGW3LjZbUfpTK29pGtw==", + "version": "9.25.0", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.25.0.tgz", + "integrity": "sha512-EWa7l1rrbSNthCRDpdBw7ESAa1/jAjSsWCGkaVAO0HMOGlQjzvYI6gNi4KUeymnurDZ2IPr0jr+f9We6AWi6QA==", "dev": true, "license": "MIT", "dependencies": { "@wdio/logger": "9.18.0", - "@wdio/types": "9.24.0", - "@wdio/utils": "9.24.0", + "@wdio/types": "9.25.0", + "@wdio/utils": "9.25.0", "deepmerge-ts": "^7.0.3", "glob": "^10.2.2", "import-meta-resolve": "^4.0.0", @@ -6277,9 +6277,9 @@ } }, "node_modules/@wdio/protocols": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.24.0.tgz", - "integrity": "sha512-ozQKYddBLT4TRvU9J+fGrhVUtx3iDAe+KNCJcTDMFMxNSdDMR2xFQdNp8HLHypspk58oXTYCvz6ZYjySthhqsw==", + "version": "9.25.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.25.0.tgz", + "integrity": "sha512-PErbZqdpFmE69bRuku3OR34Ro2xuZNNLXYFOcJnjXJVzf5+ApDyGHYrMlvhtrrSy9/55LUybk851ppjS+3RoDA==", "dev": true, "license": "MIT" }, @@ -6297,9 +6297,9 @@ } }, "node_modules/@wdio/repl/node_modules/@types/node": { - "version": "20.19.35", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz", - "integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==", + "version": "20.19.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", + "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", "dev": true, "license": "MIT", "dependencies": { @@ -6307,9 +6307,9 @@ } }, "node_modules/@wdio/types": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.24.0.tgz", - "integrity": "sha512-PYYunNl8Uq1r8YMJAK6ReRy/V/XIrCSyj5cpCtR5EqCL6heETOORFj7gt4uPnzidfgbtMBcCru0LgjjlMiH1UQ==", + "version": "9.25.0", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.25.0.tgz", + "integrity": "sha512-ovSEcUBLz6gVDIsBZYKQXz8EGU37jS8sqbmlOe5+jB4XbsTBCyTLjQK/rO7LWQAKJcs0vBq+Pd+VrlsFtA7tTQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6320,9 +6320,9 @@ } }, "node_modules/@wdio/types/node_modules/@types/node": { - "version": "20.19.35", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz", - "integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==", + "version": "20.19.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", + "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", "dev": true, "license": "MIT", "dependencies": { @@ -6330,15 +6330,15 @@ } }, "node_modules/@wdio/utils": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.24.0.tgz", - "integrity": "sha512-6WhtzC5SNCGRBTkaObX6A07Ofnnyyf+TQH/d/fuhZRqvBknrP4AMMZF+PFxGl1fwdySWdBn+gV2QLE+52Byowg==", + "version": "9.25.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.25.0.tgz", + "integrity": "sha512-w/ej8gZkc2tZr8L91ATyA1AWrbPDYDOvblQ7r+zt1uPRobuA4H98GME7Zm7i3FIP695BvV4G35Gcs5NssZW1pw==", "dev": true, "license": "MIT", "dependencies": { "@puppeteer/browsers": "^2.2.0", "@wdio/logger": "9.18.0", - "@wdio/types": "9.24.0", + "@wdio/types": "9.25.0", "decamelize": "^6.0.0", "deepmerge-ts": "^7.0.3", "edgedriver": "^6.1.2", @@ -6356,9 +6356,9 @@ } }, "node_modules/@zip.js/zip.js": { - "version": "2.8.21", - "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.21.tgz", - "integrity": "sha512-fkyzXISE3IMrstDO1AgPkJCx14MYHP/suIGiAovEYEuBjq3mffsuL6aMV7ohOSjW4rXtuACuUfpA3GtITgdtYg==", + "version": "2.8.23", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.23.tgz", + "integrity": "sha512-RB+RLnxPJFPrGvQ9rgO+4JOcsob6lD32OcF0QE0yg24oeW9q8KnTTNlugcDaIveEcCbclobJcZP+fLQ++sH0bw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -6852,12 +6852,11 @@ } }, "node_modules/bare-fs": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.4.tgz", - "integrity": "sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==", + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.5.tgz", + "integrity": "sha512-XvwYM6VZqKoqDll8BmSww5luA5eflDzY0uEFfBJtFKe4PAAtxBjU3YIxzIBzhyaEQBy1VXEQBto4cpN5RZJw+w==", "dev": true, "license": "Apache-2.0", - "optional": true, "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", @@ -6878,12 +6877,11 @@ } }, "node_modules/bare-os": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", - "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.0.tgz", + "integrity": "sha512-Dc9/SlwfxkXIGYhvMQNUtKaXCaGkZYGcd1vuNUUADVqzu4/vQfvnMkYYOUnt2VwQ2AqKr/8qAVFRtwETljgeFg==", "dev": true, "license": "Apache-2.0", - "optional": true, "engines": { "bare": ">=1.14.0" } @@ -6894,18 +6892,16 @@ "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", "dev": true, "license": "Apache-2.0", - "optional": true, "dependencies": { "bare-os": "^3.0.1" } }, "node_modules/bare-stream": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.8.0.tgz", - "integrity": "sha512-reUN0M2sHRqCdG4lUK3Fw8w98eeUIZHL5c3H7Mbhk2yVBL+oofgaIp0ieLfD5QXwPCypBpmEEKU2WZKzbAk8GA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.8.1.tgz", + "integrity": "sha512-bSeR8RfvbRwDpD7HWZvn8M3uYNDrk7m9DQjYOFkENZlXW8Ju/MPaqUPQq5LqJ3kyjEm07siTaAQ7wBKCU59oHg==", "dev": true, "license": "Apache-2.0", - "optional": true, "dependencies": { "streamx": "^2.21.0", "teex": "^1.0.1" @@ -6929,7 +6925,6 @@ "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", "dev": true, "license": "Apache-2.0", - "optional": true, "dependencies": { "bare-path": "^3.0.0" } @@ -6965,9 +6960,9 @@ } }, "node_modules/bl": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.4.tgz", - "integrity": "sha512-ZV/9asSuknOExbM/zPPA8z00lc1ihPKWaStHkkQrxHNeYx+yY+TmF+v80dpv2G0mv3HVXBu7ryoAsxbFFhf4eg==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.6.tgz", + "integrity": "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==", "license": "MIT", "dependencies": { "@types/readable-stream": "^4.0.0", @@ -9030,10 +9025,10 @@ "node": ">=18.2.0" } }, - "node_modules/fast-xml-parser": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.6.tgz", - "integrity": "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==", + "node_modules/fast-xml-builder": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.3.tgz", + "integrity": "sha512-1o60KoFw2+LWKQu3IdcfcFlGTW4dpqEWmjhYec6H82AYZU2TVBXep6tMl8Z1Y+wM+ZrzCwe3BZ9Vyd9N2rIvmg==", "funding": [ { "type": "github", @@ -9042,6 +9037,22 @@ ], "license": "MIT", "dependencies": { + "path-expression-matcher": "^1.1.3" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", + "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { @@ -9204,9 +9215,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", + "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", "dev": true, "license": "ISC" }, @@ -12465,6 +12476,21 @@ "node": ">=8" } }, + "node_modules/path-expression-matcher": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz", + "integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -12509,9 +12535,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -13893,9 +13919,9 @@ } }, "node_modules/safe-regex2": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz", - "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.0.tgz", + "integrity": "sha512-pNHAuBW7TrcleFHsxBr5QMi/Iyp0ENjUKz7GCcX1UO7cMh+NmVK6HxQckNL1tJp1XAJVjG6B8OKIPqodqj9rtw==", "dev": true, "funding": [ { @@ -13910,6 +13936,9 @@ "license": "MIT", "dependencies": { "ret": "~0.5.0" + }, + "bin": { + "safe-regex2": "bin/safe-regex2.js" } }, "node_modules/safer-buffer": { @@ -14495,9 +14524,9 @@ } }, "node_modules/strnum": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", - "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz", + "integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==", "funding": [ { "type": "github", @@ -14741,9 +14770,9 @@ } }, "node_modules/tar-fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", - "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz", + "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", "dev": true, "license": "MIT", "dependencies": { @@ -14756,13 +14785,14 @@ } }, "node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", + "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==", "dev": true, "license": "MIT", "dependencies": { "b4a": "^1.6.4", + "bare-fs": "^4.5.5", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } @@ -14773,7 +14803,6 @@ "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "streamx": "^2.12.5" } @@ -15040,9 +15069,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", "cpu": [ "ppc64" ], @@ -15057,9 +15086,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", "cpu": [ "arm" ], @@ -15074,9 +15103,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", "cpu": [ "arm64" ], @@ -15091,9 +15120,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", "cpu": [ "x64" ], @@ -15108,9 +15137,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", "cpu": [ "arm64" ], @@ -15125,9 +15154,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", "cpu": [ "x64" ], @@ -15142,9 +15171,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", "cpu": [ "arm64" ], @@ -15159,9 +15188,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", "cpu": [ "x64" ], @@ -15176,9 +15205,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", "cpu": [ "arm" ], @@ -15193,9 +15222,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", "cpu": [ "arm64" ], @@ -15210,9 +15239,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", "cpu": [ "ia32" ], @@ -15227,9 +15256,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", "cpu": [ "loong64" ], @@ -15244,9 +15273,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", "cpu": [ "mips64el" ], @@ -15261,9 +15290,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", "cpu": [ "ppc64" ], @@ -15278,9 +15307,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", "cpu": [ "riscv64" ], @@ -15295,9 +15324,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", "cpu": [ "s390x" ], @@ -15312,9 +15341,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", "cpu": [ "x64" ], @@ -15329,9 +15358,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", "cpu": [ "arm64" ], @@ -15346,9 +15375,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", "cpu": [ "x64" ], @@ -15363,9 +15392,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", "cpu": [ "arm64" ], @@ -15380,9 +15409,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", "cpu": [ "x64" ], @@ -15397,9 +15426,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", "cpu": [ "x64" ], @@ -15414,9 +15443,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", "cpu": [ "arm64" ], @@ -15431,9 +15460,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", "cpu": [ "ia32" ], @@ -15448,9 +15477,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", "cpu": [ "x64" ], @@ -15465,9 +15494,9 @@ } }, "node_modules/tsx/node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -15478,32 +15507,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" } }, "node_modules/tunnel-agent": { @@ -15699,9 +15728,9 @@ } }, "node_modules/undici": { - "version": "7.22.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", - "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.2.tgz", + "integrity": "sha512-P9J1HWYV/ajFr8uCqk5QixwiRKmB1wOamgS0e+o2Z4A44Ej2+thFVRLG/eA7qprx88XXhnV5Bl8LHXTURpzB3Q==", "dev": true, "license": "MIT", "engines": { @@ -15850,9 +15879,9 @@ } }, "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", "cpu": [ "ppc64" ], @@ -15867,9 +15896,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", "cpu": [ "arm" ], @@ -15884,9 +15913,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", "cpu": [ "arm64" ], @@ -15901,9 +15930,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", "cpu": [ "x64" ], @@ -15918,9 +15947,9 @@ } }, "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", "cpu": [ "arm64" ], @@ -15935,9 +15964,9 @@ } }, "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", "cpu": [ "x64" ], @@ -15952,9 +15981,9 @@ } }, "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", "cpu": [ "arm64" ], @@ -15969,9 +15998,9 @@ } }, "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", "cpu": [ "x64" ], @@ -15986,9 +16015,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", "cpu": [ "arm" ], @@ -16003,9 +16032,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", "cpu": [ "arm64" ], @@ -16020,9 +16049,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", "cpu": [ "ia32" ], @@ -16037,9 +16066,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", "cpu": [ "loong64" ], @@ -16054,9 +16083,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", "cpu": [ "mips64el" ], @@ -16071,9 +16100,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", "cpu": [ "ppc64" ], @@ -16088,9 +16117,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", "cpu": [ "riscv64" ], @@ -16105,9 +16134,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", "cpu": [ "s390x" ], @@ -16122,9 +16151,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", "cpu": [ "x64" ], @@ -16139,9 +16168,9 @@ } }, "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", "cpu": [ "arm64" ], @@ -16156,9 +16185,9 @@ } }, "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", "cpu": [ "x64" ], @@ -16173,9 +16202,9 @@ } }, "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", "cpu": [ "arm64" ], @@ -16190,9 +16219,9 @@ } }, "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", "cpu": [ "x64" ], @@ -16207,9 +16236,9 @@ } }, "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", "cpu": [ "x64" ], @@ -16224,9 +16253,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", "cpu": [ "arm64" ], @@ -16241,9 +16270,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", "cpu": [ "ia32" ], @@ -16258,9 +16287,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", "cpu": [ "x64" ], @@ -16275,9 +16304,9 @@ } }, "node_modules/vite/node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -16288,32 +16317,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" } }, "node_modules/vite/node_modules/fdir": { @@ -16538,19 +16567,19 @@ "license": "Apache-2.0" }, "node_modules/webdriver": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.24.0.tgz", - "integrity": "sha512-2R31Ey83NzMsafkl4hdFq6GlIBvOODQMkueLjeRqYAITu3QCYiq9oqBdnWA6CdePuV4dbKlYsKRX0mwMiPclDA==", + "version": "9.25.0", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.25.0.tgz", + "integrity": "sha512-XnABKdrp83zX3xVltmX0OcFzn8zOzWGtZQxIUKY0+INB0g9Nnnfu7G75W0G+0y4nyb3zH8mavGzDBiXctdEd3Q==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "^20.1.0", "@types/ws": "^8.5.3", - "@wdio/config": "9.24.0", + "@wdio/config": "9.25.0", "@wdio/logger": "9.18.0", - "@wdio/protocols": "9.24.0", - "@wdio/types": "9.24.0", - "@wdio/utils": "9.24.0", + "@wdio/protocols": "9.25.0", + "@wdio/types": "9.25.0", + "@wdio/utils": "9.25.0", "deepmerge-ts": "^7.0.3", "https-proxy-agent": "^7.0.6", "undici": "^6.21.3", @@ -16561,9 +16590,9 @@ } }, "node_modules/webdriver/node_modules/@types/node": { - "version": "20.19.35", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz", - "integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==", + "version": "20.19.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", + "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", "dev": true, "license": "MIT", "dependencies": { @@ -16571,9 +16600,9 @@ } }, "node_modules/webdriver/node_modules/undici": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", - "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "version": "6.24.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.0.tgz", + "integrity": "sha512-lVLNosgqo5EkGqh5XUDhGfsMSoO8K0BAN0TyJLvwNRSl4xWGZlCVYsAIpa/OpA3TvmnM01GWcoKmc3ZWo5wKKA==", "dev": true, "license": "MIT", "engines": { @@ -16581,20 +16610,20 @@ } }, "node_modules/webdriverio": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.24.0.tgz", - "integrity": "sha512-LTJt6Z/iDM0ne/4ytd3BykoPv9CuJ+CAILOzlwFeMGn4Mj02i4Bk2Rg9o/jeJ89f52hnv4OPmNjD0e8nzWAy5g==", + "version": "9.25.0", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.25.0.tgz", + "integrity": "sha512-ualC/LtWGjL5rwGAbUUzURKqKoHJG2/qecEppcS9k4n1IX3MlbzGXuL/qpXiRbs/h4981HpRbZAKBxRYqwUe3g==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "^20.11.30", "@types/sinonjs__fake-timers": "^8.1.5", - "@wdio/config": "9.24.0", + "@wdio/config": "9.25.0", "@wdio/logger": "9.18.0", - "@wdio/protocols": "9.24.0", + "@wdio/protocols": "9.25.0", "@wdio/repl": "9.16.2", - "@wdio/types": "9.24.0", - "@wdio/utils": "9.24.0", + "@wdio/types": "9.25.0", + "@wdio/utils": "9.25.0", "archiver": "^7.0.1", "aria-query": "^5.3.0", "cheerio": "^1.0.0-rc.12", @@ -16611,7 +16640,7 @@ "rgb2hex": "0.2.5", "serialize-error": "^12.0.0", "urlpattern-polyfill": "^10.0.0", - "webdriver": "9.24.0" + "webdriver": "9.25.0" }, "engines": { "node": ">=18.20.0" @@ -16626,9 +16655,9 @@ } }, "node_modules/webdriverio/node_modules/@types/node": { - "version": "20.19.35", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz", - "integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==", + "version": "20.19.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", + "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", "dev": true, "license": "MIT", "dependencies": { @@ -17610,14 +17639,14 @@ }, "dependencies": { "@aws-sdk/util-endpoints": { - "version": "3.996.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.3.tgz", - "integrity": "sha512-yWIQSNiCjykLL+ezN5A+DfBb1gfXTytBxm57e64lYmwxDHNmInYHRJYYRAGWG1o77vKEiWaw4ui28e3yb1k5aQ==", + "version": "3.996.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz", + "integrity": "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==", "requires": { - "@aws-sdk/types": "^3.973.4", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.10", - "@smithy/util-endpoints": "^3.3.1", + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-endpoints": "^3.3.3", "tslib": "^2.6.2" } } @@ -17669,14 +17698,14 @@ }, "dependencies": { "@aws-sdk/util-endpoints": { - "version": "3.996.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.3.tgz", - "integrity": "sha512-yWIQSNiCjykLL+ezN5A+DfBb1gfXTytBxm57e64lYmwxDHNmInYHRJYYRAGWG1o77vKEiWaw4ui28e3yb1k5aQ==", + "version": "3.996.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz", + "integrity": "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==", "requires": { - "@aws-sdk/types": "^3.973.4", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.10", - "@smithy/util-endpoints": "^3.3.1", + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-endpoints": "^3.3.3", "tslib": "^2.6.2" } } @@ -17722,11 +17751,11 @@ } }, "@aws-sdk/types": { - "version": "3.973.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.4.tgz", - "integrity": "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q==", + "version": "3.973.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.6.tgz", + "integrity": "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw==", "requires": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, @@ -17782,12 +17811,12 @@ } }, "@aws-sdk/xml-builder": { - "version": "3.972.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.8.tgz", - "integrity": "sha512-Ql8elcUdYCha83Ol7NznBsgN5GVZnv3vUd86fEc6waU6oUdY0T1O9NODkEEOS/Uaogr87avDrUC6DSeM4oXjZg==", + "version": "3.972.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.11.tgz", + "integrity": "sha512-iitV/gZKQMvY9d7ovmyFnFuTHbBAtrmLnvaSb/3X8vOKyevwtpmEtyc8AdhVWZe0pI/1GsHxlEvQeOePFzy7KQ==", "requires": { - "@smithy/types": "^4.13.0", - "fast-xml-parser": "5.3.6", + "@smithy/types": "^4.13.1", + "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, @@ -18107,9 +18136,9 @@ "optional": true }, "@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", "dev": true, "optional": true }, @@ -18166,9 +18195,9 @@ }, "dependencies": { "@eslint/core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", - "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz", + "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==", "dev": true, "requires": { "@types/json-schema": "^7.0.15" @@ -19931,9 +19960,9 @@ } }, "@smithy/is-array-buffer": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.1.tgz", - "integrity": "sha512-Yfu664Qbf1B4IYIsYgKoABt010daZjkaCRvdU/sPnZG6TtHOB0md0RjNdLGzxe5UIdn9js4ftPICzmkRa9RJ4Q==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", "requires": { "tslib": "^2.6.2" } @@ -20020,13 +20049,13 @@ } }, "@smithy/node-config-provider": { - "version": "4.3.10", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.10.tgz", - "integrity": "sha512-UALRbJtVX34AdP2VECKVlnNgidLHA2A7YgcJzwSBg1hzmnO/bZBHl/LDQQyYifzUwp1UOODnl9JJ3KNawpUJ9w==", + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.12.tgz", + "integrity": "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw==", "requires": { - "@smithy/property-provider": "^4.2.10", - "@smithy/shared-ini-file-loader": "^4.4.5", - "@smithy/types": "^4.13.0", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, @@ -20043,11 +20072,11 @@ } }, "@smithy/property-provider": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.10.tgz", - "integrity": "sha512-5jm60P0CU7tom0eNrZ7YrkgBaoLFXzmqB0wVS+4uK8PPGmosSrLNf6rRd50UBvukztawZ7zyA8TxlrKpF5z9jw==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.12.tgz", + "integrity": "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A==", "requires": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, @@ -20071,11 +20100,11 @@ } }, "@smithy/querystring-parser": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.10.tgz", - "integrity": "sha512-4Mh18J26+ao1oX5wXJfWlTT+Q1OpDR8ssiC9PDOuEgVBGloqg18Fw7h5Ct8DyT9NBYwJgtJ2nLjKKFU6RP1G1Q==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.12.tgz", + "integrity": "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw==", "requires": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, @@ -20088,11 +20117,11 @@ } }, "@smithy/shared-ini-file-loader": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.5.tgz", - "integrity": "sha512-pHgASxl50rrtOztgQCPmOXFjRW+mCd7ALr/3uXNzRrRoGV5G2+78GOsQ3HlQuBVHCh9o6xqMNvlIKZjWn4Euug==", + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.7.tgz", + "integrity": "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw==", "requires": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, @@ -20126,20 +20155,20 @@ } }, "@smithy/types": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.0.tgz", - "integrity": "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } }, "@smithy/url-parser": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.10.tgz", - "integrity": "sha512-uypjF7fCDsRk26u3qHmFI/ePL7bxxB9vKkE+2WKEciHhz+4QtbzWiHRVNRJwU3cKhrYDYQE3b0MRFtqfLYdA4A==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.12.tgz", + "integrity": "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA==", "requires": { - "@smithy/querystring-parser": "^4.2.10", - "@smithy/types": "^4.13.0", + "@smithy/querystring-parser": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, @@ -20170,11 +20199,11 @@ } }, "@smithy/util-buffer-from": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.1.tgz", - "integrity": "sha512-/swhmt1qTiVkaejlmMPPDgZhEaWb/HWMGRBheaxwuVkusp/z+ErJyQxO6kaXumOciZSWlmq6Z5mNylCd33X7Ig==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", "requires": { - "@smithy/is-array-buffer": "^4.2.1", + "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, @@ -20212,12 +20241,12 @@ } }, "@smithy/util-endpoints": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.1.tgz", - "integrity": "sha512-xyctc4klmjmieQiF9I1wssBWleRV0RhJ2DpO8+8yzi2LO1Z+4IWOZNGZGNj4+hq9kdo+nyfrRLmQTzc16Op2Vg==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.3.tgz", + "integrity": "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig==", "requires": { - "@smithy/node-config-provider": "^4.3.10", - "@smithy/types": "^4.13.0", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, @@ -20272,11 +20301,11 @@ } }, "@smithy/util-utf8": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.1.tgz", - "integrity": "sha512-DSIwNaWtmzrNQHv8g7DBGR9mulSit65KSj5ymGEIAknmIN8IpbZefEep10LaMG/P/xquwbmJ1h9ectz8z6mV6g==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", "requires": { - "@smithy/util-buffer-from": "^4.2.1", + "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, @@ -20524,9 +20553,9 @@ "dev": true }, "@types/node": { - "version": "24.10.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.15.tgz", - "integrity": "sha512-BgjLoRuSr0MTI5wA6gMw9Xy0sFudAaUuvrnjgGx9wZ522fYYLA5SYJ+1Y30vTcJEG+DRCyDHx/gzQVfofYzSdg==", + "version": "24.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", + "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", "requires": { "undici-types": "~7.16.0" }, @@ -21267,14 +21296,14 @@ } }, "@wdio/config": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.24.0.tgz", - "integrity": "sha512-rcHu0eG16rSEmHL0sEKDcr/vYFmGhQ5GOlmlx54r+1sgh6sf136q+kth4169s16XqviWGW3LjZbUfpTK29pGtw==", + "version": "9.25.0", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.25.0.tgz", + "integrity": "sha512-EWa7l1rrbSNthCRDpdBw7ESAa1/jAjSsWCGkaVAO0HMOGlQjzvYI6gNi4KUeymnurDZ2IPr0jr+f9We6AWi6QA==", "dev": true, "requires": { "@wdio/logger": "9.18.0", - "@wdio/types": "9.24.0", - "@wdio/utils": "9.24.0", + "@wdio/types": "9.25.0", + "@wdio/utils": "9.25.0", "deepmerge-ts": "^7.0.3", "glob": "^10.2.2", "import-meta-resolve": "^4.0.0", @@ -21362,9 +21391,9 @@ } }, "@wdio/protocols": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.24.0.tgz", - "integrity": "sha512-ozQKYddBLT4TRvU9J+fGrhVUtx3iDAe+KNCJcTDMFMxNSdDMR2xFQdNp8HLHypspk58oXTYCvz6ZYjySthhqsw==", + "version": "9.25.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.25.0.tgz", + "integrity": "sha512-PErbZqdpFmE69bRuku3OR34Ro2xuZNNLXYFOcJnjXJVzf5+ApDyGHYrMlvhtrrSy9/55LUybk851ppjS+3RoDA==", "dev": true }, "@wdio/repl": { @@ -21377,9 +21406,9 @@ }, "dependencies": { "@types/node": { - "version": "20.19.35", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz", - "integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==", + "version": "20.19.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", + "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", "dev": true, "requires": { "undici-types": "~6.21.0" @@ -21388,18 +21417,18 @@ } }, "@wdio/types": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.24.0.tgz", - "integrity": "sha512-PYYunNl8Uq1r8YMJAK6ReRy/V/XIrCSyj5cpCtR5EqCL6heETOORFj7gt4uPnzidfgbtMBcCru0LgjjlMiH1UQ==", + "version": "9.25.0", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.25.0.tgz", + "integrity": "sha512-ovSEcUBLz6gVDIsBZYKQXz8EGU37jS8sqbmlOe5+jB4XbsTBCyTLjQK/rO7LWQAKJcs0vBq+Pd+VrlsFtA7tTQ==", "dev": true, "requires": { "@types/node": "^20.1.0" }, "dependencies": { "@types/node": { - "version": "20.19.35", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz", - "integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==", + "version": "20.19.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", + "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", "dev": true, "requires": { "undici-types": "~6.21.0" @@ -21408,14 +21437,14 @@ } }, "@wdio/utils": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.24.0.tgz", - "integrity": "sha512-6WhtzC5SNCGRBTkaObX6A07Ofnnyyf+TQH/d/fuhZRqvBknrP4AMMZF+PFxGl1fwdySWdBn+gV2QLE+52Byowg==", + "version": "9.25.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.25.0.tgz", + "integrity": "sha512-w/ej8gZkc2tZr8L91ATyA1AWrbPDYDOvblQ7r+zt1uPRobuA4H98GME7Zm7i3FIP695BvV4G35Gcs5NssZW1pw==", "dev": true, "requires": { "@puppeteer/browsers": "^2.2.0", "@wdio/logger": "9.18.0", - "@wdio/types": "9.24.0", + "@wdio/types": "9.25.0", "decamelize": "^6.0.0", "deepmerge-ts": "^7.0.3", "edgedriver": "^6.1.2", @@ -21430,9 +21459,9 @@ } }, "@zip.js/zip.js": { - "version": "2.8.21", - "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.21.tgz", - "integrity": "sha512-fkyzXISE3IMrstDO1AgPkJCx14MYHP/suIGiAovEYEuBjq3mffsuL6aMV7ohOSjW4rXtuACuUfpA3GtITgdtYg==", + "version": "2.8.23", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.23.tgz", + "integrity": "sha512-RB+RLnxPJFPrGvQ9rgO+4JOcsob6lD32OcF0QE0yg24oeW9q8KnTTNlugcDaIveEcCbclobJcZP+fLQ++sH0bw==", "dev": true }, "abort-controller": { @@ -21760,11 +21789,10 @@ "requires": {} }, "bare-fs": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.4.tgz", - "integrity": "sha512-POK4oplfA7P7gqvetNmCs4CNtm9fNsx+IAh7jH7GgU0OJdge2rso0R20TNWVq6VoWcCvsTdlNDaleLHGaKx8CA==", + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.5.tgz", + "integrity": "sha512-XvwYM6VZqKoqDll8BmSww5luA5eflDzY0uEFfBJtFKe4PAAtxBjU3YIxzIBzhyaEQBy1VXEQBto4cpN5RZJw+w==", "dev": true, - "optional": true, "requires": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", @@ -21774,28 +21802,25 @@ } }, "bare-os": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", - "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", - "dev": true, - "optional": true + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.0.tgz", + "integrity": "sha512-Dc9/SlwfxkXIGYhvMQNUtKaXCaGkZYGcd1vuNUUADVqzu4/vQfvnMkYYOUnt2VwQ2AqKr/8qAVFRtwETljgeFg==", + "dev": true }, "bare-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", "dev": true, - "optional": true, "requires": { "bare-os": "^3.0.1" } }, "bare-stream": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.8.0.tgz", - "integrity": "sha512-reUN0M2sHRqCdG4lUK3Fw8w98eeUIZHL5c3H7Mbhk2yVBL+oofgaIp0ieLfD5QXwPCypBpmEEKU2WZKzbAk8GA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.8.1.tgz", + "integrity": "sha512-bSeR8RfvbRwDpD7HWZvn8M3uYNDrk7m9DQjYOFkENZlXW8Ju/MPaqUPQq5LqJ3kyjEm07siTaAQ7wBKCU59oHg==", "dev": true, - "optional": true, "requires": { "streamx": "^2.21.0", "teex": "^1.0.1" @@ -21806,7 +21831,6 @@ "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", "dev": true, - "optional": true, "requires": { "bare-path": "^3.0.0" } @@ -21823,9 +21847,9 @@ "dev": true }, "bl": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.4.tgz", - "integrity": "sha512-ZV/9asSuknOExbM/zPPA8z00lc1ihPKWaStHkkQrxHNeYx+yY+TmF+v80dpv2G0mv3HVXBu7ryoAsxbFFhf4eg==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.6.tgz", + "integrity": "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==", "requires": { "@types/readable-stream": "^4.0.0", "buffer": "^6.0.3", @@ -23233,11 +23257,20 @@ "tslib": "^2.8.1" } }, - "fast-xml-parser": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.6.tgz", - "integrity": "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==", + "fast-xml-builder": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.3.tgz", + "integrity": "sha512-1o60KoFw2+LWKQu3IdcfcFlGTW4dpqEWmjhYec6H82AYZU2TVBXep6tMl8Z1Y+wM+ZrzCwe3BZ9Vyd9N2rIvmg==", "requires": { + "path-expression-matcher": "^1.1.3" + } + }, + "fast-xml-parser": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", + "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", + "requires": { + "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" } }, @@ -23366,9 +23399,9 @@ } }, "flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", + "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", "dev": true }, "for-each": { @@ -25578,6 +25611,11 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, + "path-expression-matcher": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz", + "integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==" + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -25607,9 +25645,9 @@ }, "dependencies": { "lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", "dev": true } } @@ -26580,9 +26618,9 @@ } }, "safe-regex2": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz", - "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.0.tgz", + "integrity": "sha512-pNHAuBW7TrcleFHsxBr5QMi/Iyp0ENjUKz7GCcX1UO7cMh+NmVK6HxQckNL1tJp1XAJVjG6B8OKIPqodqj9rtw==", "dev": true, "requires": { "ret": "~0.5.0" @@ -26964,9 +27002,9 @@ "dev": true }, "strnum": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", - "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz", + "integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==" }, "style-mod": { "version": "4.1.3", @@ -27097,9 +27135,9 @@ "requires": {} }, "tar-fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", - "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz", + "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", "dev": true, "requires": { "bare-fs": "^4.0.1", @@ -27109,12 +27147,13 @@ } }, "tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", + "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==", "dev": true, "requires": { "b4a": "^1.6.4", + "bare-fs": "^4.5.5", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } @@ -27124,7 +27163,6 @@ "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", "dev": true, - "optional": true, "requires": { "streamx": "^2.12.5" } @@ -27319,212 +27357,212 @@ }, "dependencies": { "@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", "dev": true, "optional": true }, "@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", "dev": true, "optional": true }, "@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", "dev": true, "optional": true }, "@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", "dev": true, "optional": true }, "@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", "dev": true, "optional": true }, "@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", "dev": true, "optional": true }, "@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", "dev": true, "optional": true }, "@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", "dev": true, "optional": true }, "@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", "dev": true, "optional": true }, "@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", "dev": true, "optional": true }, "@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", "dev": true, "optional": true }, "@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", "dev": true, "optional": true }, "@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", "dev": true, "optional": true }, "@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", "dev": true, "optional": true }, "@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", "dev": true, "optional": true }, "@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", "dev": true, "optional": true }, "@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", "dev": true, "optional": true }, "@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", "dev": true, "optional": true }, "@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", "dev": true, "optional": true }, "@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", "dev": true, "optional": true }, "@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", "dev": true, "optional": true }, "@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", "dev": true, "optional": true }, "@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", "dev": true, "optional": true }, "@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", "dev": true, "optional": true }, "@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", "dev": true, "optional": true }, "esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", "dev": true, "requires": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" } } } @@ -27660,9 +27698,9 @@ } }, "undici": { - "version": "7.22.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", - "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.2.tgz", + "integrity": "sha512-P9J1HWYV/ajFr8uCqk5QixwiRKmB1wOamgS0e+o2Z4A44Ej2+thFVRLG/eA7qprx88XXhnV5Bl8LHXTURpzB3Q==", "dev": true }, "undici-types": { @@ -27733,212 +27771,212 @@ }, "dependencies": { "@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", "dev": true, "optional": true }, "@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", "dev": true, "optional": true }, "@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", "dev": true, "optional": true }, "@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", "dev": true, "optional": true }, "@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", "dev": true, "optional": true }, "@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", "dev": true, "optional": true }, "@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", "dev": true, "optional": true }, "@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", "dev": true, "optional": true }, "@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", "dev": true, "optional": true }, "@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", "dev": true, "optional": true }, "@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", "dev": true, "optional": true }, "@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", "dev": true, "optional": true }, "@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", "dev": true, "optional": true }, "@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", "dev": true, "optional": true }, "@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", "dev": true, "optional": true }, "@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", "dev": true, "optional": true }, "@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", "dev": true, "optional": true }, "@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", "dev": true, "optional": true }, "@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", "dev": true, "optional": true }, "@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", "dev": true, "optional": true }, "@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", "dev": true, "optional": true }, "@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", "dev": true, "optional": true }, "@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", "dev": true, "optional": true }, "@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", "dev": true, "optional": true }, "@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", "dev": true, "optional": true }, "esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", "dev": true, "requires": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" } }, "fdir": { @@ -28058,18 +28096,18 @@ "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==" }, "webdriver": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.24.0.tgz", - "integrity": "sha512-2R31Ey83NzMsafkl4hdFq6GlIBvOODQMkueLjeRqYAITu3QCYiq9oqBdnWA6CdePuV4dbKlYsKRX0mwMiPclDA==", + "version": "9.25.0", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.25.0.tgz", + "integrity": "sha512-XnABKdrp83zX3xVltmX0OcFzn8zOzWGtZQxIUKY0+INB0g9Nnnfu7G75W0G+0y4nyb3zH8mavGzDBiXctdEd3Q==", "dev": true, "requires": { "@types/node": "^20.1.0", "@types/ws": "^8.5.3", - "@wdio/config": "9.24.0", + "@wdio/config": "9.25.0", "@wdio/logger": "9.18.0", - "@wdio/protocols": "9.24.0", - "@wdio/types": "9.24.0", - "@wdio/utils": "9.24.0", + "@wdio/protocols": "9.25.0", + "@wdio/types": "9.25.0", + "@wdio/utils": "9.25.0", "deepmerge-ts": "^7.0.3", "https-proxy-agent": "^7.0.6", "undici": "^6.21.3", @@ -28077,36 +28115,36 @@ }, "dependencies": { "@types/node": { - "version": "20.19.35", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz", - "integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==", + "version": "20.19.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", + "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", "dev": true, "requires": { "undici-types": "~6.21.0" } }, "undici": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", - "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "version": "6.24.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.0.tgz", + "integrity": "sha512-lVLNosgqo5EkGqh5XUDhGfsMSoO8K0BAN0TyJLvwNRSl4xWGZlCVYsAIpa/OpA3TvmnM01GWcoKmc3ZWo5wKKA==", "dev": true } } }, "webdriverio": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.24.0.tgz", - "integrity": "sha512-LTJt6Z/iDM0ne/4ytd3BykoPv9CuJ+CAILOzlwFeMGn4Mj02i4Bk2Rg9o/jeJ89f52hnv4OPmNjD0e8nzWAy5g==", + "version": "9.25.0", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.25.0.tgz", + "integrity": "sha512-ualC/LtWGjL5rwGAbUUzURKqKoHJG2/qecEppcS9k4n1IX3MlbzGXuL/qpXiRbs/h4981HpRbZAKBxRYqwUe3g==", "dev": true, "requires": { "@types/node": "^20.11.30", "@types/sinonjs__fake-timers": "^8.1.5", - "@wdio/config": "9.24.0", + "@wdio/config": "9.25.0", "@wdio/logger": "9.18.0", - "@wdio/protocols": "9.24.0", + "@wdio/protocols": "9.25.0", "@wdio/repl": "9.16.2", - "@wdio/types": "9.24.0", - "@wdio/utils": "9.24.0", + "@wdio/types": "9.25.0", + "@wdio/utils": "9.25.0", "archiver": "^7.0.1", "aria-query": "^5.3.0", "cheerio": "^1.0.0-rc.12", @@ -28123,13 +28161,13 @@ "rgb2hex": "0.2.5", "serialize-error": "^12.0.0", "urlpattern-polyfill": "^10.0.0", - "webdriver": "9.24.0" + "webdriver": "9.25.0" }, "dependencies": { "@types/node": { - "version": "20.19.35", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz", - "integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==", + "version": "20.19.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", + "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", "dev": true, "requires": { "undici-types": "~6.21.0" diff --git a/package.json b/package.json index 5086786..c6884e1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.52-patched-1", + "version": "0.25.52-patched-2", "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 662c8a0..f9d0db4 100644 --- a/updates.md +++ b/updates.md @@ -3,7 +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. -## -- unreleased -- +## 0.25.52-patched-2 + +14th March, 2026 + +### Fixed + +- No longer unexpected `Unhandled Rejections` during P2P operations (waiting acceptance). +- Fixed an issue where conflicts cannot be resolved in Journal Sync ### CLI new features From 653cf8dfbebb19eb8b2551c1e9316d11ae355720 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sun, 15 Mar 2026 03:33:03 +0900 Subject: [PATCH 087/339] Refactor: Refactor P2P Replicator --- src/apps/cli/commands/p2p.ts | 48 +---- src/apps/webpeer/src/P2PReplicatorShim.ts | 170 +++++------------- src/features/P2PSync/CmdP2PReplicator.ts | 150 ++++------------ .../P2PReplicator/P2PReplicatorPane.svelte | 14 +- .../P2PReplicator/P2PReplicatorPaneView.ts | 4 +- .../P2PReplicator/PeerStatusRow.svelte | 12 +- src/lib | 2 +- test/suite/sync_common.ts | 8 +- 8 files changed, 101 insertions(+), 307 deletions(-) diff --git a/src/apps/cli/commands/p2p.ts b/src/apps/cli/commands/p2p.ts index 41013e0..dab475e 100644 --- a/src/apps/cli/commands/p2p.ts +++ b/src/apps/cli/commands/p2p.ts @@ -1,7 +1,7 @@ import type { LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; -import { P2P_DEFAULT_SETTINGS, SETTING_KEY_P2P_DEVICE_NAME, type EntryDoc } from "@lib/common/types"; +import { P2P_DEFAULT_SETTINGS } from "@lib/common/types"; import type { ServiceContext } from "@lib/services/base/ServiceBase"; -import { TrysteroReplicator } from "@lib/replication/trystero/TrysteroReplicator"; +import { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator"; type CLIP2PPeer = { peerId: string; @@ -32,42 +32,12 @@ function validateP2PSettings(core: LiveSyncBaseCore) { settings.P2P_IsHeadless = true; } -async function createReplicator(core: LiveSyncBaseCore): Promise { +function createReplicator(core: LiveSyncBaseCore): LiveSyncTrysteroReplicator { validateP2PSettings(core); - const getSettings = () => core.services.setting.currentSettings(); - const getDB = () => core.services.database.localDatabase.localDatabase; - const getSimpleStore = () => core.services.keyValueDB.openSimpleStore("p2p-sync"); - const getDeviceName = () => - core.services.config.getSmallConfig(SETTING_KEY_P2P_DEVICE_NAME) || core.services.vault.getVaultName(); - - const env = { - get settings() { - return getSettings(); - }, - get db() { - return getDB(); - }, - get simpleStore() { - return getSimpleStore(); - }, - get deviceName() { - return getDeviceName(); - }, - get platform() { - return core.services.API.getPlatform(); - }, - get confirm() { - return core.services.API.confirm; - }, - processReplicatedDocs: async (docs: EntryDoc[]) => { - await core.services.replication.parseSynchroniseResult(docs as any); - }, - }; - - return new TrysteroReplicator(env as any); + return new LiveSyncTrysteroReplicator({ services: core.services }); } -function getSortedPeers(replicator: TrysteroReplicator): CLIP2PPeer[] { +function getSortedPeers(replicator: LiveSyncTrysteroReplicator): CLIP2PPeer[] { return [...replicator.knownAdvertisements] .map((peer) => ({ peerId: peer.peerId, name: peer.name })) .sort((a, b) => a.peerId.localeCompare(b.peerId)); @@ -77,7 +47,7 @@ export async function collectPeers( core: LiveSyncBaseCore, timeoutSec: number ): Promise { - const replicator = await createReplicator(core); + const replicator = createReplicator(core); await replicator.open(); try { await delay(timeoutSec * 1000); @@ -107,7 +77,7 @@ export async function syncWithPeer( peerToken: string, timeoutSec: number ): Promise { - const replicator = await createReplicator(core); + const replicator = createReplicator(core); await replicator.open(); try { const timeoutMs = timeoutSec * 1000; @@ -142,8 +112,8 @@ export async function syncWithPeer( } } -export async function openP2PHost(core: LiveSyncBaseCore): Promise { - const replicator = await createReplicator(core); +export async function openP2PHost(core: LiveSyncBaseCore): Promise { + const replicator = createReplicator(core); await replicator.open(); return replicator; } diff --git a/src/apps/webpeer/src/P2PReplicatorShim.ts b/src/apps/webpeer/src/P2PReplicatorShim.ts index 282c60d..63f2b7e 100644 --- a/src/apps/webpeer/src/P2PReplicatorShim.ts +++ b/src/apps/webpeer/src/P2PReplicatorShim.ts @@ -1,10 +1,8 @@ import { PouchDB } from "@lib/pouchdb/pouchdb-browser"; import { type EntryDoc, - type LOG_LEVEL, type ObsidianLiveSyncSettings, type P2PSyncSetting, - LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, P2P_DEFAULT_SETTINGS, REMOTE_P2P, @@ -12,35 +10,33 @@ import { import { eventHub } from "@lib/hub/hub"; import type { Confirm } from "@lib/interfaces/Confirm"; -import { LOG_LEVEL_INFO, Logger } from "@lib/common/logger"; +import { LOG_LEVEL_NOTICE, Logger } from "@lib/common/logger"; import { storeP2PStatusLine } from "./CommandsShim"; import { EVENT_P2P_PEER_SHOW_EXTRA_MENU, - type CommandShim, type PeerStatus, type PluginShim, } from "@lib/replication/trystero/P2PReplicatorPaneCommon"; import { - closeP2PReplicator, - openP2PReplicator, P2PLogCollector, type P2PReplicatorBase, + useP2PReplicator, } from "@lib/replication/trystero/P2PReplicatorCore"; import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase"; import { reactiveSource } from "octagonal-wheels/dataobject/reactive_v2"; import { EVENT_SETTING_SAVED } from "@lib/events/coreEvents"; import { unique } from "octagonal-wheels/collection"; import { BrowserServiceHub } from "@lib/services/BrowserServices"; -import { TrysteroReplicator } from "@lib/replication/trystero/TrysteroReplicator"; import { SETTING_KEY_P2P_DEVICE_NAME } from "@lib/common/types"; import { ServiceContext } from "@lib/services/base/ServiceBase"; import type { InjectableServiceHub } from "@lib/services/InjectableServices"; import { Menu } from "@lib/services/implements/browser/Menu"; -import type { InjectableVaultServiceCompat } from "@lib/services/implements/injectable/InjectableVaultService"; import { SimpleStoreIDBv2 } from "octagonal-wheels/databases/SimpleStoreIDBv2"; -import type { InjectableAPIService } from "@/lib/src/services/implements/injectable/InjectableAPIService"; import type { BrowserAPIService } from "@/lib/src/services/implements/browser/BrowserAPIService"; import type { InjectableSettingService } from "@/lib/src/services/implements/injectable/InjectableSettingService"; +import { + LiveSyncTrysteroReplicator, +} from "@lib/replication/trystero/LiveSyncTrysteroReplicator"; function addToList(item: string, list: string) { return unique( @@ -60,12 +56,10 @@ function removeFromList(item: string, list: string) { .join(","); } -export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { +export class P2PReplicatorShim implements P2PReplicatorBase { storeP2PStatusLine = reactiveSource(""); plugin!: PluginShim; - // environment!: IEnvironment; confirm!: Confirm; - // simpleStoreAPI!: ISimpleStoreAPI; db?: PouchDB.Database; services: InjectableServiceHub; @@ -76,12 +70,26 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { return this.db; } _simpleStore!: SimpleStore; + async closeDB() { if (this.db) { await this.db.close(); this.db = undefined; } } + + private _liveSyncReplicator?: LiveSyncTrysteroReplicator; + p2pLogCollector!: P2PLogCollector; + + private _initP2PReplicator() { + const { replicator, p2pLogCollector, storeP2PStatusLine: p2pStatusLine } = useP2PReplicator({ services: this.services } as any); + this._liveSyncReplicator = replicator; + this.p2pLogCollector = p2pLogCollector; + p2pLogCollector.p2pReplicationLine.onChanged((line) => { + storeP2PStatusLine.set(line.value); + }); + } + constructor() { const browserServiceHub = new BrowserServiceHub(); this.services = browserServiceHub; @@ -89,7 +97,6 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { (this.services.API as BrowserAPIService).getSystemVaultName.setHandler( () => "p2p-livesync-web-peer" ); - // this.services.API.addLog.setHandler(Logger); const repStore = SimpleStoreIDBv2.open("p2p-livesync-web-peer"); this._simpleStore = repStore; let _settings = { ...P2P_DEFAULT_SETTINGS, additionalSuffixOfDatabaseName: "" } as ObsidianLiveSyncSettings; @@ -103,14 +110,13 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { return settings; }); } + get settings() { return this.services.setting.currentSettings() as P2PSyncSetting; } + async init() { - // const { simpleStoreAPI } = await getWrappedSynchromesh(); - // this.confirm = confirm; this.confirm = this.services.UI.confirm; - // this.environment = environment; if (this.db) { try { @@ -123,30 +129,16 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { await this.services.setting.loadSettings(); this.plugin = { - // saveSettings: async () => { - // await repStore.set("settings", _settings); - // eventHub.emitEvent(EVENT_SETTING_SAVED, _settings); - // }, - // get settings() { - // return _settings; - // }, - // set settings(newSettings: P2PSyncSetting) { - // _settings = { ..._settings, ...newSettings }; - // }, - // rebuilder: null, - // core: { - // settings: this.services.setting.settings, - // }, services: this.services, core: { services: this.services, }, - // $$scheduleAppReload: () => {}, - // $$getVaultName: () => "p2p-livesync-web-peer", }; - // const deviceName = this.getDeviceName(); const database_name = this.settings.P2P_AppID + "-" + this.settings.P2P_roomID + "p2p-livesync-web-peer"; this.db = new PouchDB(database_name); + + this._initP2PReplicator(); + setTimeout(() => { if (this.settings.P2P_AutoStart && this.settings.P2P_Enabled) { void this.open(); @@ -155,7 +147,7 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { return this; } - _log(msg: any, level?: LOG_LEVEL): void { + _log(msg: any, level?: any): void { Logger(msg, level); } _notice(msg: string, key?: string): void { @@ -167,14 +159,10 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { simpleStore(): SimpleStore { return this._simpleStore; } - handleReplicatedDocuments(docs: EntryDoc[]): Promise { - // No op. This is a client and does not need to process the docs + handleReplicatedDocuments(_docs: EntryDoc[]): Promise { return Promise.resolve(true); } - getPluginShim() { - return {}; - } getConfig(key: string) { const vaultName = this.services.vault.getVaultName(); const dbKey = `${vaultName}-${key}`; @@ -189,9 +177,7 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { getDeviceName(): string { return this.getConfig(SETTING_KEY_P2P_DEVICE_NAME) ?? this.plugin.services.vault.getVaultName(); } - getPlatform(): string { - return "pseudo-replicator"; - } + m?: Menu; afterConstructor(): void { eventHub.onEvent(EVENT_P2P_PEER_SHOW_EXTRA_MENU, ({ peer, event }) => { @@ -202,12 +188,6 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { .addItem((item) => item.setTitle("📥 Only Fetch").onClick(() => this.replicateFrom(peer))) .addItem((item) => item.setTitle("📤 Only Send").onClick(() => this.replicateTo(peer))) .addSeparator() - // .addItem((item) => { - // item.setTitle("🔧 Get Configuration").onClick(async () => { - // await this.getRemoteConfig(peer); - // }); - // }) - // .addSeparator() .addItem((item) => { const mark = peer.syncOnConnect ? "checkmark" : null; item.setTitle("Toggle Sync on connect") @@ -234,97 +214,43 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { }); void this.m.showAtPosition({ x: event.x, y: event.y }); }); - this.p2pLogCollector.p2pReplicationLine.onChanged((line) => { - storeP2PStatusLine.set(line.value); - }); } - _replicatorInstance?: TrysteroReplicator; - p2pLogCollector = new P2PLogCollector(); async open() { - await openP2PReplicator(this); + await this._liveSyncReplicator?.open(); } + async close() { - await closeP2PReplicator(this); + await this._liveSyncReplicator?.close(); } + enableBroadcastCastings() { - return this?._replicatorInstance?.enableBroadcastChanges(); + return this._liveSyncReplicator?.enableBroadcastChanges(); } disableBroadcastCastings() { - return this?._replicatorInstance?.disableBroadcastChanges(); - } - - async initialiseP2PReplicator(): Promise { - await this.init(); - try { - if (this._replicatorInstance) { - await this._replicatorInstance.close(); - this._replicatorInstance = undefined; - } - - if (!this.settings.P2P_AppID) { - this.settings.P2P_AppID = P2P_DEFAULT_SETTINGS.P2P_AppID; - } - const getInitialDeviceName = () => - this.getConfig(SETTING_KEY_P2P_DEVICE_NAME) || this.services.vault.getVaultName(); - - const getSettings = () => this.settings; - const store = () => this.simpleStore(); - const getDB = () => this.getDB(); - - const getConfirm = () => this.confirm; - const getPlatform = () => this.getPlatform(); - const env = { - get db() { - return getDB(); - }, - get confirm() { - return getConfirm(); - }, - get deviceName() { - return getInitialDeviceName(); - }, - get platform() { - return getPlatform(); - }, - get settings() { - return getSettings(); - }, - processReplicatedDocs: async (docs: EntryDoc[]): Promise => { - await this.handleReplicatedDocuments(docs); - // No op. This is a client and does not need to process the docs - }, - get simpleStore() { - return store(); - }, - }; - this._replicatorInstance = new TrysteroReplicator(env); - return this._replicatorInstance; - } catch (e) { - this._log( - e instanceof Error ? e.message : "Something occurred on Initialising P2P Replicator", - LOG_LEVEL_INFO - ); - this._log(e, LOG_LEVEL_VERBOSE); - throw e; - } + return this._liveSyncReplicator?.disableBroadcastChanges(); } get replicator() { - return this._replicatorInstance!; + return this._liveSyncReplicator; } + async replicateFrom(peer: PeerStatus) { - await this.replicator.replicateFrom(peer.peerId); + const r = this._liveSyncReplicator; + if (!r) return; + await r.replicateFrom(peer.peerId); } + async replicateTo(peer: PeerStatus) { - await this.replicator.requestSynchroniseToPeer(peer.peerId); + await this._liveSyncReplicator?.requestSynchroniseToPeer(peer.peerId); } + async getRemoteConfig(peer: PeerStatus) { Logger( `Requesting remote config for ${peer.name}. Please input the passphrase on the remote device`, LOG_LEVEL_NOTICE ); - const remoteConfig = await this.replicator.getRemoteConfig(peer.peerId); + const remoteConfig = await this._liveSyncReplicator?.getRemoteConfig(peer.peerId); if (remoteConfig) { Logger(`Remote config for ${peer.name} is retrieved successfully`); const DROP = "Yes, and drop local database"; @@ -344,9 +270,7 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { if (remoteConfig.remoteType !== REMOTE_P2P) { const yn2 = await this.confirm.askYesNoDialog( `Do you want to set the remote type to "P2P Sync" to rebuild by "P2P replication"?`, - { - title: "Rebuild from remote device", - } + { title: "Rebuild from remote device" } ); if (yn2 === "yes") { remoteConfig.remoteType = REMOTE_P2P; @@ -355,9 +279,7 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { } } await this.services.setting.applyPartial(remoteConfig, true); - if (yn === DROP) { - // await this.plugin.rebuilder.scheduleFetch(); - } else { + if (yn !== DROP) { await this.plugin.core.services.appLifecycle.scheduleRestart(); } } else { @@ -381,8 +303,6 @@ export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { [targetSetting]: currentSettingAll ? currentSettingAll[targetSetting] : "", }; if (peer[prop]) { - // this.plugin.settings[targetSetting] = removeFromList(peer.name, this.plugin.settings[targetSetting]); - // await this.plugin.saveSettings(); currentSetting[targetSetting] = removeFromList(peer.name, currentSetting[targetSetting]); } else { currentSetting[targetSetting] = addToList(peer.name, currentSetting[targetSetting]); diff --git a/src/features/P2PSync/CmdP2PReplicator.ts b/src/features/P2PSync/CmdP2PReplicator.ts index fbbd37b..0325176 100644 --- a/src/features/P2PSync/CmdP2PReplicator.ts +++ b/src/features/P2PSync/CmdP2PReplicator.ts @@ -2,40 +2,36 @@ import { P2PReplicatorPaneView, VIEW_TYPE_P2P } from "./P2PReplicator/P2PReplica import { AutoAccepting, LOG_LEVEL_NOTICE, - P2P_DEFAULT_SETTINGS, REMOTE_P2P, - type EntryDoc, type P2PSyncSetting, type RemoteDBSettings, } from "../../lib/src/common/types.ts"; import { LiveSyncCommands } from "../LiveSyncCommands.ts"; -import { - LiveSyncTrysteroReplicator, - setReplicatorFunc, -} from "../../lib/src/replication/trystero/LiveSyncTrysteroReplicator.ts"; +import { LiveSyncTrysteroReplicator } from "../../lib/src/replication/trystero/LiveSyncTrysteroReplicator.ts"; import { EVENT_REQUEST_OPEN_P2P, eventHub } from "../../common/events.ts"; import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator.ts"; -import { LOG_LEVEL_INFO, LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger"; -import type { CommandShim } from "../../lib/src/replication/trystero/P2PReplicatorPaneCommon.ts"; +import { Logger } from "octagonal-wheels/common/logger"; import { - addP2PEventHandlers, - closeP2PReplicator, - openP2PReplicator, P2PLogCollector, - removeP2PReplicatorInstance, type P2PReplicatorBase, + useP2PReplicator, } from "../../lib/src/replication/trystero/P2PReplicatorCore.ts"; -import { reactiveSource } from "octagonal-wheels/dataobject/reactive_v2"; +import type { ReactiveSource } from "octagonal-wheels/dataobject/reactive_v2"; import type { Confirm } from "../../lib/src/interfaces/Confirm.ts"; import type ObsidianLiveSyncPlugin from "../../main.ts"; import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase"; -// import { getPlatformName } from "../../lib/src/PlatformAPIs/obsidian/Environment.ts"; import type { LiveSyncCore } from "../../main.ts"; -import { TrysteroReplicator } from "../../lib/src/replication/trystero/TrysteroReplicator.ts"; -import { SETTING_KEY_P2P_DEVICE_NAME } from "../../lib/src/common/types.ts"; +import type { EntryDoc } from "../../lib/src/common/types.ts"; -export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase, CommandShim { - storeP2PStatusLine = reactiveSource(""); +export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase { + storeP2PStatusLine!: ReactiveSource; + p2pLogCollector!: P2PLogCollector; + + private _liveSyncReplicator?: LiveSyncTrysteroReplicator; + + get liveSyncReplicator() { + return this._liveSyncReplicator; + } getSettings(): P2PSyncSetting { return this.core.settings; @@ -43,27 +39,20 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase getDB() { return this.core.localDatabase.localDatabase; } - get confirm(): Confirm { return this.core.confirm; } _simpleStore!: SimpleStore; - simpleStore(): SimpleStore { return this._simpleStore; } constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) { super(plugin, core); - setReplicatorFunc(() => this._replicatorInstance); - addP2PEventHandlers(this); this.afterConstructor(); - // onBindFunction is called in super class - // this.onBindFunction(plugin, plugin.services); } async handleReplicatedDocuments(docs: EntryDoc[]): Promise { - // console.log("Processing Replicated Docs", docs); return await this.services.replication.parseSynchroniseResult( docs as PouchDB.Core.ExistingDocument[] ); @@ -72,22 +61,21 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase _anyNewReplicator(settingOverride: Partial = {}): Promise { const settings = { ...this.settings, ...settingOverride }; if (settings.remoteType == REMOTE_P2P) { - return Promise.resolve(new LiveSyncTrysteroReplicator(this.plugin.core)); + return Promise.resolve(new LiveSyncTrysteroReplicator({ services: this.services })); } return undefined!; } - _replicatorInstance?: TrysteroReplicator; - p2pLogCollector = new P2PLogCollector(); afterConstructor() { return; } async open() { - await openP2PReplicator(this); + await this._liveSyncReplicator?.open(); } + async close() { - await closeP2PReplicator(this); + await this._liveSyncReplicator?.close(); } getConfig(key: string) { @@ -97,10 +85,10 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase return this.services.config.setSmallConfig(key, value); } enableBroadcastCastings() { - return this?._replicatorInstance?.enableBroadcastChanges(); + return this._liveSyncReplicator?.enableBroadcastChanges(); } disableBroadcastCastings() { - return this?._replicatorInstance?.disableBroadcastChanges(); + return this._liveSyncReplicator?.disableBroadcastChanges(); } init() { @@ -108,64 +96,7 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase return Promise.resolve(this); } - async initialiseP2PReplicator(): Promise { - await this.init(); - try { - if (this._replicatorInstance) { - await this._replicatorInstance.close(); - this._replicatorInstance = undefined; - } - - if (!this.settings.P2P_AppID) { - this.settings.P2P_AppID = P2P_DEFAULT_SETTINGS.P2P_AppID; - } - const getInitialDeviceName = () => - this.getConfig(SETTING_KEY_P2P_DEVICE_NAME) || this.services.vault.getVaultName(); - - const getSettings = () => this.settings; - const store = () => this.simpleStore(); - const getDB = () => this.getDB(); - - const getConfirm = () => this.confirm; - const getPlatform = () => this.services.API.getPlatform(); - const env = { - get db() { - return getDB(); - }, - get confirm() { - return getConfirm(); - }, - get deviceName() { - return getInitialDeviceName(); - }, - get platform() { - return getPlatform(); - }, - get settings() { - return getSettings(); - }, - processReplicatedDocs: async (docs: EntryDoc[]): Promise => { - await this.handleReplicatedDocuments(docs); - // No op. This is a client and does not need to process the docs - }, - get simpleStore() { - return store(); - }, - }; - this._replicatorInstance = new TrysteroReplicator(env); - return this._replicatorInstance; - } catch (e) { - this._log( - e instanceof Error ? e.message : "Something occurred on Initialising P2P Replicator", - LOG_LEVEL_INFO - ); - this._log(e, LOG_LEVEL_VERBOSE); - throw e; - } - } - onunload(): void { - removeP2PReplicatorInstance(); void this.close(); } @@ -173,13 +104,6 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase eventHub.onEvent(EVENT_REQUEST_OPEN_P2P, () => { void this.openPane(); }); - this.p2pLogCollector.p2pReplicationLine.onChanged((line) => { - this.storeP2PStatusLine.value = line.value; - }); - } - async _everyOnInitializeDatabase(): Promise { - await this.initialiseP2PReplicator(); - return Promise.resolve(true); } private async _allSuspendExtraSync() { @@ -192,10 +116,6 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase return await Promise.resolve(true); } - // async $everyOnLoadStart() { - // return await Promise.resolve(); - // } - async openPane() { await this.services.API.showWindow(VIEW_TYPE_P2P); } @@ -217,7 +137,7 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase name: "P2P Sync : Connect to the Signalling Server", checkCallback: (isChecking) => { if (isChecking) { - return !(this._replicatorInstance?.server?.isServing ?? false); + return !(this._liveSyncReplicator?.server?.isServing ?? false); } void this.open(); }, @@ -227,7 +147,7 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase name: "P2P Sync : Disconnect from the Signalling Server", checkCallback: (isChecking) => { if (isChecking) { - return this._replicatorInstance?.server?.isServing ?? false; + return this._liveSyncReplicator?.server?.isServing ?? false; } Logger(`Closing P2P Connection`, LOG_LEVEL_NOTICE); void this.close(); @@ -239,10 +159,10 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase checkCallback: (isChecking) => { if (isChecking) { if (this.settings.remoteType == REMOTE_P2P) return false; - if (!this._replicatorInstance?.server?.isServing) return false; + if (!this._liveSyncReplicator?.server?.isServing) return false; return true; } - void this._replicatorInstance?.replicateFromCommand(false); + void this._liveSyncReplicator?.replicateFromCommand(false); }, }); this.plugin @@ -253,26 +173,16 @@ export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase return await Promise.resolve(true); } - _everyAfterResumeProcess(): Promise { - if (this.settings.P2P_Enabled && this.settings.P2P_AutoStart) { - setTimeout(() => void this.open(), 100); - } - const rep = this._replicatorInstance; - rep?.allowReconnection(); - return Promise.resolve(true); - } - _everyBeforeSuspendProcess(): Promise { - const rep = this._replicatorInstance; - rep?.disconnectFromServer(); - return Promise.resolve(true); - } override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { + // Initialise useP2PReplicator — wires lifecycle, event handlers, and log collector + const { replicator, p2pLogCollector, storeP2PStatusLine } = useP2PReplicator({ services } as any); + this._liveSyncReplicator = replicator; + this.p2pLogCollector = p2pLogCollector; + this.storeP2PStatusLine = storeP2PStatusLine; + services.replicator.getNewReplicator.addHandler(this._anyNewReplicator.bind(this)); - services.databaseEvents.onDatabaseInitialisation.addHandler(this._everyOnInitializeDatabase.bind(this)); services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); - services.appLifecycle.onSuspending.addHandler(this._everyBeforeSuspendProcess.bind(this)); - services.appLifecycle.onResumed.addHandler(this._everyAfterResumeProcess.bind(this)); services.setting.suspendExtraSync.addHandler(this._allSuspendExtraSync.bind(this)); } } diff --git a/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte b/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte index 7ac67eb..4c34d64 100644 --- a/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte +++ b/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte @@ -4,10 +4,9 @@ import { AcceptedStatus, ConnectionStatus, - type CommandShim, type PeerStatus, - type PluginShim, } from "../../../lib/src/replication/trystero/P2PReplicatorPaneCommon"; + import type { P2PReplicator } from "../CmdP2PReplicator"; import PeerStatusRow from "../P2PReplicator/PeerStatusRow.svelte"; import { EVENT_LAYOUT_READY, eventHub } from "../../../common/events"; import { @@ -23,7 +22,7 @@ import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore"; interface Props { - cmdSync: CommandShim; + cmdSync: P2PReplicator; core: LiveSyncBaseCore; } @@ -95,9 +94,8 @@ }, true ); - cmdSync.setConfig(SETTING_KEY_P2P_DEVICE_NAME, eDeviceName); + core.services.config.setSmallConfig(SETTING_KEY_P2P_DEVICE_NAME, eDeviceName); deviceName = eDeviceName; - // await plugin.saveSettings(); } async function revert() { eP2PEnabled = settings.P2P_Enabled; @@ -115,7 +113,7 @@ const applyLoadSettings = (d: P2PSyncSetting, force: boolean) => { if (force) { const initDeviceName = - cmdSync.getConfig(SETTING_KEY_P2P_DEVICE_NAME) ?? core.services.vault.getVaultName(); + core.services.config.getSmallConfig(SETTING_KEY_P2P_DEVICE_NAME) ?? core.services.vault.getVaultName(); deviceName = initDeviceName; eDeviceName = initDeviceName; } @@ -248,7 +246,7 @@ const initialDialogStatusKey = `p2p-dialog-status`; const getDialogStatus = () => { try { - const initialDialogStatus = JSON.parse(cmdSync.getConfig(initialDialogStatusKey) ?? "{}") as { + const initialDialogStatus = JSON.parse(core.services.config.getSmallConfig(initialDialogStatusKey) ?? "{}") as { notice?: boolean; setting?: boolean; }; @@ -265,7 +263,7 @@ notice: isNoticeOpened, setting: isSettingOpened, }; - cmdSync.setConfig(initialDialogStatusKey, JSON.stringify(dialogStatus)); + core.services.config.setSmallConfig(initialDialogStatusKey, JSON.stringify(dialogStatus)); }); let isObsidian = $derived.by(() => { return core.services.API.getPlatform() === "obsidian"; diff --git a/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts b/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts index fcde9a2..bd4c106 100644 --- a/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts +++ b/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts @@ -46,10 +46,10 @@ export class P2PReplicatorPaneView extends SvelteItemView { } get replicator() { const r = this.core.getAddOn(P2PReplicator.name); - if (!r || !r._replicatorInstance) { + if (!r || !r.liveSyncReplicator) { throw new Error("Replicator not found"); } - return r._replicatorInstance; + return r.liveSyncReplicator; } async replicateFrom(peer: PeerStatus) { await this.replicator.replicateFrom(peer.peerId); diff --git a/src/features/P2PSync/P2PReplicator/PeerStatusRow.svelte b/src/features/P2PSync/P2PReplicator/PeerStatusRow.svelte index 2ee660a..2dd4397 100644 --- a/src/features/P2PSync/P2PReplicator/PeerStatusRow.svelte +++ b/src/features/P2PSync/P2PReplicator/PeerStatusRow.svelte @@ -57,7 +57,7 @@ let isNew = $derived.by(() => peer.accepted === AcceptedStatus.UNKNOWN); function makeDecision(isAccepted: boolean, isTemporary: boolean) { - cmdReplicator._replicatorInstance?.server?.makeDecision({ + cmdReplicator.liveSyncReplicator?.makeDecision({ peerId: peer.peerId, name: peer.name, decision: isAccepted, @@ -65,13 +65,13 @@ }); } function revokeDecision() { - cmdReplicator._replicatorInstance?.server?.revokeDecision({ + cmdReplicator.liveSyncReplicator?.revokeDecision({ peerId: peer.peerId, name: peer.name, }); } const cmdReplicator = getContext<() => P2PReplicator>("getReplicator")(); - const replicator = cmdReplicator._replicatorInstance!; + const replicator = cmdReplicator.liveSyncReplicator; const peerAttrLabels = $derived.by(() => { const attrs = []; @@ -87,14 +87,14 @@ return attrs; }); function startWatching() { - replicator.watchPeer(peer.peerId); + replicator?.watchPeer(peer.peerId); } function stopWatching() { - replicator.unwatchPeer(peer.peerId); + replicator?.unwatchPeer(peer.peerId); } function sync() { - replicator.sync(peer.peerId, false); + void replicator?.sync(peer.peerId, false); } function moreMenu(evt: MouseEvent) { diff --git a/src/lib b/src/lib index 10aa321..b5f22eb 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 10aa32108b4b6b2d78e7a45d8c4bd1150c947ac3 +Subproject commit b5f22eb8f379f2471a085f29dd30aaa1553e6a1e diff --git a/test/suite/sync_common.ts b/test/suite/sync_common.ts index f8ee4c2..74da866 100644 --- a/test/suite/sync_common.ts +++ b/test/suite/sync_common.ts @@ -1,6 +1,6 @@ import { expect } from "vitest"; import { waitForIdle, type LiveSyncHarness } from "../harness/harness"; -import { LOG_LEVEL_INFO, RemoteTypes, type ObsidianLiveSyncSettings } from "@/lib/src/common/types"; +import { RemoteTypes, type ObsidianLiveSyncSettings } from "@/lib/src/common/types"; import { delay, fireAndForget } from "@/lib/src/common/utils"; import { commands } from "vitest/browser"; @@ -15,14 +15,10 @@ async function waitForP2PPeers(harness: LiveSyncHarness) { if (!(replicator instanceof LiveSyncTrysteroReplicator)) { throw new Error("Replicator is not an instance of LiveSyncTrysteroReplicator"); } - const p2pReplicator = await replicator.getP2PConnection(LOG_LEVEL_INFO); - if (!p2pReplicator) { - throw new Error("P2P Replicator is not initialized"); - } while (retries-- > 0) { fireAndForget(() => commands.acceptWebPeer()); await delay(1000); - const peers = p2pReplicator.knownAdvertisements; + const peers = replicator.knownAdvertisements; if (peers && peers.length > 0) { console.log("P2P peers connected:", peers); From 89bf0488c3b3542371a1926147a3e850c72110a7 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sun, 15 Mar 2026 04:07:47 +0900 Subject: [PATCH 088/339] Refactor: More refactor P2P Replicator --- src/apps/cli/commands/p2p.ts | 5 +- src/apps/webpeer/src/P2PReplicatorShim.ts | 16 +- src/common/events.ts | 5 - src/features/P2PSync/CmdP2PReplicator.ts | 188 ------------------ .../P2PReplicator/P2PReplicatorPane.svelte | 8 +- .../P2PReplicator/P2PReplicatorPaneView.ts | 34 +--- .../P2PReplicator/PeerStatusRow.svelte | 9 +- src/lib | 2 +- src/main.ts | 12 +- 9 files changed, 41 insertions(+), 238 deletions(-) delete mode 100644 src/features/P2PSync/CmdP2PReplicator.ts diff --git a/src/apps/cli/commands/p2p.ts b/src/apps/cli/commands/p2p.ts index dab475e..b889f51 100644 --- a/src/apps/cli/commands/p2p.ts +++ b/src/apps/cli/commands/p2p.ts @@ -2,6 +2,7 @@ import type { LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; import { P2P_DEFAULT_SETTINGS } from "@lib/common/types"; import type { ServiceContext } from "@lib/services/base/ServiceBase"; import { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator"; +import { addP2PEventHandlers } from "@lib/replication/trystero/P2PReplicatorCore"; type CLIP2PPeer = { peerId: string; @@ -34,7 +35,9 @@ function validateP2PSettings(core: LiveSyncBaseCore) { function createReplicator(core: LiveSyncBaseCore): LiveSyncTrysteroReplicator { validateP2PSettings(core); - return new LiveSyncTrysteroReplicator({ services: core.services }); + const replicator = new LiveSyncTrysteroReplicator({ services: core.services }); + addP2PEventHandlers(replicator); + return replicator; } function getSortedPeers(replicator: LiveSyncTrysteroReplicator): CLIP2PPeer[] { diff --git a/src/apps/webpeer/src/P2PReplicatorShim.ts b/src/apps/webpeer/src/P2PReplicatorShim.ts index 63f2b7e..fd461ef 100644 --- a/src/apps/webpeer/src/P2PReplicatorShim.ts +++ b/src/apps/webpeer/src/P2PReplicatorShim.ts @@ -17,11 +17,7 @@ import { type PeerStatus, type PluginShim, } from "@lib/replication/trystero/P2PReplicatorPaneCommon"; -import { - P2PLogCollector, - type P2PReplicatorBase, - useP2PReplicator, -} from "@lib/replication/trystero/P2PReplicatorCore"; +import { P2PLogCollector, type P2PReplicatorBase, useP2PReplicator } from "@lib/replication/trystero/P2PReplicatorCore"; import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase"; import { reactiveSource } from "octagonal-wheels/dataobject/reactive_v2"; import { EVENT_SETTING_SAVED } from "@lib/events/coreEvents"; @@ -34,9 +30,7 @@ import { Menu } from "@lib/services/implements/browser/Menu"; import { SimpleStoreIDBv2 } from "octagonal-wheels/databases/SimpleStoreIDBv2"; import type { BrowserAPIService } from "@/lib/src/services/implements/browser/BrowserAPIService"; import type { InjectableSettingService } from "@/lib/src/services/implements/injectable/InjectableSettingService"; -import { - LiveSyncTrysteroReplicator, -} from "@lib/replication/trystero/LiveSyncTrysteroReplicator"; +import { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator"; function addToList(item: string, list: string) { return unique( @@ -82,7 +76,11 @@ export class P2PReplicatorShim implements P2PReplicatorBase { p2pLogCollector!: P2PLogCollector; private _initP2PReplicator() { - const { replicator, p2pLogCollector, storeP2PStatusLine: p2pStatusLine } = useP2PReplicator({ services: this.services } as any); + const { + replicator, + p2pLogCollector, + storeP2PStatusLine: p2pStatusLine, + } = useP2PReplicator({ services: this.services } as any); this._liveSyncReplicator = replicator; this.p2pLogCollector = p2pLogCollector; p2pLogCollector.p2pReplicationLine.onChanged((line) => { diff --git a/src/common/events.ts b/src/common/events.ts index e34346f..985ae94 100644 --- a/src/common/events.ts +++ b/src/common/events.ts @@ -16,9 +16,6 @@ export const EVENT_REQUEST_RELOAD_SETTING_TAB = "reload-setting-tab"; export const EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG = "request-open-plugin-sync-dialog"; -export const EVENT_REQUEST_OPEN_P2P = "request-open-p2p"; -export const EVENT_REQUEST_CLOSE_P2P = "request-close-p2p"; - export const EVENT_REQUEST_RUN_DOCTOR = "request-run-doctor"; export const EVENT_REQUEST_RUN_FIX_INCOMPLETE = "request-run-fix-incomplete"; @@ -36,8 +33,6 @@ declare global { [EVENT_REQUEST_OPEN_SETTING_WIZARD]: undefined; [EVENT_REQUEST_RELOAD_SETTING_TAB]: undefined; [EVENT_LEAF_ACTIVE_CHANGED]: undefined; - [EVENT_REQUEST_CLOSE_P2P]: undefined; - [EVENT_REQUEST_OPEN_P2P]: undefined; [EVENT_REQUEST_OPEN_SETUP_URI]: undefined; [EVENT_REQUEST_COPY_SETUP_URI]: undefined; [EVENT_REQUEST_SHOW_SETUP_QR]: undefined; diff --git a/src/features/P2PSync/CmdP2PReplicator.ts b/src/features/P2PSync/CmdP2PReplicator.ts deleted file mode 100644 index 0325176..0000000 --- a/src/features/P2PSync/CmdP2PReplicator.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { P2PReplicatorPaneView, VIEW_TYPE_P2P } from "./P2PReplicator/P2PReplicatorPaneView.ts"; -import { - AutoAccepting, - LOG_LEVEL_NOTICE, - REMOTE_P2P, - type P2PSyncSetting, - type RemoteDBSettings, -} from "../../lib/src/common/types.ts"; -import { LiveSyncCommands } from "../LiveSyncCommands.ts"; -import { LiveSyncTrysteroReplicator } from "../../lib/src/replication/trystero/LiveSyncTrysteroReplicator.ts"; -import { EVENT_REQUEST_OPEN_P2P, eventHub } from "../../common/events.ts"; -import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator.ts"; -import { Logger } from "octagonal-wheels/common/logger"; -import { - P2PLogCollector, - type P2PReplicatorBase, - useP2PReplicator, -} from "../../lib/src/replication/trystero/P2PReplicatorCore.ts"; -import type { ReactiveSource } from "octagonal-wheels/dataobject/reactive_v2"; -import type { Confirm } from "../../lib/src/interfaces/Confirm.ts"; -import type ObsidianLiveSyncPlugin from "../../main.ts"; -import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase"; -import type { LiveSyncCore } from "../../main.ts"; -import type { EntryDoc } from "../../lib/src/common/types.ts"; - -export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase { - storeP2PStatusLine!: ReactiveSource; - p2pLogCollector!: P2PLogCollector; - - private _liveSyncReplicator?: LiveSyncTrysteroReplicator; - - get liveSyncReplicator() { - return this._liveSyncReplicator; - } - - getSettings(): P2PSyncSetting { - return this.core.settings; - } - getDB() { - return this.core.localDatabase.localDatabase; - } - get confirm(): Confirm { - return this.core.confirm; - } - _simpleStore!: SimpleStore; - simpleStore(): SimpleStore { - return this._simpleStore; - } - - constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) { - super(plugin, core); - this.afterConstructor(); - } - - async handleReplicatedDocuments(docs: EntryDoc[]): Promise { - return await this.services.replication.parseSynchroniseResult( - docs as PouchDB.Core.ExistingDocument[] - ); - } - - _anyNewReplicator(settingOverride: Partial = {}): Promise { - const settings = { ...this.settings, ...settingOverride }; - if (settings.remoteType == REMOTE_P2P) { - return Promise.resolve(new LiveSyncTrysteroReplicator({ services: this.services })); - } - return undefined!; - } - - afterConstructor() { - return; - } - - async open() { - await this._liveSyncReplicator?.open(); - } - - async close() { - await this._liveSyncReplicator?.close(); - } - - getConfig(key: string) { - return this.services.config.getSmallConfig(key); - } - setConfig(key: string, value: string) { - return this.services.config.setSmallConfig(key, value); - } - enableBroadcastCastings() { - return this._liveSyncReplicator?.enableBroadcastChanges(); - } - disableBroadcastCastings() { - return this._liveSyncReplicator?.disableBroadcastChanges(); - } - - init() { - this._simpleStore = this.services.keyValueDB.openSimpleStore("p2p-sync"); - return Promise.resolve(this); - } - - onunload(): void { - void this.close(); - } - - onload(): void | Promise { - eventHub.onEvent(EVENT_REQUEST_OPEN_P2P, () => { - void this.openPane(); - }); - } - - private async _allSuspendExtraSync() { - this.plugin.core.settings.P2P_Enabled = false; - this.plugin.core.settings.P2P_AutoAccepting = AutoAccepting.NONE; - this.plugin.core.settings.P2P_AutoBroadcast = false; - this.plugin.core.settings.P2P_AutoStart = false; - this.plugin.core.settings.P2P_AutoSyncPeers = ""; - this.plugin.core.settings.P2P_AutoWatchPeers = ""; - return await Promise.resolve(true); - } - - async openPane() { - await this.services.API.showWindow(VIEW_TYPE_P2P); - } - - async _everyOnloadStart(): Promise { - this.plugin.registerView( - VIEW_TYPE_P2P, - (leaf) => new P2PReplicatorPaneView(leaf, this.plugin.core, this.plugin) - ); - this.plugin.addCommand({ - id: "open-p2p-replicator", - name: "P2P Sync : Open P2P Replicator", - callback: async () => { - await this.openPane(); - }, - }); - this.plugin.addCommand({ - id: "p2p-establish-connection", - name: "P2P Sync : Connect to the Signalling Server", - checkCallback: (isChecking) => { - if (isChecking) { - return !(this._liveSyncReplicator?.server?.isServing ?? false); - } - void this.open(); - }, - }); - this.plugin.addCommand({ - id: "p2p-close-connection", - name: "P2P Sync : Disconnect from the Signalling Server", - checkCallback: (isChecking) => { - if (isChecking) { - return this._liveSyncReplicator?.server?.isServing ?? false; - } - Logger(`Closing P2P Connection`, LOG_LEVEL_NOTICE); - void this.close(); - }, - }); - this.plugin.addCommand({ - id: "replicate-now-by-p2p", - name: "Replicate now by P2P", - checkCallback: (isChecking) => { - if (isChecking) { - if (this.settings.remoteType == REMOTE_P2P) return false; - if (!this._liveSyncReplicator?.server?.isServing) return false; - return true; - } - void this._liveSyncReplicator?.replicateFromCommand(false); - }, - }); - this.plugin - .addRibbonIcon("waypoints", "P2P Replicator", async () => { - await this.openPane(); - }) - .addClass("livesync-ribbon-replicate-p2p"); - - return await Promise.resolve(true); - } - - override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { - // Initialise useP2PReplicator — wires lifecycle, event handlers, and log collector - const { replicator, p2pLogCollector, storeP2PStatusLine } = useP2PReplicator({ services } as any); - this._liveSyncReplicator = replicator; - this.p2pLogCollector = p2pLogCollector; - this.storeP2PStatusLine = storeP2PStatusLine; - - services.replicator.getNewReplicator.addHandler(this._anyNewReplicator.bind(this)); - services.appLifecycle.onInitialise.addHandler(this._everyOnloadStart.bind(this)); - services.setting.suspendExtraSync.addHandler(this._allSuspendExtraSync.bind(this)); - } -} diff --git a/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte b/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte index 4c34d64..9dd7c8e 100644 --- a/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte +++ b/src/features/P2PSync/P2PReplicator/P2PReplicatorPane.svelte @@ -6,7 +6,7 @@ ConnectionStatus, type PeerStatus, } from "../../../lib/src/replication/trystero/P2PReplicatorPaneCommon"; - import type { P2PReplicator } from "../CmdP2PReplicator"; + import type { LiveSyncTrysteroReplicator } from "../../../lib/src/replication/trystero/LiveSyncTrysteroReplicator"; import PeerStatusRow from "../P2PReplicator/PeerStatusRow.svelte"; import { EVENT_LAYOUT_READY, eventHub } from "../../../common/events"; import { @@ -22,7 +22,7 @@ import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore"; interface Props { - cmdSync: P2PReplicator; + cmdSync: LiveSyncTrysteroReplicator; core: LiveSyncBaseCore; } @@ -237,10 +237,10 @@ await cmdSync.close(); } function startBroadcasting() { - void cmdSync.enableBroadcastCastings(); + void cmdSync.enableBroadcastChanges(); } function stopBroadcasting() { - void cmdSync.disableBroadcastCastings(); + void cmdSync.disableBroadcastChanges(); } const initialDialogStatusKey = `p2p-dialog-status`; diff --git a/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts b/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts index bd4c106..d96445f 100644 --- a/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts +++ b/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts @@ -1,19 +1,15 @@ import { Menu, WorkspaceLeaf } from "@/deps.ts"; import ReplicatorPaneComponent from "./P2PReplicatorPane.svelte"; -import type ObsidianLiveSyncPlugin from "../../../main.ts"; import { mount } from "svelte"; -import { SvelteItemView } from "../../../common/SvelteItemView.ts"; -import { eventHub } from "../../../common/events.ts"; +import { SvelteItemView } from "@/common/SvelteItemView.ts"; +import { eventHub } from "@/common/events.ts"; import { unique } from "octagonal-wheels/collection"; -import { LOG_LEVEL_NOTICE, REMOTE_P2P } from "../../../lib/src/common/types.ts"; -import { Logger } from "../../../lib/src/common/logger.ts"; -import { P2PReplicator } from "../CmdP2PReplicator.ts"; -import { - EVENT_P2P_PEER_SHOW_EXTRA_MENU, - type PeerStatus, -} from "../../../lib/src/replication/trystero/P2PReplicatorPaneCommon.ts"; +import { LOG_LEVEL_NOTICE, REMOTE_P2P } from "@lib/common/types.ts"; +import { Logger } from "@lib/common/logger.ts"; +import { EVENT_P2P_PEER_SHOW_EXTRA_MENU, type PeerStatus } from "@lib/replication/trystero/P2PReplicatorPaneCommon.ts"; import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts"; +import type { UseP2PReplicatorResult } from "@lib/replication/trystero/P2PReplicatorCore.ts"; export const VIEW_TYPE_P2P = "p2p-replicator"; function addToList(item: string, list: string) { @@ -35,8 +31,8 @@ function removeFromList(item: string, list: string) { } export class P2PReplicatorPaneView extends SvelteItemView { - // plugin: ObsidianLiveSyncPlugin; core: LiveSyncBaseCore; + private _p2pResult: UseP2PReplicatorResult; override icon = "waypoints"; title: string = ""; override navigation = false; @@ -45,11 +41,7 @@ export class P2PReplicatorPaneView extends SvelteItemView { return "waypoints"; } get replicator() { - const r = this.core.getAddOn(P2PReplicator.name); - if (!r || !r.liveSyncReplicator) { - throw new Error("Replicator not found"); - } - return r.liveSyncReplicator; + return this._p2pResult.replicator; } async replicateFrom(peer: PeerStatus) { await this.replicator.replicateFrom(peer.peerId); @@ -131,10 +123,10 @@ And you can also drop the local database to rebuild from the remote device.`, await this.core.services.setting.applyPartial(currentSetting, true); } m?: Menu; - constructor(leaf: WorkspaceLeaf, core: LiveSyncBaseCore, plugin: ObsidianLiveSyncPlugin) { + constructor(leaf: WorkspaceLeaf, core: LiveSyncBaseCore, p2pResult: UseP2PReplicatorResult) { super(leaf); - // this.plugin = plugin; this.core = core; + this._p2pResult = p2pResult; eventHub.onEvent(EVENT_P2P_PEER_SHOW_EXTRA_MENU, ({ peer, event }) => { if (this.m) { this.m.hide(); @@ -192,14 +184,10 @@ And you can also drop the local database to rebuild from the remote device.`, } } instantiateComponent(target: HTMLElement) { - const cmdSync = this.core.getAddOn(P2PReplicator.name); - if (!cmdSync) { - throw new Error("Replicator not found"); - } return mount(ReplicatorPaneComponent, { target: target, props: { - cmdSync: cmdSync, + cmdSync: this._p2pResult.replicator, core: this.core, }, }); diff --git a/src/features/P2PSync/P2PReplicator/PeerStatusRow.svelte b/src/features/P2PSync/P2PReplicator/PeerStatusRow.svelte index 2dd4397..144ec29 100644 --- a/src/features/P2PSync/P2PReplicator/PeerStatusRow.svelte +++ b/src/features/P2PSync/P2PReplicator/PeerStatusRow.svelte @@ -1,7 +1,7 @@ +

Redirecting to WebApp...

diff --git a/src/apps/webapp/main.ts b/src/apps/webapp/main.ts index a7ffcdd..5892bd9 100644 --- a/src/apps/webapp/main.ts +++ b/src/apps/webapp/main.ts @@ -13,9 +13,7 @@ import type { InjectableSettingService } from "@lib/services/implements/injectab import { useOfflineScanner } from "@lib/serviceFeatures/offlineScanner"; import { useRedFlagFeatures } from "@/serviceFeatures/redFlag"; import { useCheckRemoteSize } from "@lib/serviceFeatures/checkRemoteSize"; -import { SetupManager } from "@/modules/features/SetupManager"; // import { ModuleObsidianSettingsAsMarkdown } from "@/modules/features/ModuleObsidianSettingAsMarkdown"; -import { ModuleSetupObsidian } from "@/modules/features/ModuleSetupObsidian"; // import { ModuleObsidianMenu } from "@/modules/essentialObsidian/ModuleObsidianMenu"; const SETTINGS_DIR = ".livesync"; @@ -47,21 +45,18 @@ const DEFAULT_SETTINGS: Partial = { }; class LiveSyncWebApp { - private rootHandle: FileSystemDirectoryHandle | null = null; + private rootHandle: FileSystemDirectoryHandle; private core: LiveSyncBaseCore | null = null; private serviceHub: BrowserServiceHub | null = null; + constructor(rootHandle: FileSystemDirectoryHandle) { + this.rootHandle = rootHandle; + } + async initialize() { console.log("Self-hosted LiveSync WebApp"); console.log("Initializing..."); - // Request directory access - await this.requestDirectoryAccess(); - - if (!this.rootHandle) { - throw new Error("Failed to get directory access"); - } - console.log(`Vault directory: ${this.rootHandle.name}`); // Create service context and hub @@ -102,14 +97,12 @@ class LiveSyncWebApp { this.core = new LiveSyncBaseCore( this.serviceHub, (core, serviceHub) => { - return initialiseServiceModulesFSAPI(this.rootHandle!, core, serviceHub); + return initialiseServiceModulesFSAPI(this.rootHandle, core, serviceHub); }, (core) => [ // new ModuleObsidianEvents(this, core), // new ModuleObsidianSettingDialogue(this, core), // new ModuleObsidianMenu(core), - new ModuleSetupObsidian(core), - new SetupManager(core), // new ModuleObsidianSettingsAsMarkdown(core), // new ModuleLog(this, core), // new ModuleObsidianDocumentHistory(this, core), @@ -133,8 +126,6 @@ class LiveSyncWebApp { } private async saveSettingsToFile(data: ObsidianLiveSyncSettings): Promise { - if (!this.rootHandle) return; - try { // Create .livesync directory if it doesn't exist const livesyncDir = await this.rootHandle.getDirectoryHandle(SETTINGS_DIR, { create: true }); @@ -151,8 +142,6 @@ class LiveSyncWebApp { } private async loadSettingsFromFile(): Promise | null> { - if (!this.rootHandle) return null; - try { const livesyncDir = await this.rootHandle.getDirectoryHandle(SETTINGS_DIR); const fileHandle = await livesyncDir.getFileHandle(SETTINGS_FILE); @@ -165,90 +154,6 @@ class LiveSyncWebApp { } } - private async requestDirectoryAccess() { - try { - // Check if we have a cached directory handle - const cached = await this.loadCachedDirectoryHandle(); - if (cached) { - // Verify permission (cast to any for compatibility) - try { - const permission = await (cached as any).queryPermission({ mode: "readwrite" }); - if (permission === "granted") { - this.rootHandle = cached; - console.log("[Directory] Using cached directory handle"); - return; - } - } catch (e) { - // queryPermission might not be supported, try to use anyway - console.log("[Directory] Could not verify permission, requesting new access"); - } - } - - // Request new directory access - console.log("[Directory] Requesting directory access..."); - this.rootHandle = await (window as any).showDirectoryPicker({ - mode: "readwrite", - startIn: "documents", - }); - - // Save the handle for next time - await this.saveCachedDirectoryHandle(this.rootHandle); - console.log("[Directory] Directory access granted"); - } catch (error) { - console.error("[Directory] Failed to get directory access:", error); - throw error; - } - } - - private async saveCachedDirectoryHandle(handle: FileSystemDirectoryHandle) { - try { - // Use IndexedDB to store the directory handle - const db = await this.openHandleDB(); - const transaction = db.transaction(["handles"], "readwrite"); - const store = transaction.objectStore("handles"); - await new Promise((resolve, reject) => { - const request = store.put(handle, "rootHandle"); - request.onsuccess = resolve; - request.onerror = reject; - }); - db.close(); - } catch (error) { - console.error("[Directory] Failed to cache handle:", error); - } - } - - private async loadCachedDirectoryHandle(): Promise { - try { - const db = await this.openHandleDB(); - const transaction = db.transaction(["handles"], "readonly"); - const store = transaction.objectStore("handles"); - const handle = await new Promise((resolve, reject) => { - const request = store.get("rootHandle"); - request.onsuccess = () => resolve(request.result || null); - request.onerror = reject; - }); - db.close(); - return handle; - } catch (error) { - console.error("[Directory] Failed to load cached handle:", error); - return null; - } - } - - private async openHandleDB(): Promise { - return new Promise((resolve, reject) => { - const request = indexedDB.open("livesync-webapp-handles", 1); - request.onerror = () => reject(request.error); - request.onsuccess = () => resolve(request.result); - request.onupgradeneeded = (event) => { - const db = (event.target as IDBOpenDBRequest).result; - if (!db.objectStoreNames.contains("handles")) { - db.createObjectStore("handles"); - } - }; - }); - } - private async start() { if (!this.core) { throw new Error("Core not initialized"); @@ -333,21 +238,4 @@ class LiveSyncWebApp { } } -// Initialize on load -const app = new LiveSyncWebApp(); - -window.addEventListener("load", async () => { - try { - await app.initialize(); - } catch (error) { - console.error("Failed to initialize:", error); - } -}); - -// Handle page unload -window.addEventListener("beforeunload", () => { - void app.shutdown(); -}); - -// Export for debugging -(window as any).livesyncApp = app; +export { LiveSyncWebApp }; diff --git a/src/apps/webapp/vaultSelector.ts b/src/apps/webapp/vaultSelector.ts new file mode 100644 index 0000000..2935376 --- /dev/null +++ b/src/apps/webapp/vaultSelector.ts @@ -0,0 +1,192 @@ +const HANDLE_DB_NAME = "livesync-webapp-handles"; +const HANDLE_STORE_NAME = "handles"; +const LAST_USED_KEY = "meta:lastUsedVaultId"; +const VAULT_KEY_PREFIX = "vault:"; +const MAX_HISTORY_COUNT = 10; + +export type VaultHistoryItem = { + id: string; + name: string; + handle: FileSystemDirectoryHandle; + lastUsedAt: number; +}; + +type VaultHistoryValue = VaultHistoryItem; + +function makeVaultKey(id: string): string { + return `${VAULT_KEY_PREFIX}${id}`; +} + +function parseVaultId(key: string): string | null { + if (!key.startsWith(VAULT_KEY_PREFIX)) { + return null; + } + return key.slice(VAULT_KEY_PREFIX.length); +} + +function randomId(): string { + const n = Math.random().toString(36).slice(2, 10); + return `${Date.now()}-${n}`; +} + +async function hasReadWritePermission(handle: FileSystemDirectoryHandle, requestIfNeeded: boolean): Promise { + const h = handle as any; + if (typeof h.queryPermission === "function") { + const queried = await h.queryPermission({ mode: "readwrite" }); + if (queried === "granted") { + return true; + } + } + if (!requestIfNeeded) { + return false; + } + if (typeof h.requestPermission === "function") { + const requested = await h.requestPermission({ mode: "readwrite" }); + return requested === "granted"; + } + return true; +} + +export class VaultHistoryStore { + private async openHandleDB(): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(HANDLE_DB_NAME, 1); + request.onerror = () => reject(request.error); + request.onsuccess = () => resolve(request.result); + request.onupgradeneeded = (event) => { + const db = (event.target as IDBOpenDBRequest).result; + if (!db.objectStoreNames.contains(HANDLE_STORE_NAME)) { + db.createObjectStore(HANDLE_STORE_NAME); + } + }; + }); + } + + private async withStore( + mode: IDBTransactionMode, + task: (store: IDBObjectStore) => Promise + ): Promise { + const db = await this.openHandleDB(); + try { + const tx = db.transaction([HANDLE_STORE_NAME], mode); + const store = tx.objectStore(HANDLE_STORE_NAME); + return await task(store); + } finally { + db.close(); + } + } + + private async requestAsPromise(request: IDBRequest): Promise { + return new Promise((resolve, reject) => { + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); + } + + async getLastUsedVaultId(): Promise { + return this.withStore("readonly", async (store) => { + const value = await this.requestAsPromise(store.get(LAST_USED_KEY)); + return typeof value === "string" ? value : null; + }); + } + + async getVaultHistory(): Promise { + return this.withStore("readonly", async (store) => { + const keys = (await this.requestAsPromise(store.getAllKeys())) as IDBValidKey[]; + const values = (await this.requestAsPromise(store.getAll())) as unknown[]; + const items: VaultHistoryItem[] = []; + for (let i = 0; i < keys.length; i++) { + const key = String(keys[i]); + const id = parseVaultId(key); + const value = values[i] as Partial | undefined; + if (!id || !value || !value.handle || !value.name) { + continue; + } + items.push({ + id, + name: String(value.name), + handle: value.handle, + lastUsedAt: Number(value.lastUsedAt || 0), + }); + } + items.sort((a, b) => b.lastUsedAt - a.lastUsedAt); + return items; + }); + } + + async saveSelectedVault(handle: FileSystemDirectoryHandle): Promise { + const now = Date.now(); + const existing = await this.getVaultHistory(); + + let matched: VaultHistoryItem | null = null; + for (const item of existing) { + try { + if (await item.handle.isSameEntry(handle)) { + matched = item; + break; + } + } catch { + // Ignore handles that cannot be compared, keep scanning. + } + } + + const item: VaultHistoryItem = { + id: matched?.id ?? randomId(), + name: handle.name, + handle, + lastUsedAt: now, + }; + + await this.withStore("readwrite", async (store): Promise => { + await this.requestAsPromise(store.put(item, makeVaultKey(item.id))); + await this.requestAsPromise(store.put(item.id, LAST_USED_KEY)); + + const merged = [...existing.filter((v) => v.id !== item.id), item].sort((a, b) => b.lastUsedAt - a.lastUsedAt); + const stale = merged.slice(MAX_HISTORY_COUNT); + for (const old of stale) { + await this.requestAsPromise(store.delete(makeVaultKey(old.id))); + } + }); + + return item; + } + + async activateHistoryItem(item: VaultHistoryItem): Promise { + const granted = await hasReadWritePermission(item.handle, true); + if (!granted) { + throw new Error("Vault permissions were not granted"); + } + + const activated: VaultHistoryItem = { + ...item, + lastUsedAt: Date.now(), + }; + + await this.withStore("readwrite", async (store): Promise => { + await this.requestAsPromise(store.put(activated, makeVaultKey(activated.id))); + await this.requestAsPromise(store.put(activated.id, LAST_USED_KEY)); + }); + + return item.handle; + } + + async pickNewVault(): Promise { + const picker = (window as any).showDirectoryPicker; + if (typeof picker !== "function") { + throw new Error("FileSystem API showDirectoryPicker is not supported in this browser"); + } + + const handle = (await picker({ + mode: "readwrite", + startIn: "documents", + })) as FileSystemDirectoryHandle; + + const granted = await hasReadWritePermission(handle, true); + if (!granted) { + throw new Error("Vault permissions were not granted"); + } + + await this.saveSelectedVault(handle); + return handle; + } +} diff --git a/src/apps/webapp/vite.config.ts b/src/apps/webapp/vite.config.ts index ea99b14..5b42608 100644 --- a/src/apps/webapp/vite.config.ts +++ b/src/apps/webapp/vite.config.ts @@ -20,7 +20,11 @@ export default defineConfig({ rollupOptions: { input: { index: path.resolve(__dirname, "index.html"), + webapp: path.resolve(__dirname, "webapp.html"), }, + external:[ + "crypto" + ] }, }, define: { diff --git a/src/apps/webapp/webapp.html b/src/apps/webapp/webapp.html new file mode 100644 index 0000000..5692f73 --- /dev/null +++ b/src/apps/webapp/webapp.html @@ -0,0 +1,267 @@ + + + + + + Self-hosted LiveSync WebApp + + + +
+

Self-hosted LiveSync

+

Browser-based Self-hosted LiveSync using FileSystem API

+ +
Initialising...
+ +
+

Select Vault Folder

+

Open a vault you already used, or pick a new folder.

+ +
+

No saved vaults yet.

+ +
+ +
+

How to Use

+
    +
  • Select a vault folder and grant permission
  • +
  • Create .livesync/settings.json in your vault folder
  • +
  • Add your CouchDB connection details
  • +
  • Your files will be synced automatically
  • +
+
+ + +
+ + + + From 1f87a9fd3d8bf44fd5274ac5e2e5ef87cf11e35d Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Tue, 17 Mar 2026 19:58:12 +0900 Subject: [PATCH 093/339] port setupManager, setupProtocol to serviceFeature remove styles on webapp UI, and add stylesheet --- package.json | 2 + src/apps/webapp/bootstrap.ts | 4 +- src/apps/webapp/main.ts | 6 + src/apps/webapp/vaultSelector.ts | 9 +- src/apps/webapp/vite.config.ts | 4 +- src/apps/webapp/webapp.css | 369 ++++++++++++++++++ src/apps/webapp/webapp.html | 224 +---------- src/lib | 2 +- src/main.ts | 11 +- .../setupObsidian/setupManagerHandlers.ts | 34 ++ .../setupManagerHandlers.unit.spec.ts | 87 +++++ .../setupObsidian/setupProtocol.ts | 37 ++ .../setupObsidian/setupProtocol.unit.spec.ts | 131 +++++++ 13 files changed, 684 insertions(+), 236 deletions(-) create mode 100644 src/apps/webapp/webapp.css create mode 100644 src/serviceFeatures/setupObsidian/setupManagerHandlers.ts create mode 100644 src/serviceFeatures/setupObsidian/setupManagerHandlers.unit.spec.ts create mode 100644 src/serviceFeatures/setupObsidian/setupProtocol.ts create mode 100644 src/serviceFeatures/setupObsidian/setupProtocol.unit.spec.ts diff --git a/package.json b/package.json index 9734ed1..38bbbfd 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "@tsconfig/svelte": "^5.0.8", "@types/deno": "^2.5.0", "@types/diff-match-patch": "^1.0.36", + "@types/markdown-it": "^14.1.2", "@types/node": "^24.10.13", "@types/pouchdb": "^6.4.2", "@types/pouchdb-adapter-http": "^6.1.6", @@ -134,6 +135,7 @@ "diff-match-patch": "^1.0.5", "fflate": "^0.8.2", "idb": "^8.0.3", + "markdown-it": "^14.1.1", "minimatch": "^10.2.2", "node-datachannel": "^0.32.1", "octagonal-wheels": "^0.1.45", diff --git a/src/apps/webapp/bootstrap.ts b/src/apps/webapp/bootstrap.ts index f186128..2450285 100644 --- a/src/apps/webapp/bootstrap.ts +++ b/src/apps/webapp/bootstrap.ts @@ -42,7 +42,7 @@ async function renderHistoryList(): Promise { const [items, lastUsedId] = await Promise.all([historyStore.getVaultHistory(), historyStore.getLastUsedVaultId()]); listEl.innerHTML = ""; - emptyEl.style.display = items.length > 0 ? "none" : "block"; + emptyEl.classList.toggle("is-hidden", items.length > 0); for (const item of items) { const row = document.createElement("div"); @@ -82,7 +82,7 @@ async function startWithHandle(handle: FileSystemDirectoryHandle): Promise await app.initialize(); const selectorEl = getRequiredElement("vault-selector"); - selectorEl.style.display = "none"; + selectorEl.classList.add("is-hidden"); } async function startWithHistory(item: VaultHistoryItem): Promise { diff --git a/src/apps/webapp/main.ts b/src/apps/webapp/main.ts index 5892bd9..3cd4adf 100644 --- a/src/apps/webapp/main.ts +++ b/src/apps/webapp/main.ts @@ -13,6 +13,9 @@ import type { InjectableSettingService } from "@lib/services/implements/injectab import { useOfflineScanner } from "@lib/serviceFeatures/offlineScanner"; import { useRedFlagFeatures } from "@/serviceFeatures/redFlag"; import { useCheckRemoteSize } from "@lib/serviceFeatures/checkRemoteSize"; +import { useSetupQRCodeFeature } from "@lib/serviceFeatures/setupObsidian/qrCode"; +import { useSetupURIFeature } from "@lib/serviceFeatures/setupObsidian/setupUri"; +import { SetupManager } from "@/modules/features/SetupManager"; // import { ModuleObsidianSettingsAsMarkdown } from "@/modules/features/ModuleObsidianSettingAsMarkdown"; // import { ModuleObsidianMenu } from "@/modules/essentialObsidian/ModuleObsidianMenu"; @@ -112,12 +115,15 @@ class LiveSyncWebApp { // new ModuleReplicateTest(this, core), // new ModuleIntegratedTest(this, core), // new SetupManager(core), + new SetupManager(core), // this should be moved to core? ], () => [], // No add-ons (core) => { useOfflineScanner(core); useRedFlagFeatures(core); useCheckRemoteSize(core); + useSetupQRCodeFeature(core); + useSetupURIFeature(core); } ); diff --git a/src/apps/webapp/vaultSelector.ts b/src/apps/webapp/vaultSelector.ts index 2935376..79764a9 100644 --- a/src/apps/webapp/vaultSelector.ts +++ b/src/apps/webapp/vaultSelector.ts @@ -62,10 +62,7 @@ export class VaultHistoryStore { }); } - private async withStore( - mode: IDBTransactionMode, - task: (store: IDBObjectStore) => Promise - ): Promise { + private async withStore(mode: IDBTransactionMode, task: (store: IDBObjectStore) => Promise): Promise { const db = await this.openHandleDB(); try { const tx = db.transaction([HANDLE_STORE_NAME], mode); @@ -141,7 +138,9 @@ export class VaultHistoryStore { await this.requestAsPromise(store.put(item, makeVaultKey(item.id))); await this.requestAsPromise(store.put(item.id, LAST_USED_KEY)); - const merged = [...existing.filter((v) => v.id !== item.id), item].sort((a, b) => b.lastUsedAt - a.lastUsedAt); + const merged = [...existing.filter((v) => v.id !== item.id), item].sort( + (a, b) => b.lastUsedAt - a.lastUsedAt + ); const stale = merged.slice(MAX_HISTORY_COUNT); for (const old of stale) { await this.requestAsPromise(store.delete(makeVaultKey(old.id))); diff --git a/src/apps/webapp/vite.config.ts b/src/apps/webapp/vite.config.ts index 5b42608..48daae6 100644 --- a/src/apps/webapp/vite.config.ts +++ b/src/apps/webapp/vite.config.ts @@ -22,9 +22,7 @@ export default defineConfig({ index: path.resolve(__dirname, "index.html"), webapp: path.resolve(__dirname, "webapp.html"), }, - external:[ - "crypto" - ] + external: ["crypto"], }, }, define: { diff --git a/src/apps/webapp/webapp.css b/src/apps/webapp/webapp.css new file mode 100644 index 0000000..69bedda --- /dev/null +++ b/src/apps/webapp/webapp.css @@ -0,0 +1,369 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; +} + +.container { + background: white; + border-radius: 12px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + padding: 40px; + max-width: 700px; + width: 100%; +} + +h1 { + color: #333; + margin-bottom: 10px; + font-size: 28px; +} + +.subtitle { + color: #666; + margin-bottom: 24px; + font-size: 14px; +} + +#status { + padding: 15px; + border-radius: 8px; + margin-bottom: 20px; + font-size: 14px; + font-weight: 500; +} + +#status.error { + background: #fee; + color: #c33; + border: 1px solid #fcc; +} + +#status.warning { + background: #ffeaa7; + color: #d63031; + border: 1px solid #fdcb6e; +} + +#status.success { + background: #d4edda; + color: #155724; + border: 1px solid #c3e6cb; +} + +#status.info { + background: #d1ecf1; + color: #0c5460; + border: 1px solid #bee5eb; +} + +.vault-selector { + border: 1px solid #e6e9f2; + border-radius: 8px; + padding: 16px; + background: #fbfcff; + margin-bottom: 22px; +} + +.vault-selector h2 { + font-size: 18px; + margin-bottom: 8px; + color: #333; +} + +.vault-selector p { + color: #555; + font-size: 14px; + margin-bottom: 12px; +} + +.vault-list { + display: flex; + flex-direction: column; + gap: 10px; + margin-bottom: 12px; +} + +.vault-item { + border: 1px solid #d9deee; + border-radius: 8px; + padding: 10px 12px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + background: #fff; +} + +.vault-item-info { + min-width: 0; +} + +.vault-item-name { + font-weight: 600; + color: #1f2a44; + word-break: break-word; +} + +.vault-item-meta { + margin-top: 2px; + font-size: 12px; + color: #63708f; +} + +button { + border: none; + border-radius: 6px; + padding: 8px 12px; + background: #2f5ae5; + color: #fff; + cursor: pointer; + font-weight: 600; + white-space: nowrap; +} + +button:hover { + background: #1e4ad6; +} + +button:disabled { + cursor: not-allowed; + opacity: 0.6; +} + +.empty-note { + font-size: 13px; + color: #6c757d; + margin-bottom: 8px; +} + +.empty-note.is-hidden, +.vault-selector.is-hidden { + display: none; +} + +.info-section { + margin-top: 20px; + padding: 20px; + background: #f8f9fa; + border-radius: 8px; +} + +.info-section h2 { + font-size: 18px; + margin-bottom: 12px; + color: #333; +} + +.info-section ul { + list-style: none; + padding-left: 0; +} + +.info-section li { + padding: 7px 0; + color: #666; + font-size: 14px; +} + +.info-section li::before { + content: "•"; + color: #667eea; + font-weight: bold; + display: inline-block; + width: 1em; + margin-left: -1em; + padding-right: 0.5em; +} + +code { + background: #e9ecef; + padding: 2px 6px; + border-radius: 4px; + font-family: "Courier New", monospace; + font-size: 13px; +} + +.footer { + margin-top: 24px; + text-align: center; + color: #999; + font-size: 12px; +} + +.footer a { + color: #667eea; + text-decoration: none; +} + +.footer a:hover { + text-decoration: underline; +} + +body.livesync-log-visible { + min-height: 100vh; + padding-bottom: 42vh; +} + +#livesync-log-panel { + position: fixed; + left: 0; + right: 0; + bottom: 0; + height: 42vh; + z-index: 900; + display: flex; + flex-direction: column; + background: #0f172a; + border-top: 1px solid #334155; +} + +.livesync-log-header { + padding: 8px 12px; + font-size: 12px; + font-weight: 600; + color: #e2e8f0; + background: #111827; + border-bottom: 1px solid #334155; +} + +#livesync-log-viewport { + flex: 1; + overflow: auto; + padding: 8px 12px; + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; + font-size: 12px; + line-height: 1.4; + color: #e2e8f0; + white-space: pre-wrap; + word-break: break-word; +} + +.livesync-log-line { + margin-bottom: 2px; +} + +#livesync-command-bar { + position: fixed; + right: 16px; + bottom: 16px; + z-index: 1000; + display: flex; + flex-wrap: wrap; + gap: 8px; + max-width: 40vw; + padding: 10px; + border-radius: 10px; + background: rgba(255, 255, 255, 0.95); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); +} + +.livesync-command-button { + border: 1px solid #ddd; + border-radius: 8px; + padding: 6px 10px; + background: #fff; + color: #111827; + cursor: pointer; + font-size: 12px; + line-height: 1.2; + white-space: nowrap; + font-weight: 500; +} + +.livesync-command-button:hover:not(:disabled) { + background: #f3f4f6; +} + +.livesync-command-button.is-disabled { + opacity: 0.55; +} + +#livesync-window-root { + position: fixed; + top: 16px; + left: 16px; + right: 16px; + bottom: calc(42vh + 16px); + z-index: 850; + display: flex; + flex-direction: column; + border-radius: 10px; + background: rgba(255, 255, 255, 0.98); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18); + overflow: hidden; +} + +#livesync-window-tabs { + display: flex; + gap: 6px; + padding: 8px; + background: #f3f4f6; + border-bottom: 1px solid #e5e7eb; +} + +#livesync-window-body { + position: relative; + flex: 1; + overflow: auto; + padding: 10px; +} + +.livesync-window-tab { + border: 1px solid #d1d5db; + background: #fff; + color: #111827; + padding: 4px 8px; + border-radius: 6px; + cursor: pointer; + font-size: 12px; + font-weight: 500; +} + +.livesync-window-tab.is-active { + background: #e0e7ff; + border-color: #818cf8; +} + +.livesync-window-panel { + display: none; + width: 100%; + height: 100%; + overflow: auto; +} + +.livesync-window-panel.is-active { + display: block; +} + +@media (max-width: 600px) { + .container { + padding: 28px 18px; + } + + h1 { + font-size: 24px; + } + + .vault-item { + flex-direction: column; + align-items: stretch; + } + + #livesync-command-bar { + max-width: calc(100vw - 24px); + right: 12px; + left: 12px; + bottom: 12px; + } +} diff --git a/src/apps/webapp/webapp.html b/src/apps/webapp/webapp.html index 5692f73..7512066 100644 --- a/src/apps/webapp/webapp.html +++ b/src/apps/webapp/webapp.html @@ -4,229 +4,7 @@ Self-hosted LiveSync WebApp - +
diff --git a/src/lib b/src/lib index 9145013..94e44e8 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 9145013efa054f6e6f388bff9f405ad42eb18b92 +Subproject commit 94e44e8a03b4d079f874c09b9ec60e43b7d35c58 diff --git a/src/main.ts b/src/main.ts index 78df81e..b1f9374 100644 --- a/src/main.ts +++ b/src/main.ts @@ -29,7 +29,6 @@ import type { ServiceModules } from "./types.ts"; import { setNoticeClass } from "@lib/mock_and_interop/wrapper.ts"; import type { ObsidianServiceContext } from "./lib/src/services/implements/obsidian/ObsidianServiceContext.ts"; import { LiveSyncBaseCore } from "./LiveSyncBaseCore.ts"; -import { ModuleSetupObsidian } from "./modules/features/ModuleSetupObsidian.ts"; import { ModuleObsidianMenu } from "./modules/essentialObsidian/ModuleObsidianMenu.ts"; import { ModuleObsidianSettingsAsMarkdown } from "./modules/features/ModuleObsidianSettingAsMarkdown.ts"; import { SetupManager } from "./modules/features/SetupManager.ts"; @@ -38,6 +37,10 @@ import { enableI18nFeature } from "./serviceFeatures/onLayoutReady/enablei18n.ts import { useOfflineScanner } from "./lib/src/serviceFeatures/offlineScanner.ts"; import { useCheckRemoteSize } from "./lib/src/serviceFeatures/checkRemoteSize.ts"; import { useRedFlagFeatures } from "./serviceFeatures/redFlag.ts"; +import { useSetupProtocolFeature } from "./serviceFeatures/setupObsidian/setupProtocol.ts"; +import { useSetupQRCodeFeature } from "@lib/serviceFeatures/setupObsidian/qrCode"; +import { useSetupURIFeature } from "@lib/serviceFeatures/setupObsidian/setupUri"; +import { useSetupManagerHandlersFeature } from "./serviceFeatures/setupObsidian/setupManagerHandlers.ts"; export type LiveSyncCore = LiveSyncBaseCore; export default class ObsidianLiveSyncPlugin extends Plugin { core: LiveSyncCore; @@ -147,7 +150,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin { new ModuleObsidianEvents(this, core), new ModuleObsidianSettingDialogue(this, core), new ModuleObsidianMenu(core), - new ModuleSetupObsidian(core), new ModuleObsidianSettingsAsMarkdown(core), new ModuleLog(this, core), new ModuleObsidianDocumentHistory(this, core), @@ -174,6 +176,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin { const featuresInitialiser = enableI18nFeature; const curriedFeature = () => featuresInitialiser(core); core.services.appLifecycle.onLayoutReady.addHandler(curriedFeature); + const setupManager = core.getModule(SetupManager); + useSetupProtocolFeature(core, setupManager); + useSetupQRCodeFeature(core); + useSetupURIFeature(core); + useSetupManagerHandlersFeature(core, setupManager); useOfflineScanner(core); useRedFlagFeatures(core); useCheckRemoteSize(core); diff --git a/src/serviceFeatures/setupObsidian/setupManagerHandlers.ts b/src/serviceFeatures/setupObsidian/setupManagerHandlers.ts new file mode 100644 index 0000000..4eb9e42 --- /dev/null +++ b/src/serviceFeatures/setupObsidian/setupManagerHandlers.ts @@ -0,0 +1,34 @@ +import { type SetupManager, UserMode } from "@/modules/features/SetupManager"; +import type { SetupFeatureHost } from "@lib/serviceFeatures/setupObsidian/types"; +import { EVENT_REQUEST_OPEN_P2P_SETTINGS, EVENT_REQUEST_OPEN_SETUP_URI } from "@lib/events/coreEvents"; +import { eventHub } from "@lib/hub/hub"; +import { fireAndForget } from "@lib/common/utils"; +import type { NecessaryServices } from "@lib/interfaces/ServiceModule"; + +export async function openSetupURI(setupManager: SetupManager) { + await setupManager.onUseSetupURI(UserMode.Unknown); +} + +export async function openP2PSettings(host: SetupFeatureHost, setupManager: SetupManager) { + return await setupManager.onP2PManualSetup(UserMode.Update, host.services.setting.currentSettings(), false); +} + +export function useSetupManagerHandlersFeature( + host: NecessaryServices<"API" | "UI" | "setting" | "appLifecycle", never>, + setupManager: SetupManager +) { + host.services.appLifecycle.onLoaded.addHandler(() => { + host.services.API.addCommand({ + id: "livesync-opensetupuri", + name: "Use the copied setup URI (Formerly Open setup URI)", + callback: () => fireAndForget(openSetupURI(setupManager)), + }); + + eventHub.onEvent(EVENT_REQUEST_OPEN_SETUP_URI, () => fireAndForget(() => openSetupURI(setupManager))); + eventHub.onEvent(EVENT_REQUEST_OPEN_P2P_SETTINGS, () => + fireAndForget(() => openP2PSettings(host, setupManager)) + ); + + return Promise.resolve(true); + }); +} diff --git a/src/serviceFeatures/setupObsidian/setupManagerHandlers.unit.spec.ts b/src/serviceFeatures/setupObsidian/setupManagerHandlers.unit.spec.ts new file mode 100644 index 0000000..6611194 --- /dev/null +++ b/src/serviceFeatures/setupObsidian/setupManagerHandlers.unit.spec.ts @@ -0,0 +1,87 @@ +import { describe, expect, it, vi, afterEach } from "vitest"; +import { eventHub } from "@lib/hub/hub"; +import { EVENT_REQUEST_OPEN_P2P_SETTINGS, EVENT_REQUEST_OPEN_SETUP_URI } from "@lib/events/coreEvents"; +import { openP2PSettings, openSetupURI, useSetupManagerHandlersFeature } from "./setupManagerHandlers"; + +vi.mock("@/modules/features/SetupManager", () => { + return { + UserMode: { + Unknown: "unknown", + Update: "unknown", + }, + }; +}); + +describe("setupObsidian/setupManagerHandlers", () => { + afterEach(() => { + vi.restoreAllMocks(); + vi.clearAllMocks(); + }); + + it("openSetupURI should delegate to SetupManager.onUseSetupURI", async () => { + const setupManager = { + onUseSetupURI: vi.fn(async () => true), + } as any; + + await openSetupURI(setupManager); + expect(setupManager.onUseSetupURI).toHaveBeenCalledWith("unknown"); + }); + + it("openP2PSettings should delegate to SetupManager.onP2PManualSetup", async () => { + const settings = { x: 1 }; + const host = { + services: { + setting: { + currentSettings: vi.fn(() => settings), + }, + }, + } as any; + const setupManager = { + onP2PManualSetup: vi.fn(async () => true), + } as any; + + await openP2PSettings(host, setupManager); + expect(setupManager.onP2PManualSetup).toHaveBeenCalledWith("unknown", settings, false); + }); + + it("useSetupManagerHandlersFeature should register onLoaded handler that wires command and events", async () => { + const addHandler = vi.fn(); + const addCommand = vi.fn(); + const onEventSpy = vi.spyOn(eventHub, "onEvent"); + + const host = { + services: { + API: { + addCommand, + }, + appLifecycle: { + onLoaded: { + addHandler, + }, + }, + setting: { + currentSettings: vi.fn(() => ({ x: 1 })), + }, + }, + } as any; + const setupManager = { + onUseSetupURI: vi.fn(async () => true), + onP2PManualSetup: vi.fn(async () => true), + } as any; + + useSetupManagerHandlersFeature(host, setupManager); + expect(addHandler).toHaveBeenCalledTimes(1); + + const loadedHandler = addHandler.mock.calls[0][0] as () => Promise; + await loadedHandler(); + + expect(addCommand).toHaveBeenCalledWith( + expect.objectContaining({ + id: "livesync-opensetupuri", + name: "Use the copied setup URI (Formerly Open setup URI)", + }) + ); + expect(onEventSpy).toHaveBeenCalledWith(EVENT_REQUEST_OPEN_SETUP_URI, expect.any(Function)); + expect(onEventSpy).toHaveBeenCalledWith(EVENT_REQUEST_OPEN_P2P_SETTINGS, expect.any(Function)); + }); +}); diff --git a/src/serviceFeatures/setupObsidian/setupProtocol.ts b/src/serviceFeatures/setupObsidian/setupProtocol.ts new file mode 100644 index 0000000..5310fbf --- /dev/null +++ b/src/serviceFeatures/setupObsidian/setupProtocol.ts @@ -0,0 +1,37 @@ +import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "@lib/common/types"; +import type { LogFunction } from "@lib/services/lib/logUtils"; +import { createInstanceLogFunction } from "@lib/services/lib/logUtils"; +import type { SetupFeatureHost } from "@lib/serviceFeatures/setupObsidian/types"; +import { configURIBase } from "@/common/types"; +import type { NecessaryServices } from "@lib/interfaces/ServiceModule"; +import { type SetupManager, UserMode } from "@/modules/features/SetupManager"; + +async function handleSetupProtocol(setupManager: SetupManager, conf: Record) { + if (conf.settings) { + await setupManager.onUseSetupURI(UserMode.Unknown, `${configURIBase}${encodeURIComponent(conf.settings)}`); + } else if (conf.settingsQR) { + await setupManager.decodeQR(conf.settingsQR); + } +} + +export function registerSetupProtocolHandler(host: SetupFeatureHost, log: LogFunction, setupManager: SetupManager) { + try { + host.services.API.registerProtocolHandler("setuplivesync", async (conf) => { + await handleSetupProtocol(setupManager, conf); + }); + } catch (e) { + log("Failed to register protocol handler. This feature may not work in some environments.", LOG_LEVEL_NOTICE); + log(e, LOG_LEVEL_VERBOSE); + } +} + +export function useSetupProtocolFeature( + host: NecessaryServices<"API" | "UI" | "setting" | "appLifecycle", never>, + setupManager: SetupManager +) { + const log = createInstanceLogFunction("SF:SetupProtocol", host.services.API); + host.services.appLifecycle.onLoaded.addHandler(() => { + registerSetupProtocolHandler(host, log, setupManager); + return Promise.resolve(true); + }); +} diff --git a/src/serviceFeatures/setupObsidian/setupProtocol.unit.spec.ts b/src/serviceFeatures/setupObsidian/setupProtocol.unit.spec.ts new file mode 100644 index 0000000..03d56e5 --- /dev/null +++ b/src/serviceFeatures/setupObsidian/setupProtocol.unit.spec.ts @@ -0,0 +1,131 @@ +import { describe, expect, it, vi, afterEach } from "vitest"; +import { registerSetupProtocolHandler, useSetupProtocolFeature } from "./setupProtocol"; + +vi.mock("@/common/types", () => { + return { + configURIBase: "mock-config://", + }; +}); + +vi.mock("@/modules/features/SetupManager", () => { + return { + UserMode: { + Unknown: "unknown", + Update: "unknown", + }, + }; +}); + +describe("setupObsidian/setupProtocol", () => { + afterEach(() => { + vi.restoreAllMocks(); + vi.clearAllMocks(); + }); + + it("registerSetupProtocolHandler should route settings payload to onUseSetupURI", async () => { + let protocolHandler: ((params: Record) => Promise) | undefined; + const host = { + services: { + API: { + registerProtocolHandler: vi.fn( + (_action: string, handler: (params: Record) => Promise) => { + protocolHandler = handler; + } + ), + }, + }, + } as any; + const log = vi.fn(); + const setupManager = { + onUseSetupURI: vi.fn(async () => true), + decodeQR: vi.fn(async () => true), + } as any; + + registerSetupProtocolHandler(host, log, setupManager); + expect(host.services.API.registerProtocolHandler).toHaveBeenCalledWith("setuplivesync", expect.any(Function)); + + await protocolHandler!({ settings: "a b" }); + expect(setupManager.onUseSetupURI).toHaveBeenCalledWith( + "unknown", + `mock-config://${encodeURIComponent("a b")}` + ); + expect(setupManager.decodeQR).not.toHaveBeenCalled(); + }); + + it("registerSetupProtocolHandler should route settingsQR payload to decodeQR", async () => { + let protocolHandler: ((params: Record) => Promise) | undefined; + const host = { + services: { + API: { + registerProtocolHandler: vi.fn( + (_action: string, handler: (params: Record) => Promise) => { + protocolHandler = handler; + } + ), + }, + }, + } as any; + const log = vi.fn(); + const setupManager = { + onUseSetupURI: vi.fn(async () => true), + decodeQR: vi.fn(async () => true), + } as any; + + registerSetupProtocolHandler(host, log, setupManager); + await protocolHandler!({ settingsQR: "qr-data" }); + + expect(setupManager.decodeQR).toHaveBeenCalledWith("qr-data"); + expect(setupManager.onUseSetupURI).not.toHaveBeenCalled(); + }); + + it("registerSetupProtocolHandler should log and continue when registration throws", () => { + const host = { + services: { + API: { + registerProtocolHandler: vi.fn(() => { + throw new Error("register failed"); + }), + }, + }, + } as any; + const log = vi.fn(); + const setupManager = { + onUseSetupURI: vi.fn(), + decodeQR: vi.fn(), + } as any; + + registerSetupProtocolHandler(host, log, setupManager); + + expect(log).toHaveBeenCalledTimes(2); + }); + + it("useSetupProtocolFeature should register onLoaded handler", async () => { + const addHandler = vi.fn(); + const registerProtocolHandler = vi.fn(); + const host = { + services: { + API: { + addLog: vi.fn(), + registerProtocolHandler, + }, + appLifecycle: { + onLoaded: { + addHandler, + }, + }, + }, + } as any; + const setupManager = { + onUseSetupURI: vi.fn(), + decodeQR: vi.fn(), + } as any; + + useSetupProtocolFeature(host, setupManager); + expect(addHandler).toHaveBeenCalledTimes(1); + + const loadedHandler = addHandler.mock.calls[0][0] as () => Promise; + await loadedHandler(); + + expect(registerProtocolHandler).toHaveBeenCalledWith("setuplivesync", expect.any(Function)); + }); +}); From 0717093d816df27d9080d79e5d79fc49f5c3c3ef Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Tue, 17 Mar 2026 20:09:28 +0900 Subject: [PATCH 094/339] update for npm ci --- package-lock.json | 140 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 134 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index c2d070f..8c08f58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "diff-match-patch": "^1.0.5", "fflate": "^0.8.2", "idb": "^8.0.3", + "markdown-it": "^14.1.1", "minimatch": "^10.2.2", "node-datachannel": "^0.32.1", "octagonal-wheels": "^0.1.45", @@ -36,6 +37,7 @@ "@tsconfig/svelte": "^5.0.8", "@types/deno": "^2.5.0", "@types/diff-match-patch": "^1.0.36", + "@types/markdown-it": "^14.1.2", "@types/node": "^24.10.13", "@types/pouchdb": "^6.4.2", "@types/pouchdb-adapter-http": "^6.1.6", @@ -5086,6 +5088,13 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/lodash": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", @@ -5101,6 +5110,24 @@ "@types/lodash": "*" } }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", @@ -6586,7 +6613,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/aria-query": { @@ -8135,7 +8161,6 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -11459,6 +11484,15 @@ "url": "https://github.com/sponsors/antonk52" } }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/locate-app": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.5.0.tgz", @@ -11637,6 +11671,23 @@ "semver": "bin/semver.js" } }, + "node_modules/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -11647,6 +11698,12 @@ "node": ">= 0.4" } }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, "node_modules/memdown": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/memdown/-/memdown-1.4.1.tgz", @@ -13429,6 +13486,15 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/qrcode-generator": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.5.2.tgz", @@ -15680,6 +15746,12 @@ "node": ">=14.17" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, "node_modules/uint8-varint": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-2.0.4.tgz", @@ -20527,6 +20599,12 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true + }, "@types/lodash": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", @@ -20540,6 +20618,22 @@ "@types/lodash": "*" } }, + "@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "requires": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true + }, "@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", @@ -21613,8 +21707,7 @@ "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "aria-query": { "version": "5.3.2", @@ -22633,8 +22726,7 @@ "entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" }, "errno": { "version": "0.1.8", @@ -24921,6 +25013,14 @@ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true }, + "linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "requires": { + "uc.micro": "^2.0.0" + } + }, "locate-app": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.5.0.tgz", @@ -25054,12 +25154,30 @@ } } }, + "markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "requires": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + } + }, "math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true }, + "mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" + }, "memdown": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/memdown/-/memdown-1.4.1.tgz", @@ -26294,6 +26412,11 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, + "punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==" + }, "qrcode-generator": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.5.2.tgz", @@ -27660,6 +27783,11 @@ "dev": true, "peer": true }, + "uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" + }, "uint8-varint": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-2.0.4.tgz", From 075d260fddfc754ff1e581c1e27e031cba0df4c1 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 18 Mar 2026 11:46:52 +0100 Subject: [PATCH 095/339] Fixed: - Fixed the corrupted display of the help message. - Remove some unnecessary codes. --- src/apps/cli/.gitignore | 3 ++- src/apps/cli/main.ts | 34 ++++++++++------------------------ src/apps/cli/package.json | 1 - 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/src/apps/cli/.gitignore b/src/apps/cli/.gitignore index 630bad4..9ab466a 100644 --- a/src/apps/cli/.gitignore +++ b/src/apps/cli/.gitignore @@ -1,4 +1,5 @@ .livesync test/* !test/*.sh -node_modules \ No newline at end of file +node_modules +.*.json \ No newline at end of file diff --git a/src/apps/cli/main.ts b/src/apps/cli/main.ts index 6316181..442d9c9 100644 --- a/src/apps/cli/main.ts +++ b/src/apps/cli/main.ts @@ -45,23 +45,6 @@ import { stripAllPrefixes } from "@lib/string_and_binary/path"; const SETTINGS_FILE = ".livesync/settings.json"; defaultLoggerEnv.minLogLevel = LOG_LEVEL_DEBUG; -// DI the log again. -// const recentLogEntries = reactiveSource([]); -// const globalLogFunction = (message: any, level?: number, key?: string) => { -// const messageX = -// message instanceof Error -// ? new LiveSyncError("[Error Logged]: " + message.message, { cause: message }) -// : message; -// const entry = { message: messageX, level, key } as LogEntry; -// recentLogEntries.value = [...recentLogEntries.value, entry]; -// }; - -// setGlobalLogFunction((msg, level) => { -// console.error(`[${level}] ${typeof msg === "string" ? msg : JSON.stringify(msg)}`); -// if (msg instanceof Error) { -// console.error(msg); -// } -// }); function printHelp(): void { console.log(` Self-hosted LiveSync CLI @@ -78,8 +61,8 @@ Commands: p2p-sync Sync with the specified peer-id or peer-name p2p-host Start P2P host mode and wait until interrupted - push Push local file into local database path - pull Pull file from local database into local file + push Push local file into local database path + pull Pull file from local database into local file pull-rev Pull file at specific revision into local file setup Apply setup URI to settings file put Read UTF-8 content from stdin and write to local database path @@ -90,12 +73,12 @@ Commands: rm Mark a file as deleted in local database resolve Resolve conflicts by keeping and deleting others Examples: - livesync-cli ./my-database sync + 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 --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 @@ -106,7 +89,7 @@ Examples: 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 --verbose `); } @@ -353,7 +336,10 @@ export async function main() { (core: LiveSyncBaseCore, serviceHub: InjectableServiceHub) => { return initialiseServiceModulesCLI(vaultPath, core, serviceHub); }, - (core) => [new ModuleReplicatorP2P(core)], // Register P2P replicator for CLI (useP2PReplicator is not used here) + (core) => [ + // No modules need to be registered for P2P replication in CLI. Directly using Replicators in p2p.ts + // new ModuleReplicatorP2P(core), + ], () => [], // No add-ons (core) => { // Add target filter to prevent internal files are handled diff --git a/src/apps/cli/package.json b/src/apps/cli/package.json index 30029bb..5701ae1 100644 --- a/src/apps/cli/package.json +++ b/src/apps/cli/package.json @@ -22,7 +22,6 @@ "test:e2e:p2p-upload-download-repro": "bash test/test-p2p-upload-download-repro-linux.sh", "test:e2e:p2p-host": "bash test/test-p2p-host-linux.sh", "test:e2e:p2p-sync": "bash test/test-p2p-sync-linux.sh", - "test:e2e:p2p-peers:local-relay": "bash test/test-p2p-peers-local-relay.sh", "test:e2e:mirror": "bash test/test-mirror-linux.sh", "pretest:e2e:all": "npm run build", "test:e2e:all": " export RUN_BUILD=0 && npm run test:e2e:setup-put-cat && npm run test:e2e:push-pull && npm run test:e2e:sync-two-local && npm run test:e2e:p2p && npm run test:e2e:mirror && npm run test:e2e:two-vaults && npm run test:e2e:p2p" From 602fcef949038ccad0f47d477e8ba9b9f05ec9a9 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 18 Mar 2026 11:48:31 +0100 Subject: [PATCH 096/339] - Fixed the issue where the detail level was not being applied in the log pane. - Pop-ups are now shown. - Add coverage for test. - Pop-ups are now shown in the web app as well. --- src/apps/webapp/.gitignore | 1 + src/apps/webapp/main.ts | 25 +++++++++++++++++------ src/apps/webapp/vite.config.ts | 36 +++++++++++++++++++++++++++++++++- src/apps/webapp/webapp.css | 33 +++++++++++++++++++++++++++++++ src/apps/webapp/webapp.html | 6 +++--- 5 files changed, 91 insertions(+), 10 deletions(-) diff --git a/src/apps/webapp/.gitignore b/src/apps/webapp/.gitignore index 7ac8ea1..9fbfdf2 100644 --- a/src/apps/webapp/.gitignore +++ b/src/apps/webapp/.gitignore @@ -2,3 +2,4 @@ node_modules dist .DS_Store *.log +.nyc_output \ No newline at end of file diff --git a/src/apps/webapp/main.ts b/src/apps/webapp/main.ts index 3cd4adf..67db1bd 100644 --- a/src/apps/webapp/main.ts +++ b/src/apps/webapp/main.ts @@ -13,11 +13,11 @@ import type { InjectableSettingService } from "@lib/services/implements/injectab import { useOfflineScanner } from "@lib/serviceFeatures/offlineScanner"; import { useRedFlagFeatures } from "@/serviceFeatures/redFlag"; import { useCheckRemoteSize } from "@lib/serviceFeatures/checkRemoteSize"; -import { useSetupQRCodeFeature } from "@lib/serviceFeatures/setupObsidian/qrCode"; import { useSetupURIFeature } from "@lib/serviceFeatures/setupObsidian/setupUri"; import { SetupManager } from "@/modules/features/SetupManager"; -// import { ModuleObsidianSettingsAsMarkdown } from "@/modules/features/ModuleObsidianSettingAsMarkdown"; -// import { ModuleObsidianMenu } from "@/modules/essentialObsidian/ModuleObsidianMenu"; +import { useSetupManagerHandlersFeature } from "@/serviceFeatures/setupObsidian/setupManagerHandlers"; +import { useP2PReplicatorCommands } from "@/lib/src/replication/trystero/useP2PReplicatorCommands"; +import { useP2PReplicatorFeature } from "@/lib/src/replication/trystero/useP2PReplicatorFeature"; const SETTINGS_DIR = ".livesync"; const SETTINGS_FILE = "settings.json"; @@ -96,6 +96,16 @@ class LiveSyncWebApp { return DEFAULT_SETTINGS as ObsidianLiveSyncSettings; }); + // App lifecycle handlers + this.serviceHub.appLifecycle.scheduleRestart.setHandler(async () => { + console.log("[AppLifecycle] Restart requested"); + await this.shutdown(); + await this.initialize(); + setTimeout(() => { + window.location.reload(); + }, 1000); + }); + // Create LiveSync core this.core = new LiveSyncBaseCore( this.serviceHub, @@ -114,15 +124,18 @@ class LiveSyncWebApp { // new ModuleDev(this, core), // new ModuleReplicateTest(this, core), // new ModuleIntegratedTest(this, core), - // new SetupManager(core), - new SetupManager(core), // this should be moved to core? + // new ModuleReplicatorP2P(core), // Register P2P replicator for CLI (useP2PReplicator is not used here) + new SetupManager(core), ], () => [], // No add-ons (core) => { useOfflineScanner(core); useRedFlagFeatures(core); useCheckRemoteSize(core); - useSetupQRCodeFeature(core); + const replicator = useP2PReplicatorFeature(core); + useP2PReplicatorCommands(core, replicator); + const setupManager = core.getModule(SetupManager); + useSetupManagerHandlersFeature(core, setupManager); useSetupURIFeature(core); } ); diff --git a/src/apps/webapp/vite.config.ts b/src/apps/webapp/vite.config.ts index 48daae6..f58d3db 100644 --- a/src/apps/webapp/vite.config.ts +++ b/src/apps/webapp/vite.config.ts @@ -1,16 +1,45 @@ import { defineConfig } from "vite"; import { svelte } from "@sveltejs/vite-plugin-svelte"; +import istanbul from "vite-plugin-istanbul"; import path from "node:path"; import { readFileSync } from "node:fs"; const packageJson = JSON.parse(readFileSync("../../../package.json", "utf-8")); const manifestJson = JSON.parse(readFileSync("../../../manifest.json", "utf-8")); +const enableCoverage = process.env.PW_COVERAGE === "1"; +const repoRoot = path.resolve(__dirname, "../../.."); // https://vite.dev/config/ export default defineConfig({ - plugins: [svelte()], + plugins: [ + svelte(), + ...(enableCoverage + ? [ + istanbul({ + cwd: repoRoot, + include: ["src/**/*.ts", "src/**/*.svelte"], + exclude: [ + "node_modules", + "dist", + "test", + "coverage", + "src/apps/webapp/test/**", + "playwright.config.ts", + "vite.config.ts", + "**/*.spec.ts", + "**/*.test.ts", + ], + extension: [".js", ".ts", ".svelte"], + requireEnv: false, + cypress: false, + checkProd: false, + }), + ] + : []), + ], resolve: { alias: { "@": path.resolve(__dirname, "../../"), "@lib": path.resolve(__dirname, "../../lib/src"), + obsidian: path.resolve(__dirname, "../../../test/harness/obsidian-mock.ts"), }, }, base: "./", @@ -18,9 +47,12 @@ export default defineConfig({ outDir: "dist", emptyOutDir: true, rollupOptions: { + // test.html is used by the Playwright dev-server; include it here + // so the production build doesn't emit warnings about unused inputs. input: { index: path.resolve(__dirname, "index.html"), webapp: path.resolve(__dirname, "webapp.html"), + test: path.resolve(__dirname, "test.html"), }, external: ["crypto"], }, @@ -28,6 +60,8 @@ export default defineConfig({ define: { MANIFEST_VERSION: JSON.stringify(process.env.MANIFEST_VERSION || manifestJson.version || "0.0.0"), PACKAGE_VERSION: JSON.stringify(process.env.PACKAGE_VERSION || packageJson.version || "0.0.0"), + global: "globalThis", + hostPlatform: JSON.stringify(process.platform || "linux"), }, server: { port: 3000, diff --git a/src/apps/webapp/webapp.css b/src/apps/webapp/webapp.css index 69bedda..1867363 100644 --- a/src/apps/webapp/webapp.css +++ b/src/apps/webapp/webapp.css @@ -4,6 +4,18 @@ box-sizing: border-box; } +:root { + --background-primary: #ffffff; + --background-primary-alt: #667eea; + --background-secondary: #f0f0f0; + --background-secondary-alt: #e0e0e0; + --background-modifier-border: #d0d0d0; + --text-normal: #333333; + --text-warning: #d9534f; + --text-accent: #5bc0de; + --text-on-accent: #ffffff; +} + body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); @@ -134,6 +146,7 @@ button { button:hover { background: #1e4ad6; + opacity: 0.7; } button:disabled { @@ -367,3 +380,23 @@ body.livesync-log-visible { bottom: 12px; } } + +popup { + position: fixed; + min-width: 80vw; + max-width: 90vw; + min-height: 40vh; + max-height: 80vh; + background: rgba(255, 255, 255, 0.8); + padding: 1em; + border-radius: 6px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2); + z-index: 10000; + overflow-y: auto; + display: flex; + align-items: center; + justify-content: center; + backdrop-filter: blur(15px); + border-radius: 10px; + z-index: 10; +} \ No newline at end of file diff --git a/src/apps/webapp/webapp.html b/src/apps/webapp/webapp.html index 7512066..72ba158 100644 --- a/src/apps/webapp/webapp.html +++ b/src/apps/webapp/webapp.html @@ -8,7 +8,7 @@
-

Self-hosted LiveSync

+

Self-hosted LiveSync on Web

Browser-based Self-hosted LiveSync using FileSystem API

Initialising...
@@ -27,8 +27,8 @@
  • Select a vault folder and grant permission
  • Create .livesync/settings.json in your vault folder
  • -
  • Add your CouchDB connection details
  • -
  • Your files will be synced automatically
  • +
  • Or use Setup-URI to apply settings
  • +
  • Your files will be synced after "replicate now"
From 3963f7c971143cd95121467722b724eba7ea41fd Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 18 Mar 2026 11:49:41 +0100 Subject: [PATCH 097/339] Refactored: P2P replicator has been refactored to be a little roust and easier to understand. --- .../P2PReplicator/P2PReplicatorPaneView.ts | 6 +- src/lib | 2 +- src/main.ts | 28 +++---- src/serviceFeatures/useP2PReplicatorUI.ts | 76 +++++++++++++++++++ 4 files changed, 94 insertions(+), 18 deletions(-) create mode 100644 src/serviceFeatures/useP2PReplicatorUI.ts diff --git a/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts b/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts index d96445f..5ab5031 100644 --- a/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts +++ b/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts @@ -9,7 +9,7 @@ import { LOG_LEVEL_NOTICE, REMOTE_P2P } from "@lib/common/types.ts"; import { Logger } from "@lib/common/logger.ts"; import { EVENT_P2P_PEER_SHOW_EXTRA_MENU, type PeerStatus } from "@lib/replication/trystero/P2PReplicatorPaneCommon.ts"; import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts"; -import type { UseP2PReplicatorResult } from "@lib/replication/trystero/P2PReplicatorCore.ts"; +import type { P2PPaneParams } from "@/lib/src/replication/trystero/UseP2PReplicatorResult"; export const VIEW_TYPE_P2P = "p2p-replicator"; function addToList(item: string, list: string) { @@ -32,7 +32,7 @@ function removeFromList(item: string, list: string) { export class P2PReplicatorPaneView extends SvelteItemView { core: LiveSyncBaseCore; - private _p2pResult: UseP2PReplicatorResult; + private _p2pResult: P2PPaneParams; override icon = "waypoints"; title: string = ""; override navigation = false; @@ -123,7 +123,7 @@ And you can also drop the local database to rebuild from the remote device.`, await this.core.services.setting.applyPartial(currentSetting, true); } m?: Menu; - constructor(leaf: WorkspaceLeaf, core: LiveSyncBaseCore, p2pResult: UseP2PReplicatorResult) { + constructor(leaf: WorkspaceLeaf, core: LiveSyncBaseCore, p2pResult: P2PPaneParams) { super(leaf); this.core = core; this._p2pResult = p2pResult; diff --git a/src/lib b/src/lib index 94e44e8..202038d 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 94e44e8a03b4d079f874c09b9ec60e43b7d35c58 +Subproject commit 202038d19eeb19df76b6804bcd0ea784896ce257 diff --git a/src/main.ts b/src/main.ts index b1f9374..ab89820 100644 --- a/src/main.ts +++ b/src/main.ts @@ -14,9 +14,7 @@ import { ModuleObsidianGlobalHistory } from "./modules/features/ModuleGlobalHist import { ModuleIntegratedTest } from "./modules/extras/ModuleIntegratedTest.ts"; import { ModuleReplicateTest } from "./modules/extras/ModuleReplicateTest.ts"; import { LocalDatabaseMaintenance } from "./features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts"; -import { P2PReplicatorPaneView, VIEW_TYPE_P2P } from "./features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts"; -import { useP2PReplicator } from "@lib/replication/trystero/P2PReplicatorCore.ts"; -import type { InjectableServiceHub } from "./lib/src/services/implements/injectable/InjectableServiceHub.ts"; +import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub.ts"; import { ObsidianServiceHub } from "./modules/services/ObsidianServiceHub.ts"; import { ServiceRebuilder } from "@lib/serviceModules/Rebuilder.ts"; import { ServiceDatabaseFileAccess } from "@/serviceModules/DatabaseFileAccess.ts"; @@ -27,20 +25,23 @@ import { FileAccessObsidian } from "./serviceModules/FileAccessObsidian.ts"; import { StorageEventManagerObsidian } from "./managers/StorageEventManagerObsidian.ts"; import type { ServiceModules } from "./types.ts"; import { setNoticeClass } from "@lib/mock_and_interop/wrapper.ts"; -import type { ObsidianServiceContext } from "./lib/src/services/implements/obsidian/ObsidianServiceContext.ts"; +import type { ObsidianServiceContext } from "@lib/services/implements/obsidian/ObsidianServiceContext.ts"; import { LiveSyncBaseCore } from "./LiveSyncBaseCore.ts"; import { ModuleObsidianMenu } from "./modules/essentialObsidian/ModuleObsidianMenu.ts"; import { ModuleObsidianSettingsAsMarkdown } from "./modules/features/ModuleObsidianSettingAsMarkdown.ts"; import { SetupManager } from "./modules/features/SetupManager.ts"; import { ModuleMigration } from "./modules/essential/ModuleMigration.ts"; import { enableI18nFeature } from "./serviceFeatures/onLayoutReady/enablei18n.ts"; -import { useOfflineScanner } from "./lib/src/serviceFeatures/offlineScanner.ts"; -import { useCheckRemoteSize } from "./lib/src/serviceFeatures/checkRemoteSize.ts"; +import { useOfflineScanner } from "@lib/serviceFeatures/offlineScanner.ts"; +import { useCheckRemoteSize } from "@lib/serviceFeatures/checkRemoteSize.ts"; import { useRedFlagFeatures } from "./serviceFeatures/redFlag.ts"; import { useSetupProtocolFeature } from "./serviceFeatures/setupObsidian/setupProtocol.ts"; import { useSetupQRCodeFeature } from "@lib/serviceFeatures/setupObsidian/qrCode"; import { useSetupURIFeature } from "@lib/serviceFeatures/setupObsidian/setupUri"; import { useSetupManagerHandlersFeature } from "./serviceFeatures/setupObsidian/setupManagerHandlers.ts"; +import { useP2PReplicatorFeature } from "@lib/replication/trystero/useP2PReplicatorFeature.ts"; +import { useP2PReplicatorCommands } from "@lib/replication/trystero/useP2PReplicatorCommands.ts"; +import { useP2PReplicatorUI } from "./serviceFeatures/useP2PReplicatorUI.ts"; export type LiveSyncCore = LiveSyncBaseCore; export default class ObsidianLiveSyncPlugin extends Plugin { core: LiveSyncCore; @@ -136,10 +137,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin { const serviceHub = new ObsidianServiceHub(this); - // Capture useP2PReplicator result so it can be passed to the P2PReplicator addon - // TODO: Dependency fix: bit hacky - let p2pReplicatorResult: ReturnType | undefined; - this.core = new LiveSyncBaseCore( serviceHub, (core, serviceHub) => { @@ -184,10 +181,13 @@ export default class ObsidianLiveSyncPlugin extends Plugin { useOfflineScanner(core); useRedFlagFeatures(core); useCheckRemoteSize(core); - p2pReplicatorResult = useP2PReplicator(core, [ - VIEW_TYPE_P2P, - (leaf: any) => new P2PReplicatorPaneView(leaf, core, p2pReplicatorResult!), - ]); + // p2pReplicatorResult = useP2PReplicator(core, [ + // 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/serviceFeatures/useP2PReplicatorUI.ts b/src/serviceFeatures/useP2PReplicatorUI.ts new file mode 100644 index 0000000..bfe042d --- /dev/null +++ b/src/serviceFeatures/useP2PReplicatorUI.ts @@ -0,0 +1,76 @@ +import { eventHub, EVENT_REQUEST_OPEN_P2P } from "@/common/events"; +import { reactiveSource } from "octagonal-wheels/dataobject/reactive_v2"; +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 type { LiveSyncCore } from "@/main"; + +/** + * ServiceFeature: P2P Replicator lifecycle management. + * Binds a LiveSyncTrysteroReplicator to the host's lifecycle events, + * following the same middleware style as useOfflineScanner. + * + * @param viewTypeAndFactory Optional [viewType, factory] pair for registering the P2P pane view. + * When provided, also registers commands and ribbon icon via services.API. + */ + +export function useP2PReplicatorUI( + host: NecessaryServices< + | "API" + | "appLifecycle" + | "setting" + | "vault" + | "database" + | "databaseEvents" + | "keyValueDB" + | "replication" + | "config" + | "UI" + | "replicator", + never + >, + core: LiveSyncCore, + replicator: UseP2PReplicatorResult +) { + // const env: LiveSyncTrysteroReplicatorEnv = { services: host.services as any }; + const getReplicator = () => replicator.replicator; + const p2pLogCollector = new P2PLogCollector(); + const storeP2PStatusLine = reactiveSource(""); + p2pLogCollector.p2pReplicationLine.onChanged((line) => { + storeP2PStatusLine.value = line.value; + }); + + // Register view, commands and ribbon if a view factory is provided + const viewType = VIEW_TYPE_P2P; + const factory = (leaf: any) => { + return new P2PReplicatorPaneView(leaf, core, { + replicator: getReplicator(), + p2pLogCollector, + storeP2PStatusLine, + }); + }; + const openPane = () => host.services.API.showWindow(viewType); + host.services.API.registerWindow(viewType, factory); + + host.services.appLifecycle.onInitialise.addHandler(() => { + eventHub.onEvent(EVENT_REQUEST_OPEN_P2P, () => { + void openPane(); + }); + + host.services.API.addCommand({ + id: "open-p2p-replicator", + name: "P2P Sync : Open P2P Replicator", + callback: () => { + void openPane(); + }, + }); + + host.services.API.addRibbonIcon("waypoints", "P2P Replicator", () => { + void openPane(); + })?.addClass?.("livesync-ribbon-replicate-p2p"); + + return Promise.resolve(true); + }); + return { replicator: getReplicator(), p2pLogCollector, storeP2PStatusLine }; +} From ee690858302c9bf511a8088c9e0d594f99c8c7ad Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 18 Mar 2026 11:51:52 +0100 Subject: [PATCH 098/339] Fixed: Some buttons on the setting dialogue now respond correctly again (#827). --- .../features/SettingDialogue/ObsidianLiveSyncSettingTab.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts index 6d5d5e9..9469397 100644 --- a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts +++ b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts @@ -321,8 +321,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { } closeSetting() { - // @ts-ignore - this.core.app.setting.close(); + //@ts-ignore : + this.plugin.app.setting.close(); } handleElement(element: HTMLElement, func: OnUpdateFunc) { From 3a29818612bb004e90f0e04423a081d3b754c30e Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 18 Mar 2026 11:54:22 +0100 Subject: [PATCH 099/339] - Delete items which are no longer used that might cause potential problems - Fix Some Imports - Fix floating promises on tests --- src/modules/features/ModuleLog.ts | 2 +- src/modules/main/ModuleLiveSyncMain.ts | 8 ++++---- .../setupObsidian/setupManagerHandlers.unit.spec.ts | 8 ++++---- .../setupObsidian/setupProtocol.unit.spec.ts | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/modules/features/ModuleLog.ts b/src/modules/features/ModuleLog.ts index d04f9c3..467de2b 100644 --- a/src/modules/features/ModuleLog.ts +++ b/src/modules/features/ModuleLog.ts @@ -30,7 +30,7 @@ import { LOG_LEVEL_NOTICE, setGlobalLogFunction } from "octagonal-wheels/common/ import { LogPaneView, VIEW_TYPE_LOG } from "./Log/LogPaneView.ts"; import { serialized } from "octagonal-wheels/concurrency/lock"; import { $msg } from "src/lib/src/common/i18n.ts"; -import { P2PLogCollector } from "../../lib/src/replication/trystero/P2PReplicatorCore.ts"; +import { P2PLogCollector } from "@/lib/src/replication/trystero/P2PLogCollector.ts"; import type { LiveSyncCore } from "../../main.ts"; import { LiveSyncError } from "@lib/common/LSError.ts"; import { isValidPath } from "@/common/utils.ts"; diff --git a/src/modules/main/ModuleLiveSyncMain.ts b/src/modules/main/ModuleLiveSyncMain.ts index 0dec4ff..d392c3e 100644 --- a/src/modules/main/ModuleLiveSyncMain.ts +++ b/src/modules/main/ModuleLiveSyncMain.ts @@ -106,10 +106,10 @@ export class ModuleLiveSyncMain extends AbstractModule { this._log($msg("moduleLiveSyncMain.logReadChangelog"), LOG_LEVEL_NOTICE); } - //@ts-ignore - if (this.isMobile) { - this.settings.disableRequestURI = true; - } + // //@ts-ignore + // if (this.isMobile) { + // this.settings.disableRequestURI = true; + // } if (last_version && Number(last_version) < VER) { this.settings.liveSync = false; this.settings.syncOnSave = false; diff --git a/src/serviceFeatures/setupObsidian/setupManagerHandlers.unit.spec.ts b/src/serviceFeatures/setupObsidian/setupManagerHandlers.unit.spec.ts index 6611194..067d860 100644 --- a/src/serviceFeatures/setupObsidian/setupManagerHandlers.unit.spec.ts +++ b/src/serviceFeatures/setupObsidian/setupManagerHandlers.unit.spec.ts @@ -20,7 +20,7 @@ describe("setupObsidian/setupManagerHandlers", () => { it("openSetupURI should delegate to SetupManager.onUseSetupURI", async () => { const setupManager = { - onUseSetupURI: vi.fn(async () => true), + onUseSetupURI: vi.fn(async () => await Promise.resolve(true)), } as any; await openSetupURI(setupManager); @@ -37,7 +37,7 @@ describe("setupObsidian/setupManagerHandlers", () => { }, } as any; const setupManager = { - onP2PManualSetup: vi.fn(async () => true), + onP2PManualSetup: vi.fn(async () => await Promise.resolve(true)), } as any; await openP2PSettings(host, setupManager); @@ -65,8 +65,8 @@ describe("setupObsidian/setupManagerHandlers", () => { }, } as any; const setupManager = { - onUseSetupURI: vi.fn(async () => true), - onP2PManualSetup: vi.fn(async () => true), + onUseSetupURI: vi.fn(async () => await Promise.resolve(true)), + onP2PManualSetup: vi.fn(async () => await Promise.resolve(true)), } as any; useSetupManagerHandlersFeature(host, setupManager); diff --git a/src/serviceFeatures/setupObsidian/setupProtocol.unit.spec.ts b/src/serviceFeatures/setupObsidian/setupProtocol.unit.spec.ts index 03d56e5..7974882 100644 --- a/src/serviceFeatures/setupObsidian/setupProtocol.unit.spec.ts +++ b/src/serviceFeatures/setupObsidian/setupProtocol.unit.spec.ts @@ -37,8 +37,8 @@ describe("setupObsidian/setupProtocol", () => { } as any; const log = vi.fn(); const setupManager = { - onUseSetupURI: vi.fn(async () => true), - decodeQR: vi.fn(async () => true), + onUseSetupURI: vi.fn(async () => await Promise.resolve(true)), + decodeQR: vi.fn(async () => await Promise.resolve(true)), } as any; registerSetupProtocolHandler(host, log, setupManager); @@ -67,8 +67,8 @@ describe("setupObsidian/setupProtocol", () => { } as any; const log = vi.fn(); const setupManager = { - onUseSetupURI: vi.fn(async () => true), - decodeQR: vi.fn(async () => true), + onUseSetupURI: vi.fn(async () => await Promise.resolve(true)), + decodeQR: vi.fn(async () => await Promise.resolve(true)), } as any; registerSetupProtocolHandler(host, log, setupManager); From c88e73b7d33a7e578b816567451f4914f65e6406 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 18 Mar 2026 11:55:50 +0100 Subject: [PATCH 100/339] Add note --- package-lock.json | 1104 ++++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + updates.md | 26 ++ 3 files changed, 1109 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8c08f58..e09262a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -89,6 +89,7 @@ "tsx": "^4.21.0", "typescript": "5.9.3", "vite": "^7.3.1", + "vite-plugin-istanbul": "^8.0.0", "vitest": "^4.0.16", "webdriverio": "^9.24.0", "yaml": "^2.8.2" @@ -974,6 +975,182 @@ "node": ">=18.0.0" } }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "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", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/core/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/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/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/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -994,14 +1171,38 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -1019,10 +1220,44 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { @@ -2708,6 +2943,123 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -4998,6 +5350,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -6424,9 +6786,9 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "peer": true, @@ -6975,6 +7337,19 @@ ], "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.8", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz", + "integrity": "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/basic-ftp": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz", @@ -7046,6 +7421,41 @@ "worker-factory": "^7.0.46" } }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/buffer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", @@ -7158,6 +7568,37 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001780", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", + "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/catering": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", @@ -7400,6 +7841,13 @@ "node": ">= 6" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -8063,6 +8511,13 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.313", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", + "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==", + "dev": true, + "license": "ISC" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -9411,6 +9866,16 @@ "node": ">= 0.4" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -9460,6 +9925,16 @@ "integrity": "sha512-7HuY/hebu4gryTDT7O/XY/fvY9wRByEGdK6QOa4of8npTcv0+NS6frFKABcf6S9EBAsveTuKTsZQQBFMMNILIg==", "license": "MIT" }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/get-port": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", @@ -10556,6 +11031,23 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -10891,6 +11383,13 @@ "integrity": "sha512-BcJPCQeLg6WjEx3FE591wVAevlli8lxsxm9/FzV4HXkV49TmBH38Yvrpce6fjbADGMKFrBMGTqrVz3qPIZ88Gg==", "license": "MIT" }, + "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/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -10904,6 +11403,19 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -12059,6 +12571,13 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -14403,6 +14922,13 @@ "node": ">= 10.x" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -14899,6 +15425,21 @@ "dev": true, "license": "MIT" }, + "node_modules/test-exclude": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz", + "integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^13.0.6", + "minimatch": "^10.2.2" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/text-decoder": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", @@ -15824,6 +16365,37 @@ "node": ">= 4.0.0" } }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -15950,6 +16522,67 @@ } } }, + "node_modules/vite-plugin-istanbul": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vite-plugin-istanbul/-/vite-plugin-istanbul-8.0.0.tgz", + "integrity": "sha512-r6L7cg2iwPqNnY/rWFyemWeDTIKRZjekEWS90e2FsTjDYH4UdTS6hvW1nEX1B++PKPCnqCaj5BJTDn5Cy5jYoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/generator": "^7.29.1", + "@istanbuljs/load-nyc-config": "^1.1.0", + "@types/babel__generator": "7.27.0", + "espree": "^11.2.0", + "istanbul-lib-instrument": "^6.0.3", + "picocolors": "^1.1.1", + "source-map": "^0.7.6", + "test-exclude": "^8.0.0" + }, + "peerDependencies": { + "vite": ">=4" + } + }, + "node_modules/vite-plugin-istanbul/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/vite-plugin-istanbul/node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/vite-plugin-istanbul/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, "node_modules/vite/node_modules/@esbuild/aix-ppc64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", @@ -17080,6 +17713,13 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, "node_modules/yaml": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", @@ -17897,6 +18537,131 @@ "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==" }, + "@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + } + }, + "@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true + }, + "@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "peer": true, + "requires": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "dependencies": { + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "requires": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } + } + }, + "@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true + }, + "@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "requires": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + } + }, + "@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + } + }, "@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -17909,13 +18674,29 @@ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true }, - "@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true + }, + "@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "dev": true, "requires": { - "@babel/types": "^7.28.5" + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + } + }, + "@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "requires": { + "@babel/types": "^7.29.0" } }, "@babel/runtime": { @@ -17923,10 +18704,36 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==" }, + "@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + } + }, + "@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + } + }, "@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "requires": { "@babel/helper-string-parser": "^7.27.1", @@ -18960,6 +19767,89 @@ } } }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, "@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -20516,6 +21406,15 @@ "integrity": "sha512-UkNnw1/oFEfecR8ypyHIQuWYdkPvHiwcQ78sh+ymIiYoF+uc5H1UBetbjyqT+vgGJ3qQN6nhucJviX6HesWtKQ==", "dev": true }, + "@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, "@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -21581,9 +22480,9 @@ } }, "acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "peer": true }, @@ -21933,6 +22832,12 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, + "baseline-browser-mapping": { + "version": "2.10.8", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz", + "integrity": "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==", + "dev": true + }, "basic-ftp": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz", @@ -21991,6 +22896,20 @@ "worker-factory": "^7.0.46" } }, + "browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "peer": true, + "requires": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + } + }, "buffer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", @@ -22055,6 +22974,18 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001780", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", + "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", + "dev": true + }, "catering": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", @@ -22233,6 +23164,12 @@ } } }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -22658,6 +23595,12 @@ } } }, + "electron-to-chromium": { + "version": "1.5.313", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", + "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==", + "dev": true + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -23604,6 +24547,12 @@ "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", "dev": true }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -23637,6 +24586,12 @@ "resolved": "https://registry.npmjs.org/get-iterator/-/get-iterator-2.0.1.tgz", "integrity": "sha512-7HuY/hebu4gryTDT7O/XY/fvY9wRByEGdK6QOa4of8npTcv0+NS6frFKABcf6S9EBAsveTuKTsZQQBFMMNILIg==" }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, "get-port": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", @@ -24316,6 +25271,19 @@ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true }, + "istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "requires": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + } + }, "istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -24576,6 +25544,12 @@ "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.9.3.tgz", "integrity": "sha512-BcJPCQeLg6WjEx3FE591wVAevlli8lxsxm9/FzV4HXkV49TmBH38Yvrpce6fjbADGMKFrBMGTqrVz3qPIZ88Gg==" }, + "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 + }, "js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -24585,6 +25559,12 @@ "argparse": "^2.0.1" } }, + "jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true + }, "json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -25414,6 +26394,12 @@ "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==" }, + "node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -26995,6 +27981,12 @@ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, "stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -27310,6 +28302,17 @@ } } }, + "test-exclude": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz", + "integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^13.0.6", + "minimatch": "^10.2.2" + } + }, "text-decoder": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", @@ -27842,6 +28845,16 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" }, + "update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "requires": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + } + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -28123,6 +29136,47 @@ } } }, + "vite-plugin-istanbul": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vite-plugin-istanbul/-/vite-plugin-istanbul-8.0.0.tgz", + "integrity": "sha512-r6L7cg2iwPqNnY/rWFyemWeDTIKRZjekEWS90e2FsTjDYH4UdTS6hvW1nEX1B++PKPCnqCaj5BJTDn5Cy5jYoQ==", + "dev": true, + "requires": { + "@babel/generator": "^7.29.1", + "@istanbuljs/load-nyc-config": "^1.1.0", + "@types/babel__generator": "7.27.0", + "espree": "^11.2.0", + "istanbul-lib-instrument": "^6.0.3", + "picocolors": "^1.1.1", + "source-map": "^0.7.6", + "test-exclude": "^8.0.0" + }, + "dependencies": { + "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 + }, + "espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "requires": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + } + }, + "source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true + } + } + }, "vitefu": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", @@ -28540,6 +29594,12 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, "yaml": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", diff --git a/package.json b/package.json index 38bbbfd..36d3c84 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,7 @@ "tsx": "^4.21.0", "typescript": "5.9.3", "vite": "^7.3.1", + "vite-plugin-istanbul": "^8.0.0", "vitest": "^4.0.16", "webdriverio": "^9.24.0", "yaml": "^2.8.2" diff --git a/updates.md b/updates.md index 361aa78..2b162d7 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,32 @@ 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. +## maybe 0.25.54 (will be released soon) + +18th March, 2026 + +### Fixed + +- Remote storage size check now works correctly again (#818). +- Some buttons on the setting dialogue now respond correctly again (#827). + +### Refactored + +- P2P replicator has been refactored to be a little roust and easier to understand. +- Delete items which are no longer used that might cause potential problems + +### CLI + +- Fixed the corrupted display of the help message. +- Remove some unnecessary codes. + +### WebApp + +- Fixed the issue where the detail level was not being applied in the log pane. +- Pop-ups are now shown. +- Add coverage for test. +- Pop-ups are now shown in the web app as well. + ## 0.25.53 17th March, 2026 From c454616e1cf5cbf6eec00583f476313d85794ff3 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 18 Mar 2026 12:01:57 +0100 Subject: [PATCH 101/339] bump --- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- updates.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/manifest.json b/manifest.json index deaf660..a375038 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.53", + "version": "0.25.54", "minAppVersion": "0.9.12", "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 e09262a..f07a31d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.53", + "version": "0.25.54", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.53", + "version": "0.25.54", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 36d3c84..3d20723 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.53", + "version": "0.25.54", "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 2b162d7..7724b3b 100644 --- a/updates.md +++ b/updates.md @@ -3,7 +3,7 @@ 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. -## maybe 0.25.54 (will be released soon) +## 0.25.54 18th March, 2026 From c2bfaeb5a91a7207345cd35a4108235a213688bc Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 18 Mar 2026 12:03:51 +0100 Subject: [PATCH 102/339] Fixed: wrong import --- src/apps/cli/commands/p2p.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/apps/cli/commands/p2p.ts b/src/apps/cli/commands/p2p.ts index b889f51..f47b62e 100644 --- a/src/apps/cli/commands/p2p.ts +++ b/src/apps/cli/commands/p2p.ts @@ -2,8 +2,7 @@ import type { LiveSyncBaseCore } from "../../../LiveSyncBaseCore"; import { P2P_DEFAULT_SETTINGS } from "@lib/common/types"; import type { ServiceContext } from "@lib/services/base/ServiceBase"; import { LiveSyncTrysteroReplicator } from "@lib/replication/trystero/LiveSyncTrysteroReplicator"; -import { addP2PEventHandlers } from "@lib/replication/trystero/P2PReplicatorCore"; - +import { addP2PEventHandlers } from "@lib/replication/trystero/addP2PEventHandlers"; type CLIP2PPeer = { peerId: string; name: string; From c3341da242a1733dab75566fbe4b2cee32c5b540 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 18 Mar 2026 12:05:15 +0100 Subject: [PATCH 103/339] Fix english --- updates.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/updates.md b/updates.md index 7724b3b..34929eb 100644 --- a/updates.md +++ b/updates.md @@ -10,23 +10,23 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid ### Fixed - Remote storage size check now works correctly again (#818). -- Some buttons on the setting dialogue now respond correctly again (#827). +- Some buttons on the settings dialogue now respond correctly again (#827). ### Refactored -- P2P replicator has been refactored to be a little roust and easier to understand. +- P2P replicator has been refactored to be a little more robust and easier to understand. - Delete items which are no longer used that might cause potential problems ### CLI - Fixed the corrupted display of the help message. -- Remove some unnecessary codes. +- Remove some unnecessary code. ### WebApp - Fixed the issue where the detail level was not being applied in the log pane. - Pop-ups are now shown. -- Add coverage for test. +- Add coverage for the test. - Pop-ups are now shown in the web app as well. ## 0.25.53 From 2ff60dd5ac3fccc76ae35af03fc1b167451c43dc Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 18 Mar 2026 12:20:52 +0100 Subject: [PATCH 104/339] Add missed files --- src/apps/webapp/playwright.config.ts | 81 ++++++++ src/apps/webapp/test-entry.ts | 203 ++++++++++++++++++ src/apps/webapp/test.html | 26 +++ src/apps/webapp/test/e2e.spec.ts | 294 +++++++++++++++++++++++++++ 4 files changed, 604 insertions(+) create mode 100644 src/apps/webapp/playwright.config.ts create mode 100644 src/apps/webapp/test-entry.ts create mode 100644 src/apps/webapp/test.html create mode 100644 src/apps/webapp/test/e2e.spec.ts diff --git a/src/apps/webapp/playwright.config.ts b/src/apps/webapp/playwright.config.ts new file mode 100644 index 0000000..357d739 --- /dev/null +++ b/src/apps/webapp/playwright.config.ts @@ -0,0 +1,81 @@ +import { defineConfig, devices } from "@playwright/test"; +import * as path from "path"; +import * as fs from "fs"; +import { fileURLToPath } from "url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// --------------------------------------------------------------------------- +// Load environment variables from .test.env (root) so that CouchDB +// connection details are visible to the test process. +// --------------------------------------------------------------------------- +function loadEnvFile(envPath: string): Record { + const result: Record = {}; + if (!fs.existsSync(envPath)) return result; + const lines = fs.readFileSync(envPath, "utf-8").split("\n"); + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("#")) continue; + const eq = trimmed.indexOf("="); + if (eq < 0) continue; + const key = trimmed.slice(0, eq).trim(); + const val = trimmed.slice(eq + 1).trim(); + result[key] = val; + } + return result; +} + +// __dirname is src/apps/webapp — root is three levels up +const ROOT = path.resolve(__dirname, "../../.."); +const envVars = { + ...loadEnvFile(path.join(ROOT, ".env")), + ...loadEnvFile(path.join(ROOT, ".test.env")), +}; + +// Make the loaded variables available to all test files via process.env. +for (const [k, v] of Object.entries(envVars)) { + if (!(k in process.env)) { + process.env[k] = v; + } +} + +export default defineConfig({ + testDir: "./test", + // Give each test plenty of time for replication round-trips. + timeout: 120_000, + expect: { timeout: 30_000 }, + // Run test files sequentially; the tests themselves manage two contexts. + fullyParallel: false, + workers: 1, + reporter: "list", + + use: { + baseURL: "http://localhost:3000", + // Use Chromium for OPFS and FileSystem API support. + ...devices["Desktop Chrome"], + headless: true, + // Launch args to match the main vitest browser config. + launchOptions: { + args: ["--js-flags=--expose-gc"], + }, + }, + + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], + + // Start the vite dev server before running the tests. + webServer: { + command: "npx vite --port 3000", + url: "http://localhost:3000", + // Re-use a running dev server when developing locally. + reuseExistingServer: !process.env.CI, + timeout: 30_000, + // Run from the webapp directory so vite finds its config. + cwd: __dirname, + }, +}); diff --git a/src/apps/webapp/test-entry.ts b/src/apps/webapp/test-entry.ts new file mode 100644 index 0000000..3dcb8fe --- /dev/null +++ b/src/apps/webapp/test-entry.ts @@ -0,0 +1,203 @@ +/** + * LiveSync WebApp E2E test entry point. + * + * When served by vite dev server (at /test.html), this module wires up + * `window.livesyncTest`, a plain JS API that Playwright tests can call via + * `page.evaluate()`. All methods are async and serialisation-safe. + * + * Vault storage is backed by OPFS so no `showDirectoryPicker()` interaction + * is required, making it fully headless-compatible. + */ + +import { LiveSyncWebApp } from "./main"; +import type { ObsidianLiveSyncSettings } from "@lib/common/types"; +import type { FilePathWithPrefix } from "@lib/common/types"; + +// -------------------------------------------------------------------------- +// Internal state – one app instance per page / browser context +// -------------------------------------------------------------------------- +let app: LiveSyncWebApp | null = null; + +// -------------------------------------------------------------------------- +// Helpers +// -------------------------------------------------------------------------- + +/** Strip the "plain:" / "enc:" / … prefix used internally in PouchDB paths. */ +function stripPrefix(raw: string): string { + return raw.replace(/^[^:]+:/, ""); +} + +/** + * Poll every 300 ms until all known processing queues are drained, or until + * the timeout elapses. Mirrors `waitForIdle` in the existing vitest harness. + */ +async function waitForIdle(core: any, timeoutMs = 60_000): Promise { + const deadline = Date.now() + timeoutMs; + while (Date.now() < deadline) { + const q = + (core.services?.replication?.databaseQueueCount?.value ?? 0) + + (core.services?.fileProcessing?.totalQueued?.value ?? 0) + + (core.services?.fileProcessing?.batched?.value ?? 0) + + (core.services?.fileProcessing?.processing?.value ?? 0) + + (core.services?.replication?.storageApplyingCount?.value ?? 0); + if (q === 0) return; + await new Promise((r) => setTimeout(r, 300)); + } + throw new Error(`waitForIdle timed out after ${timeoutMs} ms`); +} + +function getCore(): any { + const core = (app as any)?.core; + if (!core) throw new Error("Vault not initialised – call livesyncTest.init() first"); + return core; +} + +// -------------------------------------------------------------------------- +// Public test API +// -------------------------------------------------------------------------- + +export interface LiveSyncTestAPI { + /** + * Initialise a vault in OPFS under the given name and apply `settings`. + * Any previous contents of the OPFS directory are wiped first so each + * test run starts clean. + */ + init(vaultName: string, settings: Partial): Promise; + + /** + * Write `content` to the local PouchDB under `vaultPath` (equivalent to + * the CLI `put` command). Waiting for the DB write to finish is + * included; you still need to call `replicate()` to push to remote. + */ + putFile(vaultPath: string, content: string): Promise; + + /** + * Mark `vaultPath` as deleted in the local PouchDB (equivalent to CLI + * `rm`). Call `replicate()` afterwards to propagate to remote. + */ + deleteFile(vaultPath: string): Promise; + + /** + * Run one full replication cycle (push + pull) against the remote CouchDB, + * then wait for the local storage-application queue to drain. + */ + replicate(): Promise; + + /** + * Wait until all processing queues are idle. Usually not needed after + * `putFile` / `deleteFile` since those already await, but useful when + * testing results after `replicate()`. + */ + waitForIdle(timeoutMs?: number): Promise; + + /** + * Return metadata for `vaultPath` from the local database, or `null` if + * not found / deleted. + */ + getInfo(vaultPath: string): Promise<{ + path: string; + revision: string; + conflicts: string[]; + size: number; + mtime: number; + } | null>; + + /** Convenience wrapper: returns true when the doc has ≥1 conflict revision. */ + hasConflict(vaultPath: string): Promise; + + /** Tear down the current app instance. */ + shutdown(): Promise; +} + +// -------------------------------------------------------------------------- +// Implementation +// -------------------------------------------------------------------------- + +const livesyncTest: LiveSyncTestAPI = { + async init(vaultName: string, settings: Partial): Promise { + // Clean up any stale OPFS data from previous runs. + const opfsRoot = await navigator.storage.getDirectory(); + try { + await opfsRoot.removeEntry(vaultName, { recursive: true }); + } catch { + // directory did not exist – that's fine + } + const vaultDir = await opfsRoot.getDirectoryHandle(vaultName, { create: true }); + + // Pre-write settings so they are loaded during initialise(). + const livesyncDir = await vaultDir.getDirectoryHandle(".livesync", { create: true }); + const settingsFile = await livesyncDir.getFileHandle("settings.json", { create: true }); + const writable = await settingsFile.createWritable(); + await writable.write(JSON.stringify(settings)); + await writable.close(); + + app = new LiveSyncWebApp(vaultDir); + await app.initialize(); + + // Give background startup tasks a moment to settle. + await waitForIdle(getCore(), 30_000); + }, + + async putFile(vaultPath: string, content: string): Promise { + const core = getCore(); + const result = await core.serviceModules.databaseFileAccess.storeContent( + vaultPath as FilePathWithPrefix, + content + ); + await waitForIdle(core); + return result !== false; + }, + + async deleteFile(vaultPath: string): Promise { + const core = getCore(); + const result = await core.serviceModules.databaseFileAccess.delete(vaultPath as FilePathWithPrefix); + await waitForIdle(core); + return result !== false; + }, + + async replicate(): Promise { + const core = getCore(); + const result = await core.services.replication.replicate(true); + // After replicate() resolves, remote docs may still be queued for + // local storage application – wait until all queues are drained. + await waitForIdle(core); + return result !== false; + }, + + async waitForIdle(timeoutMs?: number): Promise { + await waitForIdle(getCore(), timeoutMs ?? 60_000); + }, + + async getInfo(vaultPath: string) { + const core = getCore(); + const db = core.services?.database; + for await (const doc of db.localDatabase.findAllNormalDocs({ conflicts: true })) { + if (doc._deleted || doc.deleted) continue; + const docPath = stripPrefix(doc.path ?? ""); + if (docPath !== vaultPath) continue; + return { + path: docPath, + revision: (doc._rev as string) ?? "", + conflicts: (doc._conflicts as string[]) ?? [], + size: (doc.size as number) ?? 0, + mtime: (doc.mtime as number) ?? 0, + }; + } + return null; + }, + + async hasConflict(vaultPath: string): Promise { + const info = await livesyncTest.getInfo(vaultPath); + return (info?.conflicts?.length ?? 0) > 0; + }, + + async shutdown(): Promise { + if (app) { + await app.shutdown(); + app = null; + } + }, +}; + +// Expose on window for Playwright page.evaluate() calls. +(window as any).livesyncTest = livesyncTest; diff --git a/src/apps/webapp/test.html b/src/apps/webapp/test.html new file mode 100644 index 0000000..8a5efcf --- /dev/null +++ b/src/apps/webapp/test.html @@ -0,0 +1,26 @@ + + + + + + LiveSync WebApp – E2E Test Page + + + +

LiveSync WebApp E2E

+

This page is used by Playwright tests only. window.livesyncTest is exposed by the script below.

+ +
Loading…
+ + + diff --git a/src/apps/webapp/test/e2e.spec.ts b/src/apps/webapp/test/e2e.spec.ts new file mode 100644 index 0000000..ac83045 --- /dev/null +++ b/src/apps/webapp/test/e2e.spec.ts @@ -0,0 +1,294 @@ +/** + * WebApp E2E tests – two-vault scenarios. + * + * Each vault (A and B) runs in its own browser context so that JavaScript + * global state (including Trystero's global signalling tables) is fully + * isolated. The two vaults communicate only through the shared remote + * CouchDB database. + * + * Vault storage is OPFS-backed – no file-picker interaction needed. + * + * Prerequisites: + * - A reachable CouchDB instance whose connection details are in .test.env + * (read automatically by playwright.config.ts). + * + * How to run: + * cd src/apps/webapp && npm run test:e2e + */ + +import { test, expect, type BrowserContext, type Page, type TestInfo } from "@playwright/test"; +import type { LiveSyncTestAPI } from "../test-entry"; +import { mkdirSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// --------------------------------------------------------------------------- +// Settings helpers +// --------------------------------------------------------------------------- + +function requireEnv(name: string): string { + const v = process.env[name]; + if (!v) throw new Error(`Missing required env variable: ${name}`); + return v; +} + +async function ensureCouchDbDatabase(uri: string, user: string, pass: string, dbName: string): Promise { + const base = uri.replace(/\/+$/, ""); + const dbUrl = `${base}/${encodeURIComponent(dbName)}`; + const auth = Buffer.from(`${user}:${pass}`, "utf-8").toString("base64"); + const response = await fetch(dbUrl, { + method: "PUT", + headers: { + Authorization: `Basic ${auth}`, + }, + }); + + // 201: created, 202: accepted, 412: already exists + if (response.status === 201 || response.status === 202 || response.status === 412) { + return; + } + + const body = await response.text().catch(() => ""); + throw new Error(`Failed to ensure CouchDB database (${response.status}): ${body}`); +} + +function buildSettings(dbName: string): Record { + return { + // Remote database (shared between A and B – this is the replication target) + couchDB_URI: requireEnv("hostname").replace(/\/+$/, ""), + couchDB_USER: process.env["username"] ?? "", + couchDB_PASSWORD: process.env["password"] ?? "", + couchDB_DBNAME: dbName, + + // Core behaviour + isConfigured: true, + liveSync: false, + syncOnSave: false, + syncOnStart: false, + periodicReplication: false, + gcDelay: 0, + savingDelay: 0, + notifyThresholdOfRemoteStorageSize: 0, + + // Encryption off for test simplicity + encrypt: false, + + // Disable plugin/hidden-file sync (not needed in webapp) + usePluginSync: false, + autoSweepPlugins: false, + autoSweepPluginsPeriodic: false, + + //Auto accept perr + P2P_AutoAcceptingPeers: "~.*", + }; +} + +// --------------------------------------------------------------------------- +// Test-page helpers +// --------------------------------------------------------------------------- + +/** Navigate to the test entry page and wait for `window.livesyncTest`. */ +async function openTestPage(ctx: BrowserContext): Promise { + const page = await ctx.newPage(); + await page.goto("/test.html"); + await page.waitForFunction(() => !!(window as any).livesyncTest, { timeout: 20_000 }); + return page; +} + +/** Type-safe wrapper – calls `window.livesyncTest.(...args)` in the page. */ +async function call( + page: Page, + method: M, + ...args: Parameters +): Promise>> { + const invoke = () => + page.evaluate(([m, a]) => (window as any).livesyncTest[m](...a), [method, args] as [ + string, + unknown[], + ]) as Promise>>; + + try { + return await invoke(); + } catch (ex: any) { + const message = String(ex?.message ?? ex); + // Some startup flows may trigger one page reload; recover once. + if ( + message.includes("Execution context was destroyed") || + message.includes("Most likely the page has been closed") + ) { + await page.waitForFunction(() => !!(window as any).livesyncTest, { timeout: 20_000 }); + return await invoke(); + } + throw ex; + } +} + +async function dumpCoverage(page: Page | undefined, label: string, testInfo: TestInfo): Promise { + if (!process.env.PW_COVERAGE || !page || page.isClosed()) { + return; + } + const cov = await page + .evaluate(() => { + const data = (window as any).__coverage__; + if (!data) return null; + // Reset between tests to avoid runaway accumulation. + (window as any).__coverage__ = {}; + return data; + }) + .catch(() => null!); + if (!cov) return; + if (typeof cov === "object" && Object.keys(cov as Record).length === 0) { + return; + } + + const outDir = path.resolve(__dirname, "../.nyc_output"); + mkdirSync(outDir, { recursive: true }); + const name = `${testInfo.testId.replace(/[^a-zA-Z0-9_-]/g, "_")}-${label}.json`; + writeFileSync(path.join(outDir, name), JSON.stringify(cov), "utf-8"); +} + +// --------------------------------------------------------------------------- +// Two-vault E2E suite +// --------------------------------------------------------------------------- + +test.describe("WebApp two-vault E2E", () => { + let ctxA: BrowserContext; + let ctxB: BrowserContext; + let pageA: Page; + let pageB: Page; + + const DB_SUFFIX = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; + const dbName = `${requireEnv("dbname")}-${DB_SUFFIX}`; + const settings = buildSettings(dbName); + + test.beforeAll(async ({ browser }) => { + await ensureCouchDbDatabase( + String(settings.couchDB_URI ?? ""), + String(settings.couchDB_USER ?? ""), + String(settings.couchDB_PASSWORD ?? ""), + dbName + ); + + // Open Vault A and Vault B in completely separate browser contexts. + // Each context has its own JS runtime, IndexedDB and OPFS root, so + // Trystero global state and PouchDB instance names cannot collide. + ctxA = await browser.newContext(); + ctxB = await browser.newContext(); + + pageA = await openTestPage(ctxA); + pageB = await openTestPage(ctxB); + + await call(pageA, "init", "testvault_a", settings as any); + await call(pageB, "init", "testvault_b", settings as any); + }); + + test.afterAll(async () => { + await call(pageA, "shutdown").catch(() => {}); + await call(pageB, "shutdown").catch(() => {}); + await ctxA.close(); + await ctxB.close(); + }); + + test.afterEach(async ({}, testInfo) => { + await dumpCoverage(pageA, "vaultA", testInfo); + await dumpCoverage(pageB, "vaultB", testInfo); + }); + + // ----------------------------------------------------------------------- + // Case 1: Vault A writes a file and can read its metadata back from the + // local database (no replication yet). + // ----------------------------------------------------------------------- + test("Case 1: A writes a file and can get its info", async () => { + const FILE = "e2e/case1-a-only.md"; + const CONTENT = "hello from vault A"; + + const ok = await call(pageA, "putFile", FILE, CONTENT); + expect(ok).toBe(true); + + const info = await call(pageA, "getInfo", FILE); + expect(info).not.toBeNull(); + expect(info!.path).toBe(FILE); + expect(info!.revision).toBeTruthy(); + expect(info!.conflicts).toHaveLength(0); + }); + + // ----------------------------------------------------------------------- + // Case 2: Vault A writes a file, both vaults replicate, and Vault B ends + // up with the file in its local database. + // ----------------------------------------------------------------------- + test("Case 2: A writes a file, both replicate, B receives the file", async () => { + const FILE = "e2e/case2-sync.md"; + const CONTENT = "content from A – should appear in B"; + + await call(pageA, "putFile", FILE, CONTENT); + + // A pushes to remote, B pulls from remote. + await call(pageA, "replicate"); + await call(pageB, "replicate"); + + const infoB = await call(pageB, "getInfo", FILE); + expect(infoB).not.toBeNull(); + expect(infoB!.path).toBe(FILE); + }); + + // ----------------------------------------------------------------------- + // Case 3: Vault A deletes the file it synced in case 2. After both + // vaults replicate, Vault B no longer sees the file. + // ----------------------------------------------------------------------- + test("Case 3: A deletes the file, both replicate, B no longer sees it", async () => { + // This test depends on Case 2 having put e2e/case2-sync.md into both vaults. + const FILE = "e2e/case2-sync.md"; + + await call(pageA, "deleteFile", FILE); + + await call(pageA, "replicate"); + await call(pageB, "replicate"); + + const infoB = await call(pageB, "getInfo", FILE); + // The file should be gone (null means not found or deleted). + expect(infoB).toBeNull(); + }); + + // ----------------------------------------------------------------------- + // Case 4: A and B each independently edit the same file that was already + // synced. After both vaults replicate the editing cycle, both + // vaults report a conflict on that file. + // ----------------------------------------------------------------------- + test("Case 4: concurrent edits from A and B produce a conflict on both sides", async () => { + const FILE = "e2e/case4-conflict.md"; + + // 1) Write a baseline and synchronise so both vaults start from the + // same revision. + await call(pageA, "putFile", FILE, "base content"); + await call(pageA, "replicate"); + await call(pageB, "replicate"); + + // Confirm B has the base file with no conflicts yet. + const baseInfoB = await call(pageB, "getInfo", FILE); + expect(baseInfoB).not.toBeNull(); + expect(baseInfoB!.conflicts).toHaveLength(0); + + // 2) Both vaults write diverging content without syncing in between – + // this creates two competing revisions. + await call(pageA, "putFile", FILE, "content from A (conflict side)"); + await call(pageB, "putFile", FILE, "content from B (conflict side)"); + + // 3) Run replication on both sides. The order mirrors the pattern + // from the CLI two-vault tests (A → remote → B → remote → A). + await call(pageA, "replicate"); + await call(pageB, "replicate"); + await call(pageA, "replicate"); // re-check from A to pick up B's revision + + // 4) At least one side must report a conflict. + const hasConflictA = await call(pageA, "hasConflict", FILE); + const hasConflictB = await call(pageB, "hasConflict", FILE); + + expect( + hasConflictA || hasConflictB, + "Expected a conflict to appear on vault A or vault B after diverging edits" + ).toBe(true); + }); +}); From 967a78d657d0a0962eb8df7341e5e04dde917382 Mon Sep 17 00:00:00 2001 From: "Shibata, Tats" <868951+rewse@users.noreply.github.com> Date: Sun, 22 Mar 2026 11:57:47 +0900 Subject: [PATCH 105/339] fix(cli): handle incomplete localStorage in Node.js v25+ Node.js v25 provides a built-in localStorage on globalThis, but without `--localstorage-file` it is an empty object lacking getItem/setItem. The existing check `!("localStorage" in globalThis)` passes, so the polyfill is skipped and the CLI crashes with: TypeError: localStorage.getItem is not a function Check for getItem as well so the polyfill is applied when the native implementation is incomplete. --- src/apps/cli/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/cli/main.ts b/src/apps/cli/main.ts index 442d9c9..fac1072 100644 --- a/src/apps/cli/main.ts +++ b/src/apps/cli/main.ts @@ -3,7 +3,7 @@ * Command-line version of Self-hosted LiveSync plugin for syncing vaults without Obsidian */ -if (!("localStorage" in globalThis)) { +if (!("localStorage" in globalThis) || typeof (globalThis as any).localStorage?.getItem !== "function") { const store = new Map(); (globalThis as any).localStorage = { getItem: (key: string) => (store.has(key) ? store.get(key)! : null), From 985004bc0e3e4ef27a56b4c5be640dd7b2ba6801 Mon Sep 17 00:00:00 2001 From: "Shibata, Tats" <868951+rewse@users.noreply.github.com> Date: Sun, 22 Mar 2026 12:37:17 +0900 Subject: [PATCH 106/339] fix(cli): show actionable error when sync fails due to locked remote DB When the remote database is locked and the CLI device is not in the accepted_nodes list, openReplication returns false with no CLI-specific guidance. The existing log message ('Fetch rebuilt DB, explicit unlocking or chunk clean-up is required') is aimed at the Obsidian plugin UI. Check the replicator's remoteLockedAndDeviceNotAccepted flag after sync failure and print a clear message directing the user to unlock from the Obsidian plugin. Ref: #832 --- src/apps/cli/commands/runCommand.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/apps/cli/commands/runCommand.ts b/src/apps/cli/commands/runCommand.ts index b005acb..fc5d482 100644 --- a/src/apps/cli/commands/runCommand.ts +++ b/src/apps/cli/commands/runCommand.ts @@ -21,6 +21,15 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext if (options.command === "sync") { console.log("[Command] sync"); const result = await core.services.replication.replicate(true); + if (!result) { + const replicator = core.services.replicator.getActiveReplicator(); + if (replicator?.remoteLockedAndDeviceNotAccepted) { + console.error( + `[Error] The remote database is locked and this device is not yet accepted.\n` + + `[Error] Please unlock the database from the Obsidian plugin and retry.` + ); + } + } return !!result; } From e01f7f4d92647b6af20083255786eb0d5c9ef02f Mon Sep 17 00:00:00 2001 From: "Shibata, Tats" <868951+rewse@users.noreply.github.com> Date: Thu, 26 Mar 2026 00:58:51 +0900 Subject: [PATCH 107/339] test(cli): add TODO comment and locked-remote-DB test script - Add inline TODO comment in runCommand.ts about standardising replication failure cause identification logic. - Add test-sync-locked-remote-linux.sh that verifies: 1. sync succeeds when the remote milestone is not locked. 2. sync fails with an actionable error when the remote milestone has locked=true and accepted_nodes is empty. --- src/apps/cli/commands/runCommand.ts | 3 + .../cli/test/test-sync-locked-remote-linux.sh | 136 ++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100755 src/apps/cli/test/test-sync-locked-remote-linux.sh diff --git a/src/apps/cli/commands/runCommand.ts b/src/apps/cli/commands/runCommand.ts index fc5d482..7672d50 100644 --- a/src/apps/cli/commands/runCommand.ts +++ b/src/apps/cli/commands/runCommand.ts @@ -22,6 +22,9 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext console.log("[Command] sync"); const result = await core.services.replication.replicate(true); if (!result) { + // TODO: Standardise the logic for identifying the cause of replication + // failure so that every reason (locked DB, version mismatch, network + // error, etc.) is surfaced with a CLI-specific actionable message. const replicator = core.services.replicator.getActiveReplicator(); if (replicator?.remoteLockedAndDeviceNotAccepted) { console.error( diff --git a/src/apps/cli/test/test-sync-locked-remote-linux.sh b/src/apps/cli/test/test-sync-locked-remote-linux.sh new file mode 100755 index 0000000..1d331e7 --- /dev/null +++ b/src/apps/cli/test/test-sync-locked-remote-linux.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +# Test: CLI sync behaviour against a locked remote database. +# +# Scenario: +# 1. Start CouchDB, create a test database, and perform an initial sync so that +# the milestone document is created on the remote. +# 2. Unlock the milestone (locked=false, accepted_nodes=[]) and verify sync +# succeeds without the locked error message. +# 3. Lock the milestone (locked=true, accepted_nodes=[]) and verify sync fails +# with an actionable error message. +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}" +TEST_ENV_FILE="${TEST_ENV_FILE:-$CLI_DIR/.test.env}" +cli_test_init_cli_cmd + +if [[ ! -f "$TEST_ENV_FILE" ]]; then + echo "[ERROR] test env file not found: $TEST_ENV_FILE" >&2 + exit 1 +fi + +set -a +source "$TEST_ENV_FILE" +set +a + +DB_SUFFIX="$(date +%s)-$RANDOM" + +COUCHDB_URI="${hostname%/}" +COUCHDB_DBNAME="${dbname}-locked-${DB_SUFFIX}" +COUCHDB_USER="${username:-}" +COUCHDB_PASSWORD="${password:-}" + +if [[ -z "$COUCHDB_URI" || -z "$COUCHDB_USER" || -z "$COUCHDB_PASSWORD" ]]; then + echo "[ERROR] COUCHDB_URI, COUCHDB_USER, COUCHDB_PASSWORD are required" >&2 + exit 1 +fi + +WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-locked-test.XXXXXX")" +VAULT_DIR="$WORK_DIR/vault" +SETTINGS_FILE="$WORK_DIR/settings.json" +mkdir -p "$VAULT_DIR" + +cleanup() { + local exit_code=$? + cli_test_stop_couchdb + rm -rf "$WORK_DIR" + exit "$exit_code" +} +trap cleanup EXIT + +if [[ "$RUN_BUILD" == "1" ]]; then + echo "[INFO] building CLI" + npm run build +fi + +echo "[INFO] starting CouchDB and creating test database: $COUCHDB_DBNAME" +cli_test_start_couchdb "$COUCHDB_URI" "$COUCHDB_USER" "$COUCHDB_PASSWORD" "$COUCHDB_DBNAME" + +echo "[INFO] preparing settings" +cli_test_init_settings_file "$SETTINGS_FILE" +cli_test_apply_couchdb_settings "$SETTINGS_FILE" "$COUCHDB_URI" "$COUCHDB_USER" "$COUCHDB_PASSWORD" "$COUCHDB_DBNAME" 1 + +echo "[INFO] initial sync to create milestone document" +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" sync >/dev/null + +MILESTONE_ID="_local/obsydian_livesync_milestone" +MILESTONE_URL="${COUCHDB_URI}/${COUCHDB_DBNAME}/${MILESTONE_ID}" + +update_milestone() { + local locked="$1" + local accepted_nodes="$2" + local current + current="$(cli_test_curl_json --user "${COUCHDB_USER}:${COUCHDB_PASSWORD}" "$MILESTONE_URL")" + local updated + updated="$(node -e ' +const doc = JSON.parse(process.argv[1]); +doc.locked = process.argv[2] === "true"; +doc.accepted_nodes = JSON.parse(process.argv[3]); +process.stdout.write(JSON.stringify(doc)); +' "$current" "$locked" "$accepted_nodes")" + cli_test_curl_json -X PUT \ + --user "${COUCHDB_USER}:${COUCHDB_PASSWORD}" \ + -H "Content-Type: application/json" \ + -d "$updated" \ + "$MILESTONE_URL" >/dev/null +} + +SYNC_LOG="$WORK_DIR/sync.log" + +echo "[CASE] sync should succeed when remote is not locked" +update_milestone "false" "[]" + +set +e +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" sync >"$SYNC_LOG" 2>&1 +SYNC_EXIT=$? +set -e + +if [[ "$SYNC_EXIT" -ne 0 ]]; then + echo "[FAIL] sync should succeed when remote is not locked" >&2 + cat "$SYNC_LOG" >&2 + exit 1 +fi + +if grep -Fq "The remote database is locked" "$SYNC_LOG"; then + echo "[FAIL] locked error should not appear when remote is not locked" >&2 + cat "$SYNC_LOG" >&2 + exit 1 +fi + +echo "[PASS] unlocked remote DB syncs successfully" + +echo "[CASE] sync should fail with actionable error when remote is locked" +update_milestone "true" "[]" + +set +e +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" sync >"$SYNC_LOG" 2>&1 +SYNC_EXIT=$? +set -e + +if [[ "$SYNC_EXIT" -eq 0 ]]; then + echo "[FAIL] sync should have exited with non-zero when remote is locked" >&2 + cat "$SYNC_LOG" >&2 + exit 1 +fi + +cli_test_assert_contains "$(cat "$SYNC_LOG")" \ + "The remote database is locked and this device is not yet accepted" \ + "sync output should contain the locked-remote error message" + +echo "[PASS] locked remote DB produces actionable CLI error" From fee34f0dcb84c5cde314556fbe9a750ffc1e6c11 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 26 Mar 2026 11:55:06 +0100 Subject: [PATCH 108/339] Update dependency: deduplicate --- package-lock.json | 96 +++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/package-lock.json b/package-lock.json index f07a31d..d80746f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9177,9 +9177,9 @@ } }, "node_modules/eslint-plugin-svelte/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "dev": true, "license": "ISC", "engines": { @@ -12323,9 +12323,9 @@ } }, "node_modules/minimatch/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -15527,9 +15527,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "peer": true, @@ -16341,9 +16341,9 @@ } }, "node_modules/undici": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.2.tgz", - "integrity": "sha512-P9J1HWYV/ajFr8uCqk5QixwiRKmB1wOamgS0e+o2Z4A44Ej2+thFVRLG/eA7qprx88XXhnV5Bl8LHXTURpzB3Q==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz", + "integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==", "dev": true, "license": "MIT", "engines": { @@ -17069,9 +17069,9 @@ } }, "node_modules/vite/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "peer": true, @@ -17182,9 +17182,9 @@ } }, "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -17305,9 +17305,9 @@ } }, "node_modules/webdriver/node_modules/undici": { - "version": "6.24.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.0.tgz", - "integrity": "sha512-lVLNosgqo5EkGqh5XUDhGfsMSoO8K0BAN0TyJLvwNRSl4xWGZlCVYsAIpa/OpA3TvmnM01GWcoKmc3ZWo5wKKA==", + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", + "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==", "dev": true, "license": "MIT", "engines": { @@ -17721,9 +17721,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "dev": true, "license": "ISC", "peer": true, @@ -24081,9 +24081,9 @@ } }, "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "dev": true } } @@ -26230,9 +26230,9 @@ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==" }, "brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "requires": { "balanced-match": "^4.0.2" } @@ -28373,9 +28373,9 @@ "requires": {} }, "picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "peer": true } @@ -28829,9 +28829,9 @@ } }, "undici": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.2.tgz", - "integrity": "sha512-P9J1HWYV/ajFr8uCqk5QixwiRKmB1wOamgS0e+o2Z4A44Ej2+thFVRLG/eA7qprx88XXhnV5Bl8LHXTURpzB3Q==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz", + "integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==", "dev": true }, "undici-types": { @@ -29128,9 +29128,9 @@ "requires": {} }, "picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "peer": true } @@ -29214,9 +29214,9 @@ }, "dependencies": { "picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true } } @@ -29306,9 +29306,9 @@ } }, "undici": { - "version": "6.24.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.0.tgz", - "integrity": "sha512-lVLNosgqo5EkGqh5XUDhGfsMSoO8K0BAN0TyJLvwNRSl4xWGZlCVYsAIpa/OpA3TvmnM01GWcoKmc3ZWo5wKKA==", + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", + "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==", "dev": true } } @@ -29601,9 +29601,9 @@ "dev": true }, "yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "dev": true, "peer": true }, From 38d7cae1bc4ac4c4ddec8dc954785d77401cfe16 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 26 Mar 2026 12:15:38 +0100 Subject: [PATCH 109/339] update some dependencies and ran npm-update. --- package-lock.json | 16466 +++++++------------------------------------- package.json | 10 +- 2 files changed, 2553 insertions(+), 13923 deletions(-) diff --git a/package-lock.json b/package-lock.json index d80746f..f5c267a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,7 +1,7 @@ { "name": "obsidian-livesync", "version": "0.25.54", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -49,9 +49,9 @@ "@types/transform-pouch": "^1.0.6", "@typescript-eslint/eslint-plugin": "8.56.1", "@typescript-eslint/parser": "8.56.1", - "@vitest/browser": "^4.0.16", - "@vitest/browser-playwright": "^4.0.16", - "@vitest/coverage-v8": "^4.0.16", + "@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", @@ -90,20 +90,11 @@ "typescript": "5.9.3", "vite": "^7.3.1", "vite-plugin-istanbul": "^8.0.0", - "vitest": "^4.0.16", - "webdriverio": "^9.24.0", + "vitest": "^4.1.1", + "webdriverio": "^9.27.0", "yaml": "^2.8.2" } }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@aws-crypto/crc32": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", @@ -133,6 +124,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", "dependencies": { "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", @@ -146,6 +138,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -157,6 +150,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" @@ -169,6 +163,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" @@ -248,6 +243,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" } @@ -256,6 +252,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", @@ -266,6 +263,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -277,6 +275,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" @@ -289,6 +288,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" @@ -298,65 +298,65 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.983.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.983.0.tgz", - "integrity": "sha512-V40PT2irPh3lj+Z95tZI6batVrjaTrWEOXRNVBuoZSgpM3Ak1jiE9ZXwVLkMcbb9/GH4xVpB3EsGM7gbxmgFLQ==", + "version": "3.1017.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1017.0.tgz", + "integrity": "sha512-WmmPn2NEfkxxzDA0D7rlf3f32gqmqpaTABhlz4EnZbg/RfNWaOu3ecaI5xY0ragrLhvPB+1aPN9GRDnivJukvg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/credential-provider-node": "^3.972.5", - "@aws-sdk/middleware-bucket-endpoint": "^3.972.3", - "@aws-sdk/middleware-expect-continue": "^3.972.3", - "@aws-sdk/middleware-flexible-checksums": "^3.972.4", - "@aws-sdk/middleware-host-header": "^3.972.3", - "@aws-sdk/middleware-location-constraint": "^3.972.3", - "@aws-sdk/middleware-logger": "^3.972.3", - "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-sdk-s3": "^3.972.6", - "@aws-sdk/middleware-ssec": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.6", - "@aws-sdk/region-config-resolver": "^3.972.3", - "@aws-sdk/signature-v4-multi-region": "3.983.0", - "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.983.0", - "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.4", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.0", - "@smithy/eventstream-serde-browser": "^4.2.8", - "@smithy/eventstream-serde-config-resolver": "^4.3.8", - "@smithy/eventstream-serde-node": "^4.2.8", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-blob-browser": "^4.2.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/hash-stream-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/md5-js": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.12", - "@smithy/middleware-retry": "^4.4.29", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.28", - "@smithy/util-defaults-mode-node": "^4.2.31", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-stream": "^4.5.10", - "@smithy/util-utf8": "^4.2.0", - "@smithy/util-waiter": "^4.2.8", + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/credential-provider-node": "^3.972.25", + "@aws-sdk/middleware-bucket-endpoint": "^3.972.8", + "@aws-sdk/middleware-expect-continue": "^3.972.8", + "@aws-sdk/middleware-flexible-checksums": "^3.974.4", + "@aws-sdk/middleware-host-header": "^3.972.8", + "@aws-sdk/middleware-location-constraint": "^3.972.8", + "@aws-sdk/middleware-logger": "^3.972.8", + "@aws-sdk/middleware-recursion-detection": "^3.972.8", + "@aws-sdk/middleware-sdk-s3": "^3.972.25", + "@aws-sdk/middleware-ssec": "^3.972.8", + "@aws-sdk/middleware-user-agent": "^3.972.25", + "@aws-sdk/region-config-resolver": "^3.972.9", + "@aws-sdk/signature-v4-multi-region": "^3.996.13", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@aws-sdk/util-user-agent-browser": "^3.972.8", + "@aws-sdk/util-user-agent-node": "^3.973.11", + "@smithy/config-resolver": "^4.4.13", + "@smithy/core": "^3.23.12", + "@smithy/eventstream-serde-browser": "^4.2.12", + "@smithy/eventstream-serde-config-resolver": "^4.3.12", + "@smithy/eventstream-serde-node": "^4.2.12", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/hash-blob-browser": "^4.2.13", + "@smithy/hash-node": "^4.2.12", + "@smithy/hash-stream-node": "^4.2.12", + "@smithy/invalid-dependency": "^4.2.12", + "@smithy/md5-js": "^4.2.12", + "@smithy/middleware-content-length": "^4.2.12", + "@smithy/middleware-endpoint": "^4.4.27", + "@smithy/middleware-retry": "^4.4.44", + "@smithy/middleware-serde": "^4.2.15", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/node-http-handler": "^4.5.0", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.7", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.43", + "@smithy/util-defaults-mode-node": "^4.2.47", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.12", + "@smithy/util-stream": "^4.5.20", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.2.13", "tslib": "^2.6.2" }, "engines": { @@ -364,23 +364,23 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.973.15", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.15.tgz", - "integrity": "sha512-AlC0oQ1/mdJ8vCIqu524j5RB7M8i8E24bbkZmya1CuiQxkY7SdIZAyw7NDNMGaNINQFq/8oGRMX0HeOfCVsl/A==", + "version": "3.973.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.24.tgz", + "integrity": "sha512-vvf82RYQu2GidWAuQq+uIzaPz9V0gSCXVqdVzRosgl5rXcspXOpSD3wFreGGW6AYymPr97Z69kjVnLePBxloDw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.4", - "@aws-sdk/xml-builder": "^3.972.8", - "@smithy/core": "^3.23.6", - "@smithy/node-config-provider": "^4.3.10", - "@smithy/property-provider": "^4.2.10", - "@smithy/protocol-http": "^5.3.10", - "@smithy/signature-v4": "^5.3.10", - "@smithy/smithy-client": "^4.12.0", - "@smithy/types": "^4.13.0", - "@smithy/util-base64": "^4.3.1", - "@smithy/util-middleware": "^4.2.10", - "@smithy/util-utf8": "^4.2.1", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/xml-builder": "^3.972.15", + "@smithy/core": "^3.23.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/signature-v4": "^5.3.12", + "@smithy/smithy-client": "^4.12.7", + "@smithy/types": "^4.13.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -388,12 +388,12 @@ } }, "node_modules/@aws-sdk/crc64-nvme": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.3.tgz", - "integrity": "sha512-UExeK+EFiq5LAcbHm96CQLSia+5pvpUVSAsVApscBzayb7/6dJBJKwV4/onsk4VbWSmqxDMcfuTD+pC4RxgZHg==", + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.5.tgz", + "integrity": "sha512-2VbTstbjKdT+yKi8m7b3a9CiVac+pL/IY2PHJwsaGkkHmuuqkJZIErPck1h6P3T9ghQMLSdMPyW6Qp7Di5swFg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -401,15 +401,15 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.13.tgz", - "integrity": "sha512-6ljXKIQ22WFKyIs1jbORIkGanySBHaPPTOI4OxACP5WXgbcR0nDYfqNJfXEGwCK7IzHdNbCSFsNKKs0qCexR8Q==", + "version": "3.972.22", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.22.tgz", + "integrity": "sha512-cXp0VTDWT76p3hyK5D51yIKEfpf6/zsUvMfaB8CkyqadJxMQ8SbEeVroregmDlZbtG31wkj9ei0WnftmieggLg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/types": "^3.973.4", - "@smithy/property-provider": "^4.2.10", - "@smithy/types": "^4.13.0", + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -417,20 +417,20 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.15", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.15.tgz", - "integrity": "sha512-dJuSTreu/T8f24SHDNTjd7eQ4rabr0TzPh2UTCwYexQtzG3nTDKm1e5eIdhiroTMDkPEJeY+WPkA6F9wod/20A==", + "version": "3.972.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.24.tgz", + "integrity": "sha512-h694K7+tRuepSRJr09wTvQfaEnjzsKZ5s7fbESrVds02GT/QzViJ94/HCNwM7bUfFxqpPXHxulZfL6Cou0dwPg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/types": "^3.973.4", - "@smithy/fetch-http-handler": "^5.3.11", - "@smithy/node-http-handler": "^4.4.12", - "@smithy/property-provider": "^4.2.10", - "@smithy/protocol-http": "^5.3.10", - "@smithy/smithy-client": "^4.12.0", - "@smithy/types": "^4.13.0", - "@smithy/util-stream": "^4.5.15", + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/types": "^3.973.6", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/node-http-handler": "^4.5.0", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.7", + "@smithy/types": "^4.13.1", + "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" }, "engines": { @@ -438,24 +438,24 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.13.tgz", - "integrity": "sha512-JKSoGb7XeabZLBJptpqoZIFbROUIS65NuQnEHGOpuT9GuuZwag2qciKANiDLFiYk4u8nSrJC9JIOnWKVvPVjeA==", + "version": "3.972.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.24.tgz", + "integrity": "sha512-O46fFmv0RDFWiWEA9/e6oW92BnsyAXuEgTTasxHligjn2RCr9L/DK773m/NoFaL3ZdNAUz8WxgxunleMnHAkeQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/credential-provider-env": "^3.972.13", - "@aws-sdk/credential-provider-http": "^3.972.15", - "@aws-sdk/credential-provider-login": "^3.972.13", - "@aws-sdk/credential-provider-process": "^3.972.13", - "@aws-sdk/credential-provider-sso": "^3.972.13", - "@aws-sdk/credential-provider-web-identity": "^3.972.13", - "@aws-sdk/nested-clients": "^3.996.3", - "@aws-sdk/types": "^3.973.4", - "@smithy/credential-provider-imds": "^4.2.10", - "@smithy/property-provider": "^4.2.10", - "@smithy/shared-ini-file-loader": "^4.4.5", - "@smithy/types": "^4.13.0", + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/credential-provider-env": "^3.972.22", + "@aws-sdk/credential-provider-http": "^3.972.24", + "@aws-sdk/credential-provider-login": "^3.972.24", + "@aws-sdk/credential-provider-process": "^3.972.22", + "@aws-sdk/credential-provider-sso": "^3.972.24", + "@aws-sdk/credential-provider-web-identity": "^3.972.24", + "@aws-sdk/nested-clients": "^3.996.14", + "@aws-sdk/types": "^3.973.6", + "@smithy/credential-provider-imds": "^4.2.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -463,18 +463,18 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.13.tgz", - "integrity": "sha512-RtYcrxdnJHKY8MFQGLltCURcjuMjnaQpAxPE6+/QEdDHHItMKZgabRe/KScX737F9vJMQsmJy9EmMOkCnoC1JQ==", + "version": "3.972.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.24.tgz", + "integrity": "sha512-sIk8oa6AzDoUhxsR11svZESqvzGuXesw62Rl2oW6wguZx8i9cdGCvkFg+h5K7iucUZP8wyWibUbJMc+J66cu5g==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/nested-clients": "^3.996.3", - "@aws-sdk/types": "^3.973.4", - "@smithy/property-provider": "^4.2.10", - "@smithy/protocol-http": "^5.3.10", - "@smithy/shared-ini-file-loader": "^4.4.5", - "@smithy/types": "^4.13.0", + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/nested-clients": "^3.996.14", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -482,22 +482,22 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.14", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.14.tgz", - "integrity": "sha512-WqoC2aliIjQM/L3oFf6j+op/enT2i9Cc4UTxxMEKrJNECkq4/PlKE5BOjSYFcq6G9mz65EFbXJh7zOU4CvjSKQ==", + "version": "3.972.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.25.tgz", + "integrity": "sha512-m7dR0Dsva2P+VUpL+VkC0WwiDby5pgmWXkRVDB5rlwv0jXJrQJf7YMtCoM8Wjk0H9jPeCYOxOXXcIgp/qp5Alg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.13", - "@aws-sdk/credential-provider-http": "^3.972.15", - "@aws-sdk/credential-provider-ini": "^3.972.13", - "@aws-sdk/credential-provider-process": "^3.972.13", - "@aws-sdk/credential-provider-sso": "^3.972.13", - "@aws-sdk/credential-provider-web-identity": "^3.972.13", - "@aws-sdk/types": "^3.973.4", - "@smithy/credential-provider-imds": "^4.2.10", - "@smithy/property-provider": "^4.2.10", - "@smithy/shared-ini-file-loader": "^4.4.5", - "@smithy/types": "^4.13.0", + "@aws-sdk/credential-provider-env": "^3.972.22", + "@aws-sdk/credential-provider-http": "^3.972.24", + "@aws-sdk/credential-provider-ini": "^3.972.24", + "@aws-sdk/credential-provider-process": "^3.972.22", + "@aws-sdk/credential-provider-sso": "^3.972.24", + "@aws-sdk/credential-provider-web-identity": "^3.972.24", + "@aws-sdk/types": "^3.973.6", + "@smithy/credential-provider-imds": "^4.2.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -505,16 +505,16 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.13.tgz", - "integrity": "sha512-rsRG0LQA4VR+jnDyuqtXi2CePYSmfm5GNL9KxiW8DSe25YwJSr06W8TdUfONAC+rjsTI+aIH2rBGG5FjMeANrw==", + "version": "3.972.22", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.22.tgz", + "integrity": "sha512-Os32s8/4gTZjBk5BtoS/cuTILaj+K72d0dVG7TCJX/fC4598cxwLDmf1AEHEpER5oL3K//yETjvFaz0V8oO5Xw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/types": "^3.973.4", - "@smithy/property-provider": "^4.2.10", - "@smithy/shared-ini-file-loader": "^4.4.5", - "@smithy/types": "^4.13.0", + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -522,18 +522,18 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.13.tgz", - "integrity": "sha512-fr0UU1wx8kNHDhTQBXioc/YviSW8iXuAxHvnH7eQUtn8F8o/FU3uu6EUMvAQgyvn7Ne5QFnC0Cj0BFlwCk+RFw==", + "version": "3.972.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.24.tgz", + "integrity": "sha512-PaFv7snEfypU2yXkpvfyWgddEbDLtgVe51wdZlinhc2doubBjUzJZZpgwuF2Jenl1FBydMhNpMjD6SBUM3qdSA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/nested-clients": "^3.996.3", - "@aws-sdk/token-providers": "3.999.0", - "@aws-sdk/types": "^3.973.4", - "@smithy/property-provider": "^4.2.10", - "@smithy/shared-ini-file-loader": "^4.4.5", - "@smithy/types": "^4.13.0", + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/nested-clients": "^3.996.14", + "@aws-sdk/token-providers": "3.1015.0", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -541,17 +541,17 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.13.tgz", - "integrity": "sha512-a6iFMh1pgUH0TdcouBppLJUfPM7Yd3R9S1xFodPtCRoLqCz2RQFA3qjA8x4112PVYXEd4/pHX2eihapq39w0rA==", + "version": "3.972.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.24.tgz", + "integrity": "sha512-J6H4R1nvr3uBTqD/EeIPAskrBtET4WFfNhpFySr2xW7bVZOXpQfPjrLSIx65jcNjBmLXzWq8QFLdVoGxiGG/SA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/nested-clients": "^3.996.3", - "@aws-sdk/types": "^3.973.4", - "@smithy/property-provider": "^4.2.10", - "@smithy/shared-ini-file-loader": "^4.4.5", - "@smithy/types": "^4.13.0", + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/nested-clients": "^3.996.14", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -559,17 +559,17 @@ } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.3.tgz", - "integrity": "sha512-fmbgWYirF67YF1GfD7cg5N6HHQ96EyRNx/rDIrTF277/zTWVuPI2qS/ZHgofwR1NZPe/NWvoppflQY01LrbVLg==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.8.tgz", + "integrity": "sha512-WR525Rr2QJSETa9a050isktyWi/4yIGcmY3BQ1kpHqb0LqUglQHCS8R27dTJxxWNZvQ0RVGtEZjTCbZJpyF3Aw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-arn-parser": "^3.972.2", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-config-provider": "^4.2.0", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-arn-parser": "^3.972.3", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -577,14 +577,14 @@ } }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.3.tgz", - "integrity": "sha512-4msC33RZsXQpUKR5QR4HnvBSNCPLGHmB55oDiROqqgyOc+TOfVu2xgi5goA7ms6MdZLeEh2905UfWMnMMF4mRg==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.8.tgz", + "integrity": "sha512-5DTBTiotEES1e2jOHAq//zyzCjeMB78lEHd35u15qnrid4Nxm7diqIf9fQQ3Ov0ChH1V3Vvt13thOnrACmfGVQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.1", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", + "@aws-sdk/types": "^3.973.6", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -592,24 +592,24 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.973.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.973.1.tgz", - "integrity": "sha512-QLXsxsI6VW8LuGK+/yx699wzqP/NMCGk/hSGP+qtB+Lcff+23UlbahyouLlk+nfT7Iu021SkXBhnAuVd6IZcPw==", + "version": "3.974.4", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.4.tgz", + "integrity": "sha512-fhCbZXPAyy8btnNbnBlR7Cc1nD54cETSvGn2wey71ehsM89AKPO8Dpco9DBAAgvrUdLrdHQepBXcyX4vxC5OwA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/crc64-nvme": "^3.972.3", - "@aws-sdk/types": "^3.973.4", - "@smithy/is-array-buffer": "^4.2.1", - "@smithy/node-config-provider": "^4.3.10", - "@smithy/protocol-http": "^5.3.10", - "@smithy/types": "^4.13.0", - "@smithy/util-middleware": "^4.2.10", - "@smithy/util-stream": "^4.5.15", - "@smithy/util-utf8": "^4.2.1", + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/crc64-nvme": "^3.972.5", + "@aws-sdk/types": "^3.973.6", + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-stream": "^4.5.20", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -617,14 +617,14 @@ } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.6.tgz", - "integrity": "sha512-5XHwjPH1lHB+1q4bfC7T8Z5zZrZXfaLcjSMwTd1HPSPrCmPFMbg3UQ5vgNWcVj0xoX4HWqTGkSf2byrjlnRg5w==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.8.tgz", + "integrity": "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.4", - "@smithy/protocol-http": "^5.3.10", - "@smithy/types": "^4.13.0", + "@aws-sdk/types": "^3.973.6", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -632,13 +632,13 @@ } }, "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.3.tgz", - "integrity": "sha512-nIg64CVrsXp67vbK0U1/Is8rik3huS3QkRHn2DRDx4NldrEFMgdkZGI/+cZMKD9k4YOS110Dfu21KZLHrFA/1g==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.8.tgz", + "integrity": "sha512-KaUoFuoFPziIa98DSQsTPeke1gvGXlc5ZGMhy+b+nLxZ4A7jmJgLzjEF95l8aOQN2T/qlPP3MrAyELm8ExXucw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.1", - "@smithy/types": "^4.12.0", + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -646,13 +646,13 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.6.tgz", - "integrity": "sha512-iFnaMFMQdljAPrvsCVKYltPt2j40LQqukAbXvW7v0aL5I+1GO7bZ/W8m12WxW3gwyK5p5u1WlHg8TSAizC5cZw==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.8.tgz", + "integrity": "sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.4", - "@smithy/types": "^4.13.0", + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -660,15 +660,15 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.6.tgz", - "integrity": "sha512-dY4v3of5EEMvik6+UDwQ96KfUFDk8m1oZDdkSc5lwi4o7rFrjnv0A+yTV+gu230iybQZnKgDLg/rt2P3H+Vscw==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.8.tgz", + "integrity": "sha512-BnnvYs2ZEpdlmZ2PNlV2ZyQ8j8AEkMTjN79y/YA475ER1ByFYrkVR85qmhni8oeTaJcDqbx364wDpitDAA/wCA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.4", + "@aws-sdk/types": "^3.973.6", "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.10", - "@smithy/types": "^4.13.0", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -676,24 +676,24 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.15", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.15.tgz", - "integrity": "sha512-WDLgssevOU5BFx1s8jA7jj6cE5HuImz28sy9jKOaVtz0AW1lYqSzotzdyiybFaBcQTs5zxXOb2pUfyMxgEKY3Q==", + "version": "3.972.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.25.tgz", + "integrity": "sha512-4xJL7O+XkhbSkT4yAYshkAww+mxJvtGQneNHH0MOpe+w8Vo2z87M9z06UO3G6zPM2c3Ef2yKczvZpTgdArMHfg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/types": "^3.973.4", - "@aws-sdk/util-arn-parser": "^3.972.2", - "@smithy/core": "^3.23.6", - "@smithy/node-config-provider": "^4.3.10", - "@smithy/protocol-http": "^5.3.10", - "@smithy/signature-v4": "^5.3.10", - "@smithy/smithy-client": "^4.12.0", - "@smithy/types": "^4.13.0", - "@smithy/util-config-provider": "^4.2.1", - "@smithy/util-middleware": "^4.2.10", - "@smithy/util-stream": "^4.5.15", - "@smithy/util-utf8": "^4.2.1", + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-arn-parser": "^3.972.3", + "@smithy/core": "^3.23.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/signature-v4": "^5.3.12", + "@smithy/smithy-client": "^4.12.7", + "@smithy/types": "^4.13.1", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-stream": "^4.5.20", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -701,13 +701,13 @@ } }, "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.3.tgz", - "integrity": "sha512-dU6kDuULN3o3jEHcjm0c4zWJlY1zWVkjG9NPe9qxYLLpcbdj5kRYBS2DdWYD+1B9f910DezRuws7xDEqKkHQIg==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.8.tgz", + "integrity": "sha512-wqlK0yO/TxEC2UsY9wIlqeeutF6jjLe0f96Pbm40XscTo57nImUk9lBcw0dPgsm0sppFtAkSlDrfpK+pC30Wqw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.1", - "@smithy/types": "^4.12.0", + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -715,33 +715,18 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.15", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.15.tgz", - "integrity": "sha512-ABlFVcIMmuRAwBT+8q5abAxOr7WmaINirDJBnqGY5b5jSDo00UMlg/G4a0xoAgwm6oAECeJcwkvDlxDwKf58fQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/types": "^3.973.4", - "@aws-sdk/util-endpoints": "^3.996.3", - "@smithy/core": "^3.23.6", - "@smithy/protocol-http": "^5.3.10", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/util-endpoints": { - "version": "3.996.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz", - "integrity": "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==", + "version": "3.972.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.25.tgz", + "integrity": "sha512-QxiMPofvOt8SwSynTOmuZfvvPM1S9QfkESBxB22NMHTRXCJhR5BygLl8IXfC4jELiisQgwsgUby21GtXfX3f/g==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/core": "^3.973.24", "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@smithy/core": "^3.23.12", + "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", - "@smithy/url-parser": "^4.2.12", - "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-retry": "^4.2.12", "tslib": "^2.6.2" }, "engines": { @@ -749,64 +734,48 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.996.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.3.tgz", - "integrity": "sha512-AU5TY1V29xqwg/MxmA2odwysTez+ccFAhmfRJk+QZT5HNv90UTA9qKd1J9THlsQkvmH7HWTEV1lDNxkQO5PzNw==", + "version": "3.996.14", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.14.tgz", + "integrity": "sha512-fSESKvh1VbfjtV3QMnRkCPZWkUbQof6T/DOpiLp33yP2wA+rbwwnZeG3XT3Ekljgw2I8X4XaQPnw+zSR8yxJ5Q==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/middleware-host-header": "^3.972.6", - "@aws-sdk/middleware-logger": "^3.972.6", - "@aws-sdk/middleware-recursion-detection": "^3.972.6", - "@aws-sdk/middleware-user-agent": "^3.972.15", - "@aws-sdk/region-config-resolver": "^3.972.6", - "@aws-sdk/types": "^3.973.4", - "@aws-sdk/util-endpoints": "^3.996.3", - "@aws-sdk/util-user-agent-browser": "^3.972.6", - "@aws-sdk/util-user-agent-node": "^3.973.0", - "@smithy/config-resolver": "^4.4.9", - "@smithy/core": "^3.23.6", - "@smithy/fetch-http-handler": "^5.3.11", - "@smithy/hash-node": "^4.2.10", - "@smithy/invalid-dependency": "^4.2.10", - "@smithy/middleware-content-length": "^4.2.10", - "@smithy/middleware-endpoint": "^4.4.20", - "@smithy/middleware-retry": "^4.4.37", - "@smithy/middleware-serde": "^4.2.11", - "@smithy/middleware-stack": "^4.2.10", - "@smithy/node-config-provider": "^4.3.10", - "@smithy/node-http-handler": "^4.4.12", - "@smithy/protocol-http": "^5.3.10", - "@smithy/smithy-client": "^4.12.0", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.10", - "@smithy/util-base64": "^4.3.1", - "@smithy/util-body-length-browser": "^4.2.1", - "@smithy/util-body-length-node": "^4.2.2", - "@smithy/util-defaults-mode-browser": "^4.3.36", - "@smithy/util-defaults-mode-node": "^4.2.39", - "@smithy/util-endpoints": "^3.3.1", - "@smithy/util-middleware": "^4.2.10", - "@smithy/util-retry": "^4.2.10", - "@smithy/util-utf8": "^4.2.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": { - "version": "3.996.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz", - "integrity": "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==", - "license": "Apache-2.0", - "dependencies": { + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/middleware-host-header": "^3.972.8", + "@aws-sdk/middleware-logger": "^3.972.8", + "@aws-sdk/middleware-recursion-detection": "^3.972.8", + "@aws-sdk/middleware-user-agent": "^3.972.25", + "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@aws-sdk/util-user-agent-browser": "^3.972.8", + "@aws-sdk/util-user-agent-node": "^3.973.11", + "@smithy/config-resolver": "^4.4.13", + "@smithy/core": "^3.23.12", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/hash-node": "^4.2.12", + "@smithy/invalid-dependency": "^4.2.12", + "@smithy/middleware-content-length": "^4.2.12", + "@smithy/middleware-endpoint": "^4.4.27", + "@smithy/middleware-retry": "^4.4.44", + "@smithy/middleware-serde": "^4.2.15", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/node-http-handler": "^4.5.0", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.43", + "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.12", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -814,15 +783,15 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.6.tgz", - "integrity": "sha512-Aa5PusHLXAqLTX1UKDvI3pHQJtIsF7Q+3turCHqfz/1F61/zDMWfbTC8evjhrrYVAtz9Vsv3SJ/waSUeu7B6gw==", + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.9.tgz", + "integrity": "sha512-eQ+dFU05ZRC/lC2XpYlYSPlXtX3VT8sn5toxN2Fv7EXlMoA2p9V7vUBKqHunfD4TRLpxUq8Y8Ol/nCqiv327Ng==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.4", - "@smithy/config-resolver": "^4.4.9", - "@smithy/node-config-provider": "^4.3.10", - "@smithy/types": "^4.13.0", + "@aws-sdk/types": "^3.973.6", + "@smithy/config-resolver": "^4.4.13", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -830,16 +799,16 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.983.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.983.0.tgz", - "integrity": "sha512-11FCcxI/WKRufKDdPgKPXtrhjDArhkOPb4mf66rICZUnPHlD8Cb7cjZZS/eFC+iuwoHBosrxo0hYsvK3s7DxGw==", + "version": "3.996.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.13.tgz", + "integrity": "sha512-7j8rOFHHq4e9McCSuWBmBSADriW5CjPUem4inckRh/cyQGaijBwDbkNbVTgDVDWqFo29SoVVUfI6HCOnck6HZw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "^3.972.6", - "@aws-sdk/types": "^3.973.1", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/types": "^4.12.0", + "@aws-sdk/middleware-sdk-s3": "^3.972.25", + "@aws-sdk/types": "^3.973.6", + "@smithy/protocol-http": "^5.3.12", + "@smithy/signature-v4": "^5.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -847,17 +816,17 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.999.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.999.0.tgz", - "integrity": "sha512-cx0hHUlgXULfykx4rdu/ciNAJaa3AL5xz3rieCz7NKJ68MJwlj3664Y8WR5MGgxfyYJBdamnkjNSx5Kekuc0cg==", + "version": "3.1015.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1015.0.tgz", + "integrity": "sha512-3OSD4y110nisRhHzFOjoEeHU4GQL4KpzkX9PxzWaiZe0Yg2+thZKM0Pn9DjYwezH5JYfh/K++xK/SE0IHGrmCQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/nested-clients": "^3.996.3", - "@aws-sdk/types": "^3.973.4", - "@smithy/property-provider": "^4.2.10", - "@smithy/shared-ini-file-loader": "^4.4.5", - "@smithy/types": "^4.13.0", + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/nested-clients": "^3.996.14", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -878,9 +847,9 @@ } }, "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.2.tgz", - "integrity": "sha512-VkykWbqMjlSgBFDyrY3nOSqupMc6ivXuGmvci6Q3NnLq5kC+mKQe2QBZ4nrWRE/jqOxeFP2uYzLtwncYYcvQDg==", + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.3.tgz", + "integrity": "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -890,15 +859,15 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.983.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.983.0.tgz", - "integrity": "sha512-t/VbL2X3gvDEjC4gdySOeFFOZGQEBKwa23pRHeB7hBLBZ119BB/2OEFtTFWKyp3bnMQgxpeVeGS7/hxk6wpKJw==", + "version": "3.996.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz", + "integrity": "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.1", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-endpoints": "^3.3.3", "tslib": "^2.6.2" }, "engines": { @@ -906,38 +875,40 @@ } }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.568.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.568.0.tgz", - "integrity": "sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==", + "version": "3.965.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", + "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=20.0.0" } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.6.tgz", - "integrity": "sha512-Fwr/llD6GOrFgQnKaI2glhohdGuBDfHfora6iG9qsBBBR8xv1SdCSwbtf5CWlUdCw5X7g76G/9Hf0Inh0EmoxA==", + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.8.tgz", + "integrity": "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.4", - "@smithy/types": "^4.13.0", + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.973.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.0.tgz", - "integrity": "sha512-A9J2G4Nf236e9GpaC1JnA8wRn6u6GjnOXiTwBLA6NUJhlBTIGfrTy+K1IazmF8y+4OFdW3O5TZlhyspJMqiqjA==", + "version": "3.973.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.11.tgz", + "integrity": "sha512-1qdXbXo2s5MMLpUvw00284LsbhtlQ4ul7Zzdn5n+7p4WVgCMLqhxImpHIrjSoc72E/fyc4Wq8dLtUld2Gsh+lA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.15", - "@aws-sdk/types": "^3.973.4", - "@smithy/node-config-provider": "^4.3.10", - "@smithy/types": "^4.13.0", + "@aws-sdk/middleware-user-agent": "^3.972.25", + "@aws-sdk/types": "^3.973.6", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -953,13 +924,13 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.11", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.11.tgz", - "integrity": "sha512-iitV/gZKQMvY9d7ovmyFnFuTHbBAtrmLnvaSb/3X8vOKyevwtpmEtyc8AdhVWZe0pI/1GsHxlEvQeOePFzy7KQ==", + "version": "3.972.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.15.tgz", + "integrity": "sha512-PxMRlCFNiQnke9YR29vjFQwz4jq+6Q04rOVFeTDR2K7Qpv9h9FOWOxG+zJjageimYbWqE3bTuLjmryWHAWbvaA==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.13.1", - "fast-xml-parser": "5.4.1", + "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" }, "engines": { @@ -967,9 +938,9 @@ } }, "node_modules/@aws/lambda-invoke-store": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", - "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", "license": "Apache-2.0", "engines": { "node": ">=18.0.0" @@ -990,6 +961,13 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/code-frame/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/@babel/compat-data": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", @@ -1212,9 +1190,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1278,6 +1256,13 @@ "node": ">=18" } }, + "node_modules/@blazediff/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@blazediff/core/-/core-1.9.1.tgz", + "integrity": "sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==", + "dev": true, + "license": "MIT" + }, "node_modules/@chainsafe/as-chacha20poly1305": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@chainsafe/as-chacha20poly1305/-/as-chacha20poly1305-0.1.0.tgz", @@ -1331,14 +1316,14 @@ } }, "node_modules/@chialab/esbuild-plugin-meta-url": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@chialab/esbuild-plugin-meta-url/-/esbuild-plugin-meta-url-0.19.0.tgz", - "integrity": "sha512-VzXuFNaUW9v4bQeFVFVM/KL4/hB/xwMNGGxy1E8CHZAuA+6xHGSqJK7gf17vyWC+GulwIEfzTqfIHU8H8Ic/Zg==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@chialab/esbuild-plugin-meta-url/-/esbuild-plugin-meta-url-0.19.1.tgz", + "integrity": "sha512-psYdhXG5CTA16PkOc4RhWj7XJQWONXJIrRTp3xkxKW0A7d0n/B0W+TABMR3zohboyoC6Uqv1zO8jf62zx2Xh6Q==", "dev": true, "license": "MIT", "dependencies": { - "@chialab/esbuild-rna": "^0.19.0", - "@chialab/estransform": "^0.19.0", + "@chialab/esbuild-rna": "^0.19.1", + "@chialab/estransform": "^0.19.1", "mime-types": "^2.1.35" }, "engines": { @@ -1346,38 +1331,38 @@ } }, "node_modules/@chialab/esbuild-plugin-worker": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@chialab/esbuild-plugin-worker/-/esbuild-plugin-worker-0.19.0.tgz", - "integrity": "sha512-atbjSLe9GcG1vMABKai0uwmfsZmSpdjSvLh9pn5CxnI4p57VqSxp5aW24hGV2wwTna78DhtP/spUFUlaWaqVgg==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@chialab/esbuild-plugin-worker/-/esbuild-plugin-worker-0.19.1.tgz", + "integrity": "sha512-eZeOMzPmT3LyEryS8GlUJ69NDcWEYT4JDHEYMAiAtNsN+ftFTSUkEeVFgP1zyebgmZApPc4Gdpo162nPBG2rSw==", "dev": true, "license": "MIT", "dependencies": { - "@chialab/esbuild-plugin-meta-url": "^0.19.0", - "@chialab/esbuild-rna": "^0.19.0", - "@chialab/estransform": "^0.19.0" + "@chialab/esbuild-plugin-meta-url": "^0.19.1", + "@chialab/esbuild-rna": "^0.19.1", + "@chialab/estransform": "^0.19.1" }, "engines": { "node": ">=18" } }, "node_modules/@chialab/esbuild-rna": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@chialab/esbuild-rna/-/esbuild-rna-0.19.0.tgz", - "integrity": "sha512-701y6cN/F9JEjMsJW7PcLxUq1kcReNJ69DTcw7TXLA5f5flBH7zZf+U0F/bwn3vQ2StszT8scTmlflPP4J8m3Q==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@chialab/esbuild-rna/-/esbuild-rna-0.19.1.tgz", + "integrity": "sha512-v8dpllvqWmYsAvDkfVRRqz1jwxUZyfLYAR0MgsiifnI+C95OPdV3qhubvwebav8s8YUVz2Jr6J8bWpU+GW7TfA==", "dev": true, "license": "MIT", "dependencies": { - "@chialab/estransform": "^0.19.0", - "@chialab/node-resolve": "^0.19.0" + "@chialab/estransform": "^0.19.1", + "@chialab/node-resolve": "^0.19.1" }, "engines": { "node": ">=18" } }, "node_modules/@chialab/estransform": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@chialab/estransform/-/estransform-0.19.0.tgz", - "integrity": "sha512-w6i/RZ3SddSCDIYZLkwnK+KAseCKW3BT627San3EbzH3NRY0Gn5RqXCjX9J/mNGe2FydwAevG7O605z50woWuA==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@chialab/estransform/-/estransform-0.19.1.tgz", + "integrity": "sha512-Op0TvQxnzdcnBriFUIjgg3V3MpOB9Cfs4S7TvIuypPegFOSvuFAOcPl5V02NJ9dyGoOc8W6ORbSldc5PYKhOCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1392,9 +1377,9 @@ } }, "node_modules/@chialab/node-resolve": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@chialab/node-resolve/-/node-resolve-0.19.0.tgz", - "integrity": "sha512-VaYOPbgIEVPu7ic+LAA8z2syEpEQhO1nLce0BSPs2eMubQ+mxSrH3RyXKH+CREhOnBWH3Rd3uuFZn2Rofypsbg==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@chialab/node-resolve/-/node-resolve-0.19.1.tgz", + "integrity": "sha512-J4i4YJNaFuYG6UWpum9y8XfICWsWxoCawy6HQtU2lDqp915oboxXvpZ3lBdA5Llb8VexCKQZYufY8QXPyzU62Q==", "dev": true, "license": "MIT", "engines": { @@ -1426,6 +1411,19 @@ "w3c-keyname": "^2.2.4" } }, + "node_modules/@dnsquery/dns-packet": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@dnsquery/dns-packet/-/dns-packet-6.1.1.tgz", + "integrity": "sha512-WXTuFvL3G+74SchFAtz3FgIYVOe196ycvGsMgvSH/8Goptb1qpIQtIuM4SOK9G9lhMWYpHxnXyy544ZhluFOew==", + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.4", + "utf8-codec": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", @@ -1898,13 +1896,13 @@ } }, "node_modules/@eslint/compat": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.2.tgz", - "integrity": "sha512-pR1DoD0h3HfF675QZx0xsyrsU8q70Z/plx7880NOhS02NuWLgBCOMDL787nUeQ7EWLkxv3bPQJaarjcPQb2Dwg==", + "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.0" + "@eslint/core": "^1.1.1" }, "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" @@ -1918,34 +1916,39 @@ } } }, - "node_modules/@eslint/compat/node_modules/@eslint/core": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz", - "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==", - "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/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^3.1.5" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/config-array/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/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@eslint/config-array/node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -1972,7 +1975,7 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/core": { + "node_modules/@eslint/config-helpers/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==", @@ -1985,10 +1988,23 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz", + "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==", + "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.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz", - "integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { @@ -1999,7 +2015,7 @@ "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", - "minimatch": "^3.1.3", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { @@ -2009,6 +2025,24 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/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/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -2023,9 +2057,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.3", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", - "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, "license": "MIT", "engines": { @@ -2059,6 +2093,19 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit/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/@ethersproject/bytes": { "version": "5.8.0", "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.8.0.tgz", @@ -2115,15 +2162,15 @@ } }, "node_modules/@firebase/ai": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-2.4.0.tgz", - "integrity": "sha512-YilG6AJ/nYpCKtxZyvEzBRAQv5bU+2tBOKX4Ps0rNNSdxN39aT37kGhjATbk1kq1z5Lq7mkWglw/ajAF3lOWUg==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-2.10.0.tgz", + "integrity": "sha512-1lI6HomyoO/8RSJb6ItyHLpHnB2z27m5F4aX/Vpi1nhwWoxdNjkq+6UQOykHyCE0KairojOE5qQ20i1tnF0nNA==", "license": "Apache-2.0", "dependencies": { "@firebase/app-check-interop-types": "0.3.3", - "@firebase/component": "0.7.0", + "@firebase/component": "0.7.2", "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "engines": { @@ -2135,15 +2182,15 @@ } }, "node_modules/@firebase/analytics": { - "version": "0.10.19", - "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.19.tgz", - "integrity": "sha512-3wU676fh60gaiVYQEEXsbGS4HbF2XsiBphyvvqDbtC1U4/dO4coshbYktcCHq+HFaGIK07iHOh4pME0hEq1fcg==", + "version": "0.10.21", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.21.tgz", + "integrity": "sha512-j2y2q65BlgLGB5Pwjhv/Jopw2X/TBTzvAtI5z/DSp56U4wBj7LfhBfzbdCtFPges+Wz0g55GdoawXibOH5jGng==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.0", - "@firebase/installations": "0.6.19", + "@firebase/component": "0.7.2", + "@firebase/installations": "0.6.21", "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2151,15 +2198,15 @@ } }, "node_modules/@firebase/analytics-compat": { - "version": "0.2.25", - "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.25.tgz", - "integrity": "sha512-fdzoaG0BEKbqksRDhmf4JoyZf16Wosrl0Y7tbZtJyVDOOwziE0vrFjmZuTdviL0yhak+Nco6rMsUUbkbD+qb6Q==", + "version": "0.2.27", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.27.tgz", + "integrity": "sha512-ZObpYpAxL6JfgH7GnvlDD0sbzGZ0o4nijV8skatV9ZX49hJtCYbFqaEcPYptT94rgX1KUoKEderC7/fa7hybtw==", "license": "Apache-2.0", "dependencies": { - "@firebase/analytics": "0.10.19", + "@firebase/analytics": "0.10.21", "@firebase/analytics-types": "0.8.3", - "@firebase/component": "0.7.0", - "@firebase/util": "1.13.0", + "@firebase/component": "0.7.2", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2173,15 +2220,15 @@ "license": "Apache-2.0" }, "node_modules/@firebase/app": { - "version": "0.14.4", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.4.tgz", - "integrity": "sha512-pUxEGmR+uu21OG/icAovjlu1fcYJzyVhhT0rsCrn+zi+nHtrS43Bp9KPn9KGa4NMspCUE++nkyiqziuIvJdwzw==", + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.10.tgz", + "integrity": "sha512-PlPhdtjgWUra+LImQTnXOUqUa/jcufZhizdR93ZjlQSS3ahCtDTG6pJw7j0OwFal18DQjICXfeVNsUUrcNisfA==", "license": "Apache-2.0", "peer": true, "dependencies": { - "@firebase/component": "0.7.0", + "@firebase/component": "0.7.2", "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "idb": "7.1.1", "tslib": "^2.1.0" }, @@ -2190,14 +2237,14 @@ } }, "node_modules/@firebase/app-check": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.11.0.tgz", - "integrity": "sha512-XAvALQayUMBJo58U/rxW02IhsesaxxfWVmVkauZvGEz3vOAjMEQnzFlyblqkc2iAaO82uJ2ZVyZv9XzPfxjJ6w==", + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.11.2.tgz", + "integrity": "sha512-jcXQVMHAQ5AEKzVD5C7s5fmAYeFOuN6lAJeNTgZK2B9aLnofWaJt8u1A8Idm8gpsBBYSaY3cVyeH5SWMOVPBLQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.0", + "@firebase/component": "0.7.2", "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "engines": { @@ -2208,16 +2255,16 @@ } }, "node_modules/@firebase/app-check-compat": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.4.0.tgz", - "integrity": "sha512-UfK2Q8RJNjYM/8MFORltZRG9lJj11k0nW84rrffiKvcJxLf1jf6IEjCIkCamykHE73C6BwqhVfhIBs69GXQV0g==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.4.2.tgz", + "integrity": "sha512-M91NhxqbSkI0ChkJWy69blC+rPr6HEgaeRllddSaU1pQ/7IiegeCQM9pPDIgvWnwnBSzKhUHpe6ro/jhJ+cvzw==", "license": "Apache-2.0", "dependencies": { - "@firebase/app-check": "0.11.0", + "@firebase/app-check": "0.11.2", "@firebase/app-check-types": "0.5.3", - "@firebase/component": "0.7.0", + "@firebase/component": "0.7.2", "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "engines": { @@ -2240,16 +2287,16 @@ "license": "Apache-2.0" }, "node_modules/@firebase/app-compat": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.4.tgz", - "integrity": "sha512-T7ifGmb+awJEcp542Ek4HtNfBxcBrnuk1ggUdqyFEdsXHdq7+wVlhvE6YukTL7NS8hIkEfL7TMAPx/uCNqt30g==", + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.10.tgz", + "integrity": "sha512-tFmBuZL0/v1h6eyKRgWI58ucft6dEJmAi9nhPUXoAW4ZbPSTlnsh31AuEwUoRTz+wwRk9gmgss9GZV05ZM9Kug==", "license": "Apache-2.0", "peer": true, "dependencies": { - "@firebase/app": "0.14.4", - "@firebase/component": "0.7.0", + "@firebase/app": "0.14.10", + "@firebase/component": "0.7.2", "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "engines": { @@ -2270,14 +2317,14 @@ "license": "ISC" }, "node_modules/@firebase/auth": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.11.0.tgz", - "integrity": "sha512-5j7+ua93X+IRcJ1oMDTClTo85l7Xe40WSkoJ+shzPrX7OISlVWLdE1mKC57PSD+/LfAbdhJmvKixINBw2ESK6w==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.12.2.tgz", + "integrity": "sha512-CZJL8V10Vzibs+pDTXdQF+hot1IigIoqF4a4lA/qr5Deo1srcefiyIfgg28B67Lk7IxZhwfJMuI+1bu2xBmV0A==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.0", + "@firebase/component": "0.7.2", "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "engines": { @@ -2285,7 +2332,7 @@ }, "peerDependencies": { "@firebase/app": "0.x", - "@react-native-async-storage/async-storage": "^1.18.1" + "@react-native-async-storage/async-storage": "^2.2.0" }, "peerDependenciesMeta": { "@react-native-async-storage/async-storage": { @@ -2294,15 +2341,15 @@ } }, "node_modules/@firebase/auth-compat": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.6.0.tgz", - "integrity": "sha512-J0lGSxXlG/lYVi45wbpPhcWiWUMXevY4fvLZsN1GHh+po7TZVng+figdHBVhFheaiipU8HZyc7ljw1jNojM2nw==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.6.4.tgz", + "integrity": "sha512-2pj8m/hnqXvMLfC0Mk+fORVTM5DQPkS6l8JpMgtoAWGVgCmYnoWdFMaNWtKbmCxBEyvMA3FlnCJyzrUSMWTfuA==", "license": "Apache-2.0", "dependencies": { - "@firebase/auth": "1.11.0", + "@firebase/auth": "1.12.2", "@firebase/auth-types": "0.13.0", - "@firebase/component": "0.7.0", - "@firebase/util": "1.13.0", + "@firebase/component": "0.7.2", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "engines": { @@ -2329,12 +2376,12 @@ } }, "node_modules/@firebase/component": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.0.tgz", - "integrity": "sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.2.tgz", + "integrity": "sha512-iyVDGc6Vjx7Rm0cAdccLH/NG6fADsgJak/XW9IA2lPf8AjIlsemOpFGKczYyPHxm4rnKdR8z6sK4+KEC7NwmEg==", "license": "Apache-2.0", "dependencies": { - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "engines": { @@ -2342,15 +2389,15 @@ } }, "node_modules/@firebase/data-connect": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.11.tgz", - "integrity": "sha512-G258eLzAD6im9Bsw+Qm1Z+P4x0PGNQ45yeUuuqe5M9B1rn0RJvvsQCRHXgE52Z+n9+WX1OJd/crcuunvOGc7Vw==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.5.0.tgz", + "integrity": "sha512-G3GYHpWNJJ95502RQLApzw0jaG3pScHl+J/2MdxIuB51xtHnkRL6KvIAP3fFF1drUewWJHOnDA1U+q4Evf3KSw==", "license": "Apache-2.0", "dependencies": { "@firebase/auth-interop-types": "0.2.4", - "@firebase/component": "0.7.0", + "@firebase/component": "0.7.2", "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2358,16 +2405,16 @@ } }, "node_modules/@firebase/database": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.0.tgz", - "integrity": "sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.2.tgz", + "integrity": "sha512-lP96CMjMPy/+d1d9qaaHjHHdzdwvEOuyyLq9ehX89e2XMKwS1jHNzYBO+42bdSumuj5ukPbmnFtViZu8YOMT+w==", "license": "Apache-2.0", "dependencies": { "@firebase/app-check-interop-types": "0.3.3", "@firebase/auth-interop-types": "0.2.4", - "@firebase/component": "0.7.0", + "@firebase/component": "0.7.2", "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "faye-websocket": "0.11.4", "tslib": "^2.1.0" }, @@ -2376,16 +2423,16 @@ } }, "node_modules/@firebase/database-compat": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.0.tgz", - "integrity": "sha512-8nYc43RqxScsePVd1qe1xxvWNf0OBnbwHxmXJ7MHSuuTVYFO3eLyLW3PiCKJ9fHnmIz4p4LbieXwz+qtr9PZDg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.2.tgz", + "integrity": "sha512-j4A6IhVZbgxAzT6gJJC2PfOxYCK9SrDrUO7nTM4EscTYtKkAkzsbKoCnDdjFapQfnsncvPWjqVTr/0PffUwg3g==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.0", - "@firebase/database": "1.1.0", - "@firebase/database-types": "1.0.16", + "@firebase/component": "0.7.2", + "@firebase/database": "1.1.2", + "@firebase/database-types": "1.0.18", "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "engines": { @@ -2393,24 +2440,24 @@ } }, "node_modules/@firebase/database-types": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.16.tgz", - "integrity": "sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw==", + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.18.tgz", + "integrity": "sha512-yOY8IC2go9lfbVDMiy2ATun4EB2AFwocPaQADwMN/RHRUAZSM4rlAV7PGbWPSG/YhkJ2A9xQAiAENgSua9G5Fg==", "license": "Apache-2.0", "dependencies": { "@firebase/app-types": "0.9.3", - "@firebase/util": "1.13.0" + "@firebase/util": "1.15.0" } }, "node_modules/@firebase/firestore": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.9.2.tgz", - "integrity": "sha512-iuA5+nVr/IV/Thm0Luoqf2mERUvK9g791FZpUJV1ZGXO6RL2/i/WFJUj5ZTVXy5pRjpWYO+ZzPcReNrlilmztA==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.13.0.tgz", + "integrity": "sha512-7i4cVNJXTMim7/P7UsNim0DwyLPk4QQ3y1oSNzv4l0ykJOKYCiFMOuEeUxUYvrReXDJxWHrT/4XMeVQm+13rRw==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.0", + "@firebase/component": "0.7.2", "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "@firebase/webchannel-wrapper": "1.0.5", "@grpc/grpc-js": "~1.9.0", "@grpc/proto-loader": "^0.7.8", @@ -2424,15 +2471,15 @@ } }, "node_modules/@firebase/firestore-compat": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.2.tgz", - "integrity": "sha512-cy7ov6SpFBx+PHwFdOOjbI7kH00uNKmIFurAn560WiPCZXy9EMnil1SOG7VF4hHZKdenC+AHtL4r3fNpirpm0w==", + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.7.tgz", + "integrity": "sha512-Et4XxtGnjp0Q9tmaEMETnY5GHJ8gQ9+RN6sSTT4ETWKmym2d6gIjarw0rCQcx+7BrWVYLEIOAXSXysl0b3xnUA==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.0", - "@firebase/firestore": "4.9.2", + "@firebase/component": "0.7.2", + "@firebase/firestore": "4.13.0", "@firebase/firestore-types": "3.0.3", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "engines": { @@ -2453,16 +2500,16 @@ } }, "node_modules/@firebase/functions": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.13.1.tgz", - "integrity": "sha512-sUeWSb0rw5T+6wuV2o9XNmh9yHxjFI9zVGFnjFi+n7drTEWpl7ZTz1nROgGrSu472r+LAaj+2YaSicD4R8wfbw==", + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.13.3.tgz", + "integrity": "sha512-csO7ckK3SSs+NUZW1nms9EK7ckHe/1QOjiP8uAkCYa7ND18s44vjE9g3KxEeIUpyEPqZaX1EhJuFyZjHigAcYw==", "license": "Apache-2.0", "dependencies": { "@firebase/app-check-interop-types": "0.3.3", "@firebase/auth-interop-types": "0.2.4", - "@firebase/component": "0.7.0", + "@firebase/component": "0.7.2", "@firebase/messaging-interop-types": "0.2.3", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "engines": { @@ -2473,15 +2520,15 @@ } }, "node_modules/@firebase/functions-compat": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.4.1.tgz", - "integrity": "sha512-AxxUBXKuPrWaVNQ8o1cG1GaCAtXT8a0eaTDfqgS5VsRYLAR0ALcfqDLwo/QyijZj1w8Qf8n3Qrfy/+Im245hOQ==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.4.3.tgz", + "integrity": "sha512-BxkEwWgx1of0tKaao/r2VR6WBLk/RAiyztatiONPrPE8gkitFkOnOCxf8i9cUyA5hX5RGt5H30uNn25Q6QNEmQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.0", - "@firebase/functions": "0.13.1", + "@firebase/component": "0.7.2", + "@firebase/functions": "0.13.3", "@firebase/functions-types": "0.6.3", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "engines": { @@ -2498,13 +2545,13 @@ "license": "Apache-2.0" }, "node_modules/@firebase/installations": { - "version": "0.6.19", - "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.19.tgz", - "integrity": "sha512-nGDmiwKLI1lerhwfwSHvMR9RZuIH5/8E3kgUWnVRqqL7kGVSktjLTWEMva7oh5yxQ3zXfIlIwJwMcaM5bK5j8Q==", + "version": "0.6.21", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.21.tgz", + "integrity": "sha512-xGFGTeICJZ5vhrmmDukeczIcFULFXybojML2+QSDFoKj5A7zbGN7KzFGSKNhDkIxpjzsYG9IleJyUebuAcmqWA==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.0", - "@firebase/util": "1.13.0", + "@firebase/component": "0.7.2", + "@firebase/util": "1.15.0", "idb": "7.1.1", "tslib": "^2.1.0" }, @@ -2513,15 +2560,15 @@ } }, "node_modules/@firebase/installations-compat": { - "version": "0.2.19", - "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.19.tgz", - "integrity": "sha512-khfzIY3EI5LePePo7vT19/VEIH1E3iYsHknI/6ek9T8QCozAZshWT9CjlwOzZrKvTHMeNcbpo/VSOSIWDSjWdQ==", + "version": "0.2.21", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.21.tgz", + "integrity": "sha512-zahIUkaVKbR8zmTeBHkdfaVl6JGWlhVoSjF7CVH33nFqD3SlPEpEEegn2GNT5iAfsVdtlCyJJ9GW4YKjq+RJKQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.0", - "@firebase/installations": "0.6.19", + "@firebase/component": "0.7.2", + "@firebase/installations": "0.6.21", "@firebase/installations-types": "0.5.3", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2556,15 +2603,15 @@ } }, "node_modules/@firebase/messaging": { - "version": "0.12.23", - "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.23.tgz", - "integrity": "sha512-cfuzv47XxqW4HH/OcR5rM+AlQd1xL/VhuaeW/wzMW1LFrsFcTn0GND/hak1vkQc2th8UisBcrkVcQAnOnKwYxg==", + "version": "0.12.25", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.25.tgz", + "integrity": "sha512-7RhDwoDHlOK1/ou0/LeubxmjcngsTjDdrY/ssg2vwAVpUuVAhQzQvuCAOYxcX5wNC1zCgQ54AP1vdngBwbCmOQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.0", - "@firebase/installations": "0.6.19", + "@firebase/component": "0.7.2", + "@firebase/installations": "0.6.21", "@firebase/messaging-interop-types": "0.2.3", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "idb": "7.1.1", "tslib": "^2.1.0" }, @@ -2573,14 +2620,14 @@ } }, "node_modules/@firebase/messaging-compat": { - "version": "0.2.23", - "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.23.tgz", - "integrity": "sha512-SN857v/kBUvlQ9X/UjAqBoQ2FEaL1ZozpnmL1ByTe57iXkmnVVFm9KqAsTfmf+OEwWI4kJJe9NObtN/w22lUgg==", + "version": "0.2.25", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.25.tgz", + "integrity": "sha512-eoOQqGLtRlseTdiemTN44LlHZpltK5gnhq8XVUuLgtIOG+odtDzrz2UoTpcJWSzaJQVxNLb/x9f39tHdDM4N4w==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.0", - "@firebase/messaging": "0.12.23", - "@firebase/util": "1.13.0", + "@firebase/component": "0.7.2", + "@firebase/messaging": "0.12.25", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2600,15 +2647,15 @@ "license": "ISC" }, "node_modules/@firebase/performance": { - "version": "0.7.9", - "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.9.tgz", - "integrity": "sha512-UzybENl1EdM2I1sjYm74xGt/0JzRnU/0VmfMAKo2LSpHJzaj77FCLZXmYQ4oOuE+Pxtt8Wy2BVJEENiZkaZAzQ==", + "version": "0.7.11", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.11.tgz", + "integrity": "sha512-V3uAhrz7IYJuji+OgT3qYTGKxpek/TViXti9OSsUJ4AexZ3jQjYH5Yrn7JvBxk8MGiSLsC872hh+BxQiPZsm7g==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.0", - "@firebase/installations": "0.6.19", + "@firebase/component": "0.7.2", + "@firebase/installations": "0.6.21", "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "tslib": "^2.1.0", "web-vitals": "^4.2.4" }, @@ -2617,16 +2664,16 @@ } }, "node_modules/@firebase/performance-compat": { - "version": "0.2.22", - "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.22.tgz", - "integrity": "sha512-xLKxaSAl/FVi10wDX/CHIYEUP13jXUjinL+UaNXT9ByIvxII5Ne5150mx6IgM8G6Q3V+sPiw9C8/kygkyHUVxg==", + "version": "0.2.24", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.24.tgz", + "integrity": "sha512-YRlejH8wLt7ThWao+HXoKUHUrZKGYq+otxkPS+8nuE5PeN1cBXX7NAJl9ueuUkBwMIrnKdnDqL/voHXxDAAt3g==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.0", + "@firebase/component": "0.7.2", "@firebase/logger": "0.5.0", - "@firebase/performance": "0.7.9", + "@firebase/performance": "0.7.11", "@firebase/performance-types": "0.2.3", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2640,15 +2687,15 @@ "license": "Apache-2.0" }, "node_modules/@firebase/remote-config": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.7.0.tgz", - "integrity": "sha512-dX95X6WlW7QlgNd7aaGdjAIZUiQkgWgNS+aKNu4Wv92H1T8Ue/NDUjZHd9xb8fHxLXIHNZeco9/qbZzr500MjQ==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.8.2.tgz", + "integrity": "sha512-5EXqOThV4upjK9D38d/qOSVwOqRhemlaOFk9vCkMNNALeIlwr+4pLjtLNo4qoY8etQmU/1q4aIATE9N8PFqg0g==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.0", - "@firebase/installations": "0.6.19", + "@firebase/component": "0.7.2", + "@firebase/installations": "0.6.21", "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2656,16 +2703,16 @@ } }, "node_modules/@firebase/remote-config-compat": { - "version": "0.2.20", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.20.tgz", - "integrity": "sha512-P/ULS9vU35EL9maG7xp66uljkZgcPMQOxLj3Zx2F289baTKSInE6+YIkgHEi1TwHoddC/AFePXPpshPlEFkbgg==", + "version": "0.2.23", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.23.tgz", + "integrity": "sha512-4+KqRRHEUUmKT6tFmnpWATOsaFfmSuBs1jXH8JzVtMLEYqq/WS9IDM92OdefFDSrAA2xGd0WN004z8mKeIIscw==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.0", + "@firebase/component": "0.7.2", "@firebase/logger": "0.5.0", - "@firebase/remote-config": "0.7.0", + "@firebase/remote-config": "0.8.2", "@firebase/remote-config-types": "0.5.0", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "peerDependencies": { @@ -2679,13 +2726,13 @@ "license": "Apache-2.0" }, "node_modules/@firebase/storage": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.14.0.tgz", - "integrity": "sha512-xWWbb15o6/pWEw8H01UQ1dC5U3rf8QTAzOChYyCpafV6Xki7KVp3Yaw2nSklUwHEziSWE9KoZJS7iYeyqWnYFA==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.14.2.tgz", + "integrity": "sha512-o/culaTeJ8GRpKXRJov21rux/n9dRaSOWLebyatFP2sqEdCxQPjVA1H9Z2fzYwQxMIU0JVmC7SPPmU11v7L6vQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.0", - "@firebase/util": "1.13.0", + "@firebase/component": "0.7.2", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "engines": { @@ -2696,15 +2743,15 @@ } }, "node_modules/@firebase/storage-compat": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.4.0.tgz", - "integrity": "sha512-vDzhgGczr1OfcOy285YAPur5pWDEvD67w4thyeCUh6Ys0izN9fNYtA1MJERmNBfqjqu0lg0FM5GLbw0Il21M+g==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.4.2.tgz", + "integrity": "sha512-R+aB38wxCH5zjIO/xu9KznI7fgiPuZAG98uVm1NcidHyyupGgIDLKigGmRGBZMnxibe/m2oxNKoZpfEbUX2aQQ==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.7.0", - "@firebase/storage": "0.14.0", + "@firebase/component": "0.7.2", + "@firebase/storage": "0.14.2", "@firebase/storage-types": "0.8.3", - "@firebase/util": "1.13.0", + "@firebase/util": "1.15.0", "tslib": "^2.1.0" }, "engines": { @@ -2725,9 +2772,9 @@ } }, "node_modules/@firebase/util": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.13.0.tgz", - "integrity": "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.15.0.tgz", + "integrity": "sha512-AmWf3cHAOMbrCPG4xdPKQaj5iHnyYfyLKZxwz+Xf55bqKbpAmcYifB4jQinT2W9XhDRHISOoPyBOariJpCG6FA==", "hasInstallScript": true, "license": "Apache-2.0", "peer": true, @@ -2786,38 +2833,25 @@ } }, "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" + "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -2858,19 +2892,6 @@ "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/@isaacs/cliui/node_modules/ansi-styles": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", @@ -2909,22 +2930,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -3083,22 +3088,24 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { @@ -3443,19 +3450,45 @@ "license": "MIT" }, "node_modules/@multiformats/dns": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.10.tgz", - "integrity": "sha512-6X200ceQLns0b/CU0S/So16tGjB5eIXHJ1xvJMPoWaKFHWSgfpW2EhkWJrqap4U3+c37zcowVR0ToPXeYEL7Vw==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.13.tgz", + "integrity": "sha512-yr4bxtA3MbvJ+2461kYIYMsiiZj/FIqKI64hE4SdvWJUdWF9EtZLar38juf20Sf5tguXKFUruluswAO6JsjS2w==", "license": "Apache-2.0 OR MIT", "dependencies": { - "buffer": "^6.0.3", - "dns-packet": "^5.6.1", + "@dnsquery/dns-packet": "^6.1.1", + "@libp2p/interface": "^3.1.0", "hashlru": "^2.3.0", "p-queue": "^9.0.0", "progress-events": "^1.0.0", "uint8arrays": "^5.0.2" } }, + "node_modules/@multiformats/dns/node_modules/@libp2p/interface": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-3.1.0.tgz", + "integrity": "sha512-RE7/XyvC47fQBe1cHxhMvepYKa5bFCUyFrrpj8PuM0E7JtzxU7F+Du5j4VXbg2yLDcToe0+j8mB7jvwE2AThYw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@multiformats/dns": "^1.0.6", + "@multiformats/multiaddr": "^13.0.1", + "main-event": "^1.0.1", + "multiformats": "^13.4.0", + "progress-events": "^1.0.1", + "uint8arraylist": "^2.4.8" + } + }, + "node_modules/@multiformats/dns/node_modules/@multiformats/multiaddr": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-13.0.1.tgz", + "integrity": "sha512-XToN915cnfr6Lr9EdGWakGJbPT0ghpg/850HvdC+zFX8XvpLZElwa8synCiwa8TuvKNnny6m8j8NVBNCxhIO3g==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "multiformats": "^13.0.0", + "uint8-varint": "^2.0.1", + "uint8arrays": "^5.0.0" + } + }, "node_modules/@multiformats/mafmt": { "version": "12.1.6", "resolved": "https://registry.npmjs.org/@multiformats/mafmt/-/mafmt-12.1.6.tgz", @@ -3786,16 +3819,13 @@ } }, "node_modules/@noble/secp256k1": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.2.tgz", - "integrity": "sha512-/qzwYl5eFLH8OWIecQWM31qld2g1NfjgylK+TNhqtaUKP37Nm+Y+z30Fjhw0Ct8p9yCQEm2N3W/AckdIb3SMcQ==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-3.0.0.tgz", + "integrity": "sha512-NJBaR352KyIvj3t6sgT/+7xrNyF9Xk9QlLSIqUGVUYlsnDTAUqY8LOmwpcgEx4AMJXRITQ5XEVHD+mMaPfr3mg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", @@ -4084,10 +4114,38 @@ "node": ">=18" } }, + "node_modules/@puppeteer/browsers/node_modules/tar-fs": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz", + "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/@puppeteer/browsers/node_modules/tar-stream": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", + "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz", + "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==", "cpu": [ "arm" ], @@ -4099,9 +4157,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz", + "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==", "cpu": [ "arm64" ], @@ -4113,9 +4171,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz", + "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==", "cpu": [ "arm64" ], @@ -4127,9 +4185,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz", + "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==", "cpu": [ "x64" ], @@ -4141,9 +4199,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz", + "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==", "cpu": [ "arm64" ], @@ -4155,9 +4213,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz", + "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==", "cpu": [ "x64" ], @@ -4169,9 +4227,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz", + "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==", "cpu": [ "arm" ], @@ -4183,9 +4241,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz", + "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==", "cpu": [ "arm" ], @@ -4197,9 +4255,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz", + "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==", "cpu": [ "arm64" ], @@ -4211,9 +4269,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz", + "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==", "cpu": [ "arm64" ], @@ -4225,9 +4283,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz", + "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==", "cpu": [ "loong64" ], @@ -4239,9 +4297,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz", + "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==", "cpu": [ "loong64" ], @@ -4253,9 +4311,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz", + "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==", "cpu": [ "ppc64" ], @@ -4267,9 +4325,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz", + "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==", "cpu": [ "ppc64" ], @@ -4281,9 +4339,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz", + "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==", "cpu": [ "riscv64" ], @@ -4295,9 +4353,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz", + "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==", "cpu": [ "riscv64" ], @@ -4309,9 +4367,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz", + "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==", "cpu": [ "s390x" ], @@ -4323,9 +4381,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz", + "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==", "cpu": [ "x64" ], @@ -4337,9 +4395,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz", + "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==", "cpu": [ "x64" ], @@ -4351,9 +4409,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz", + "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==", "cpu": [ "x64" ], @@ -4365,9 +4423,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz", + "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==", "cpu": [ "arm64" ], @@ -4379,9 +4437,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz", + "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==", "cpu": [ "arm64" ], @@ -4393,9 +4451,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz", + "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==", "cpu": [ "ia32" ], @@ -4407,9 +4465,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz", + "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==", "cpu": [ "x64" ], @@ -4421,9 +4479,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz", + "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==", "cpu": [ "x64" ], @@ -4454,12 +4512,12 @@ } }, "node_modules/@smithy/abort-controller": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.10.tgz", - "integrity": "sha512-qocxM/X4XGATqQtUkbE9SPUB6wekBi+FyJOMbPj0AhvyvFGYEmOlz6VB22iMePCQsFmMIvFSeViDvA7mZJG47g==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.12.tgz", + "integrity": "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -4467,9 +4525,9 @@ } }, "node_modules/@smithy/chunked-blob-reader": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.0.tgz", - "integrity": "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.2.tgz", + "integrity": "sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4479,12 +4537,12 @@ } }, "node_modules/@smithy/chunked-blob-reader-native": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.1.tgz", - "integrity": "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.3.tgz", + "integrity": "sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-base64": "^4.3.0", + "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" }, "engines": { @@ -4492,16 +4550,16 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.9.tgz", - "integrity": "sha512-ejQvXqlcU30h7liR9fXtj7PIAau1t/sFbJpgWPfiYDs7zd16jpH0IsSXKcba2jF6ChTXvIjACs27kNMc5xxE2Q==", + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.13.tgz", + "integrity": "sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.10", - "@smithy/types": "^4.13.0", - "@smithy/util-config-provider": "^4.2.1", - "@smithy/util-endpoints": "^3.3.1", - "@smithy/util-middleware": "^4.2.10", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" }, "engines": { @@ -4509,20 +4567,20 @@ } }, "node_modules/@smithy/core": { - "version": "3.23.6", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.6.tgz", - "integrity": "sha512-4xE+0L2NrsFKpEVFlFELkIHQddBvMbQ41LRIP74dGCXnY1zQ9DgksrBcRBDJT+iOzGy4VEJIeU3hkUK5mn06kg==", + "version": "3.23.12", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.12.tgz", + "integrity": "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.2.11", - "@smithy/protocol-http": "^5.3.10", - "@smithy/types": "^4.13.0", - "@smithy/util-base64": "^4.3.1", - "@smithy/util-body-length-browser": "^4.2.1", - "@smithy/util-middleware": "^4.2.10", - "@smithy/util-stream": "^4.5.15", - "@smithy/util-utf8": "^4.2.1", - "@smithy/uuid": "^1.1.1", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-stream": "^4.5.20", + "@smithy/util-utf8": "^4.2.2", + "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" }, "engines": { @@ -4530,15 +4588,15 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.10.tgz", - "integrity": "sha512-3bsMLJJLTZGZqVGGeBVFfLzuRulVsGTj12BzRKODTHqUABpIr0jMN1vN3+u6r2OfyhAQ2pXaMZWX/swBK5I6PQ==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.12.tgz", + "integrity": "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.10", - "@smithy/property-provider": "^4.2.10", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.10", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" }, "engines": { @@ -4546,14 +4604,14 @@ } }, "node_modules/@smithy/eventstream-codec": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.8.tgz", - "integrity": "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.12.tgz", + "integrity": "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.12.0", - "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/types": "^4.13.1", + "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -4561,13 +4619,13 @@ } }, "node_modules/@smithy/eventstream-serde-browser": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.8.tgz", - "integrity": "sha512-MTfQT/CRQz5g24ayXdjg53V0mhucZth4PESoA5IhvaWVDTOQLfo8qI9vzqHcPsdd2v6sqfTYqF5L/l+pea5Uyw==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.12.tgz", + "integrity": "sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.8", - "@smithy/types": "^4.12.0", + "@smithy/eventstream-serde-universal": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -4575,12 +4633,12 @@ } }, "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.8.tgz", - "integrity": "sha512-ah12+luBiDGzBruhu3efNy1IlbwSEdNiw8fOZksoKoWW1ZHvO/04MQsdnws/9Aj+5b0YXSSN2JXKy/ClIsW8MQ==", + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.12.tgz", + "integrity": "sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.12.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -4588,13 +4646,13 @@ } }, "node_modules/@smithy/eventstream-serde-node": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.8.tgz", - "integrity": "sha512-cYpCpp29z6EJHa5T9WL0KAlq3SOKUQkcgSoeRfRVwjGgSFl7Uh32eYGt7IDYCX20skiEdRffyDpvF2efEZPC0A==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.12.tgz", + "integrity": "sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.8", - "@smithy/types": "^4.12.0", + "@smithy/eventstream-serde-universal": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -4602,13 +4660,13 @@ } }, "node_modules/@smithy/eventstream-serde-universal": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.8.tgz", - "integrity": "sha512-iJ6YNJd0bntJYnX6s52NC4WFYcZeKrPUr1Kmmr5AwZcwCSzVpS7oavAmxMR7pMq7V+D1G4s9F5NJK0xwOsKAlQ==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.12.tgz", + "integrity": "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-codec": "^4.2.8", - "@smithy/types": "^4.12.0", + "@smithy/eventstream-codec": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -4616,15 +4674,15 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.11", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.11.tgz", - "integrity": "sha512-wbTRjOxdFuyEg0CpumjZO0hkUl+fetJFqxNROepuLIoijQh51aMBmzFLfoQdwRjxsuuS2jizzIUTjPWgd8pd7g==", + "version": "5.3.15", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.15.tgz", + "integrity": "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.10", - "@smithy/querystring-builder": "^4.2.10", - "@smithy/types": "^4.13.0", - "@smithy/util-base64": "^4.3.1", + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", + "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" }, "engines": { @@ -4632,14 +4690,14 @@ } }, "node_modules/@smithy/hash-blob-browser": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.9.tgz", - "integrity": "sha512-m80d/iicI7DlBDxyQP6Th7BW/ejDGiF0bgI754+tiwK0lgMkcaIBgvwwVc7OFbY4eUzpGtnig52MhPAEJ7iNYg==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.13.tgz", + "integrity": "sha512-YrF4zWKh+ghLuquldj6e/RzE3xZYL8wIPfkt0MqCRphVICjyyjH8OwKD7LLlKpVEbk4FLizFfC1+gwK6XQdR3g==", "license": "Apache-2.0", "dependencies": { - "@smithy/chunked-blob-reader": "^5.2.0", - "@smithy/chunked-blob-reader-native": "^4.2.1", - "@smithy/types": "^4.12.0", + "@smithy/chunked-blob-reader": "^5.2.2", + "@smithy/chunked-blob-reader-native": "^4.2.3", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -4647,14 +4705,14 @@ } }, "node_modules/@smithy/hash-node": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.10.tgz", - "integrity": "sha512-1VzIOI5CcsvMDvP3iv1vG/RfLJVVVc67dCRyLSB2Hn9SWCZrDO3zvcIzj3BfEtqRW5kcMg5KAeVf1K3dR6nD3w==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.12.tgz", + "integrity": "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", - "@smithy/util-buffer-from": "^4.2.1", - "@smithy/util-utf8": "^4.2.1", + "@smithy/types": "^4.13.1", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -4662,13 +4720,13 @@ } }, "node_modules/@smithy/hash-stream-node": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.8.tgz", - "integrity": "sha512-v0FLTXgHrTeheYZFGhR+ehX5qUm4IQsjAiL9qehad2cyjMWcN2QG6/4mSwbSgEQzI7jwfoXj7z4fxZUx/Mhj2w==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.12.tgz", + "integrity": "sha512-O3YbmGExeafuM/kP7Y8r6+1y0hIh3/zn6GROx0uNlB54K9oihAL75Qtc+jFfLNliTi6pxOAYZrRKD9A7iA6UFw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.12.0", - "@smithy/util-utf8": "^4.2.0", + "@smithy/types": "^4.13.1", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -4676,12 +4734,12 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.10.tgz", - "integrity": "sha512-vy9KPNSFUU0ajFYk0sDZIYiUlAWGEAhRfehIr5ZkdFrRFTAuXEPUd41USuqHU6vvLX4r6Q9X7MKBco5+Il0Org==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.12.tgz", + "integrity": "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -4701,13 +4759,13 @@ } }, "node_modules/@smithy/md5-js": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.9.tgz", - "integrity": "sha512-ZCCWfGj4wvqV+5OS9e/GvR5jlR7j1mMB1UkGE+V7P1USFMwcL4Z4j5mO9nGvQGkfe20KM87ymbvZIcU9tHNlIg==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.12.tgz", + "integrity": "sha512-W/oIpHCpWU2+iAkfZYyGWE+qkpuf3vEXHLxQQDx9FPNZTTdnul0dZ2d/gUFrtQ5je1G2kp4cjG0/24YueG2LbQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.12.1", - "@smithy/util-utf8": "^4.2.1", + "@smithy/types": "^4.13.1", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -4715,14 +4773,14 @@ } }, "node_modules/@smithy/middleware-apply-body-checksum": { - "version": "4.3.9", - "resolved": "https://registry.npmjs.org/@smithy/middleware-apply-body-checksum/-/middleware-apply-body-checksum-4.3.9.tgz", - "integrity": "sha512-53wIgp1Jz5F886zUHTnVnXEUvDocPUs0icN5ja9oPIUWRXN9PpXOIQFUqEMQlU5zKMi+sfUz8yKnlsxYHkWjqw==", + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-apply-body-checksum/-/middleware-apply-body-checksum-4.3.12.tgz", + "integrity": "sha512-3pXV1BPiU575N7DBTtFuaW5VwxaN72jMaAKm8a7WivE5qB1DfLsmrOQ8z3P334dur9bv5qx90jamUP03VpOTaQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.2.1", - "@smithy/protocol-http": "^5.3.9", - "@smithy/types": "^4.12.1", + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -4730,13 +4788,13 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.10.tgz", - "integrity": "sha512-TQZ9kX5c6XbjhaEBpvhSvMEZ0klBs1CFtOdPFwATZSbC9UeQfKHPLPN9Y+I6wZGMOavlYTOlHEPDrt42PMSH9w==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.12.tgz", + "integrity": "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.10", - "@smithy/types": "^4.13.0", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -4744,18 +4802,18 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.20", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.20.tgz", - "integrity": "sha512-9W6Np4ceBP3XCYAGLoMCmn8t2RRVzuD1ndWPLBbv7H9CrwM9Bprf6Up6BM9ZA/3alodg0b7Kf6ftBK9R1N04vw==", + "version": "4.4.27", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.27.tgz", + "integrity": "sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.6", - "@smithy/middleware-serde": "^4.2.11", - "@smithy/node-config-provider": "^4.3.10", - "@smithy/shared-ini-file-loader": "^4.4.5", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.10", - "@smithy/util-middleware": "^4.2.10", + "@smithy/core": "^3.23.12", + "@smithy/middleware-serde": "^4.2.15", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" }, "engines": { @@ -4763,19 +4821,19 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.4.37", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.37.tgz", - "integrity": "sha512-/1psZZllBBSQ7+qo5+hhLz7AEPGLx3Z0+e3ramMBEuPK2PfvLK4SrncDB9VegX5mBn+oP/UTDrM6IHrFjvX1ZA==", + "version": "4.4.44", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.44.tgz", + "integrity": "sha512-Y1Rav7m5CFRPQyM4CI0koD/bXjyjJu3EQxZZhtLGD88WIrBrQ7kqXM96ncd6rYnojwOo/u9MXu57JrEvu/nLrA==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.10", - "@smithy/protocol-http": "^5.3.10", - "@smithy/service-error-classification": "^4.2.10", - "@smithy/smithy-client": "^4.12.0", - "@smithy/types": "^4.13.0", - "@smithy/util-middleware": "^4.2.10", - "@smithy/util-retry": "^4.2.10", - "@smithy/uuid": "^1.1.1", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/service-error-classification": "^4.2.12", + "@smithy/smithy-client": "^4.12.7", + "@smithy/types": "^4.13.1", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.12", + "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" }, "engines": { @@ -4783,13 +4841,14 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.11.tgz", - "integrity": "sha512-STQdONGPwbbC7cusL60s7vOa6He6A9w2jWhoapL0mgVjmR19pr26slV+yoSP76SIssMTX/95e5nOZ6UQv6jolg==", + "version": "4.2.15", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.15.tgz", + "integrity": "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.10", - "@smithy/types": "^4.13.0", + "@smithy/core": "^3.23.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -4797,12 +4856,12 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.10.tgz", - "integrity": "sha512-pmts/WovNcE/tlyHa8z/groPeOtqtEpp61q3W0nW1nDJuMq/x+hWa/OVQBtgU0tBqupeXq0VBOLA4UZwE8I0YA==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.12.tgz", + "integrity": "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -4825,15 +4884,15 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.4.12", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.12.tgz", - "integrity": "sha512-zo1+WKJkR9x7ZtMeMDAAsq2PufwiLDmkhcjpWPRRkmeIuOm6nq1qjFICSZbnjBvD09ei8KMo26BWxsu2BUU+5w==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.0.tgz", + "integrity": "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.10", - "@smithy/protocol-http": "^5.3.10", - "@smithy/querystring-builder": "^4.2.10", - "@smithy/types": "^4.13.0", + "@smithy/abort-controller": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -4854,12 +4913,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.10.tgz", - "integrity": "sha512-2NzVWpYY0tRdfeCJLsgrR89KE3NTWT2wGulhNUxYlRmtRmPwLQwKzhrfVaiNlA9ZpJvbW7cjTVChYKgnkqXj1A==", + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -4867,13 +4926,13 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.10.tgz", - "integrity": "sha512-HeN7kEvuzO2DmAzLukE9UryiUvejD3tMp9a1D1NJETerIfKobBUCLfviP6QEk500166eD2IATaXM59qgUI+YDA==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", - "@smithy/util-uri-escape": "^4.2.1", + "@smithy/types": "^4.13.1", + "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -4894,12 +4953,12 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.10.tgz", - "integrity": "sha512-0R/+/Il5y8nB/By90o8hy/bWVYptbIfvoTYad0igYQO5RefhNCDmNzqxaMx7K1t/QWo0d6UynqpqN5cCQt1MCg==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.12.tgz", + "integrity": "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0" + "@smithy/types": "^4.13.1" }, "engines": { "node": ">=18.0.0" @@ -4919,18 +4978,18 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.10.tgz", - "integrity": "sha512-Wab3wW8468WqTKIxI+aZe3JYO52/RYT/8sDOdzkUhjnLakLe9qoQqIcfih/qxcF4qWEFoWBszY0mj5uxffaVXA==", + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.12.tgz", + "integrity": "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.2.1", - "@smithy/protocol-http": "^5.3.10", - "@smithy/types": "^4.13.0", - "@smithy/util-hex-encoding": "^4.2.1", - "@smithy/util-middleware": "^4.2.10", - "@smithy/util-uri-escape": "^4.2.1", - "@smithy/util-utf8": "^4.2.1", + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-uri-escape": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -4938,17 +4997,17 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.0.tgz", - "integrity": "sha512-R8bQ9K3lCcXyZmBnQqUZJF4ChZmtWT5NLi6x5kgWx5D+/j0KorXcA0YcFg/X5TOgnTCy1tbKc6z2g2y4amFupQ==", + "version": "4.12.7", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.7.tgz", + "integrity": "sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.6", - "@smithy/middleware-endpoint": "^4.4.20", - "@smithy/middleware-stack": "^4.2.10", - "@smithy/protocol-http": "^5.3.10", - "@smithy/types": "^4.13.0", - "@smithy/util-stream": "^4.5.15", + "@smithy/core": "^3.23.12", + "@smithy/middleware-endpoint": "^4.4.27", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" }, "engines": { @@ -4982,13 +5041,13 @@ } }, "node_modules/@smithy/util-base64": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.1.tgz", - "integrity": "sha512-BKGuawX4Doq/bI/uEmg+Zyc36rJKWuin3py89PquXBIBqmbnJwBBsmKhdHfNEp0+A4TDgLmT/3MSKZ1SxHcR6w==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", + "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.2.1", - "@smithy/util-utf8": "^4.2.1", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -4996,9 +5055,9 @@ } }, "node_modules/@smithy/util-body-length-browser": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.1.tgz", - "integrity": "sha512-SiJeLiozrAoCrgDBUgsVbmqHmMgg/2bA15AzcbcW+zan7SuyAVHN4xTSbq0GlebAIwlcaX32xacnrG488/J/6g==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", + "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -5008,9 +5067,9 @@ } }, "node_modules/@smithy/util-body-length-node": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.2.tgz", - "integrity": "sha512-4rHqBvxtJEBvsZcFQSPQqXP2b/yy/YlB66KlcEgcH2WNoOKCKB03DSLzXmOsXjbl8dJ4OEYTn31knhdznwk7zw==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", + "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -5033,9 +5092,9 @@ } }, "node_modules/@smithy/util-config-provider": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.1.tgz", - "integrity": "sha512-462id/00U8JWFw6qBuTSWfN5TxOHvDu4WliI97qOIOnuC/g+NDAknTU8eoGXEPlLkRVgWEr03jJBLV4o2FL8+A==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", + "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -5045,14 +5104,14 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.36", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.36.tgz", - "integrity": "sha512-R0smq7EHQXRVMxkAxtH5akJ/FvgAmNF6bUy/GwY/N20T4GrwjT633NFm0VuRpC+8Bbv8R9A0DoJ9OiZL/M3xew==", + "version": "4.3.43", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.43.tgz", + "integrity": "sha512-Qd/0wCKMaXxev/z00TvNzGCH2jlKKKxXP1aDxB6oKwSQthe3Og2dMhSayGCnsma1bK/kQX1+X7SMP99t6FgiiQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.10", - "@smithy/smithy-client": "^4.12.0", - "@smithy/types": "^4.13.0", + "@smithy/property-provider": "^4.2.12", + "@smithy/smithy-client": "^4.12.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -5060,17 +5119,17 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.39", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.39.tgz", - "integrity": "sha512-otWuoDm35btJV1L8MyHrPl462B07QCdMTktKc7/yM+Psv6KbED/ziXiHnmr7yPHUjfIwE9S8Max0LO24Mo3ZVg==", + "version": "4.2.47", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.47.tgz", + "integrity": "sha512-qSRbYp1EQ7th+sPFuVcVO05AE0QH635hycdEXlpzIahqHHf2Fyd/Zl+8v0XYMJ3cgDVPa0lkMefU7oNUjAP+DQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.4.9", - "@smithy/credential-provider-imds": "^4.2.10", - "@smithy/node-config-provider": "^4.3.10", - "@smithy/property-provider": "^4.2.10", - "@smithy/smithy-client": "^4.12.0", - "@smithy/types": "^4.13.0", + "@smithy/config-resolver": "^4.4.13", + "@smithy/credential-provider-imds": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/smithy-client": "^4.12.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -5092,9 +5151,9 @@ } }, "node_modules/@smithy/util-hex-encoding": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.1.tgz", - "integrity": "sha512-c1hHtkgAWmE35/50gmdKajgGAKV3ePJ7t6UtEmpfCWJmQE9BQAQPz0URUVI89eSkcDqCtzqllxzG28IQoZPvwA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -5104,12 +5163,12 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.10.tgz", - "integrity": "sha512-LxaQIWLp4y0r72eA8mwPNQ9va4h5KeLM0I3M/HV9klmFaY2kN766wf5vsTzmaOpNNb7GgXAd9a25P3h8T49PSA==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -5117,13 +5176,13 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.10.tgz", - "integrity": "sha512-HrBzistfpyE5uqTwiyLsFHscgnwB0kgv8vySp7q5kZ0Eltn/tjosaSGGDj/jJ9ys7pWzIP/icE2d+7vMKXLv7A==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.12.tgz", + "integrity": "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.10", - "@smithy/types": "^4.13.0", + "@smithy/service-error-classification": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -5131,18 +5190,18 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.5.15", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.15.tgz", - "integrity": "sha512-OlOKnaqnkU9X+6wEkd7mN+WB7orPbCVDauXOj22Q7VtiTkvy7ZdSsOg4QiNAZMgI4OkvNf+/VLUC3VXkxuWJZw==", + "version": "4.5.20", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.20.tgz", + "integrity": "sha512-4yXLm5n/B5SRBR2p8cZ90Sbv4zL4NKsgxdzCzp/83cXw2KxLEumt5p+GAVyRNZgQOSrzXn9ARpO0lUe8XSlSDw==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.11", - "@smithy/node-http-handler": "^4.4.12", - "@smithy/types": "^4.13.0", - "@smithy/util-base64": "^4.3.1", - "@smithy/util-buffer-from": "^4.2.1", - "@smithy/util-hex-encoding": "^4.2.1", - "@smithy/util-utf8": "^4.2.1", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/node-http-handler": "^4.5.0", + "@smithy/types": "^4.13.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { @@ -5150,9 +5209,9 @@ } }, "node_modules/@smithy/util-uri-escape": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.1.tgz", - "integrity": "sha512-YmiUDn2eo2IOiWYYvGQkgX5ZkBSiTQu4FlDo5jNPpAxng2t6Sjb6WutnZV9l6VR4eJul1ABmCrnWBC9hKHQa6Q==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -5175,13 +5234,13 @@ } }, "node_modules/@smithy/util-waiter": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.8.tgz", - "integrity": "sha512-n+lahlMWk+aejGuax7DPWtqav8HYnWxQwR+LCG2BgCUmaGcTe9qZCFsmw8TMg9iG75HOwhrJCX9TCJRLH+Yzqg==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.13.tgz", + "integrity": "sha512-2zdZ9DTHngRtcYxJK1GUDxruNr53kv5W2Lupe0LMU+Imr6ohQg8M2T14MNkj1Y0wS3FFwpgpGQyvuaMF7CiTmQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.8", - "@smithy/types": "^4.12.0", + "@smithy/abort-controller": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { @@ -5189,9 +5248,9 @@ } }, "node_modules/@smithy/uuid": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.1.tgz", - "integrity": "sha512-dSfDCeihDmZlV2oyr0yWPTUfh07suS+R5OB+FZGiv/hHyK3hrFBW5rR1UYjfa57vBsrP9lciFkRPzebaV1Qujw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", + "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -5208,88 +5267,95 @@ "license": "MIT" }, "node_modules/@supabase/auth-js": { - "version": "2.77.0", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.77.0.tgz", - "integrity": "sha512-IRxyj2l46EutSX7AbGkHA7LSmrgqnXjPfKouBlGT6NqS1YOm+jMMwfdhf6zP5EZ4giUfdt2u+yHIBgyXU/LEtg==", + "version": "2.100.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.100.0.tgz", + "integrity": "sha512-pdT3ye3UVRN1Cg0wom6BmyY+XTtp5DiJaYnPi6j8ht5i8Lq8kfqxJMJz9GI9YDKk3w1nhGOPnh6Qz5qpyYm+1w==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "2.6.15", "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@supabase/functions-js": { - "version": "2.77.0", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.77.0.tgz", - "integrity": "sha512-MxNW3YoQysbiVEiLAozCTqk1urswtVjAZggeZ5Sw+vJ+u1EvFNmTnirzwTj7M8XjTOMmorheruPmNtfHEwudvw==", + "version": "2.100.0", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.100.0.tgz", + "integrity": "sha512-keLg79RPwP+uiwHuxFPTFgDRxPV46LM4j/swjyR2GKJgWniTVSsgiBHfbIBDcrQwehLepy09b/9QSHUywtKRWQ==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "2.6.15", "tslib": "2.8.1" - } - }, - "node_modules/@supabase/node-fetch": { - "version": "2.6.15", - "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", - "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" }, "engines": { - "node": "4.x || >=6.0.0" + "node": ">=20.0.0" } }, + "node_modules/@supabase/phoenix": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.0.tgz", + "integrity": "sha512-RHSx8bHS02xwfHdAbX5Lpbo6PXbgyf7lTaXTlwtFDPwOIw64NnVRwFAXGojHhjtVYI+PEPNSWwkL90f4agN3bw==", + "license": "MIT" + }, "node_modules/@supabase/postgrest-js": { - "version": "2.77.0", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.77.0.tgz", - "integrity": "sha512-Ly8C48x875JcUXBdML7SPRjO1Bpmjo6Sax/Tz4Ij6YU5paCrGKxEYDBVLP2eHKkQvf+LQ+GIbRFF1DorRvyfwQ==", + "version": "2.100.0", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.100.0.tgz", + "integrity": "sha512-xYNvNbBJaXOGcrZ44wxwp5830uo1okMHGS8h8dm3u4f0xcZ39yzbryUsubTJW41MG2gbL/6U57cA4Pi6YMZ9pA==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "2.6.15", "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@supabase/realtime-js": { - "version": "2.77.0", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.77.0.tgz", - "integrity": "sha512-Ycv2VZ8yTjvlR2NQecGJUlP0Dh4LhF1Y1oZ3IMQcjjTbDriWSQgfc9HSLIQUaY/eTdtfXfyVKOGE+tieWneV8Q==", + "version": "2.100.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.100.0.tgz", + "integrity": "sha512-2AZs00zzEF0HuCKY8grz5eCYlwEfVi5HONLZFoNR6aDfxQivl8zdQYNjyFoqN2MZiVhQHD7u6XV/xHwM8mCEHw==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "2.6.15", - "@types/phoenix": "^1.6.6", + "@supabase/phoenix": "^0.4.0", "@types/ws": "^8.18.1", "tslib": "2.8.1", "ws": "^8.18.2" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@supabase/storage-js": { - "version": "2.77.0", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.77.0.tgz", - "integrity": "sha512-4+OpVA4U0C4HM1QuINlgjqFxnRsmqPnuurTDN4m6nRanafuqQQ/UtMjdUU57iygBo70IGBlBefZ8gGNWzD1sLg==", + "version": "2.100.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.100.0.tgz", + "integrity": "sha512-d4EeuK6RNIgYNA2MU9kj8lQrLm5AzZ+WwpWjGkii6SADQNIGTC/uiaTRu02XJ5AmFALQfo8fLl9xuCkO6Xw+iQ==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "2.6.15", + "iceberg-js": "^0.8.1", "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@supabase/supabase-js": { - "version": "2.77.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.77.0.tgz", - "integrity": "sha512-s6OQ8RZ0ioQCwVDh2Tv502XaUQCuRbbjpujYJB1h0JWELRsqjLDsgB5kZUkETPgtTAjJk7z97YPUsRg80PohfA==", + "version": "2.100.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.100.0.tgz", + "integrity": "sha512-r0tlcukejJXJ1m/2eG/Ya5eYs4W8AC7oZfShpG3+SIo/eIU9uIt76ZeYI1SoUwUmcmzlAbgch+HDZDR/toVQPQ==", "license": "MIT", "dependencies": { - "@supabase/auth-js": "2.77.0", - "@supabase/functions-js": "2.77.0", - "@supabase/node-fetch": "2.6.15", - "@supabase/postgrest-js": "2.77.0", - "@supabase/realtime-js": "2.77.0", - "@supabase/storage-js": "2.77.0" + "@supabase/auth-js": "2.100.0", + "@supabase/functions-js": "2.100.0", + "@supabase/postgrest-js": "2.100.0", + "@supabase/realtime-js": "2.100.0", + "@supabase/storage-js": "2.100.0" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@sveltejs/acorn-typescript": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", - "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz", + "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -5319,13 +5385,13 @@ } }, "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.1.tgz", - "integrity": "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.2.tgz", + "integrity": "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.4.1" + "obug": "^2.1.0" }, "engines": { "node": "^20.19 || ^22.12 || >=24" @@ -5376,15 +5442,17 @@ "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": "*" } }, "node_modules/@types/debug": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", - "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "dev": true, + "license": "MIT", "dependencies": { "@types/ms": "*" } @@ -5407,7 +5475,8 @@ "version": "1.0.36", "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.8", @@ -5448,7 +5517,8 @@ "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/linkify-it": { "version": "5.0.0", @@ -5458,9 +5528,9 @@ "license": "MIT" }, "node_modules/@types/lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", "license": "MIT" }, "node_modules/@types/lodash.debounce": { @@ -5498,10 +5568,11 @@ "license": "MIT" }, "node_modules/@types/ms": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", - "dev": true + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "24.12.0", @@ -5512,23 +5583,12 @@ "undici-types": "~7.16.0" } }, - "node_modules/@types/node/node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "license": "MIT" - }, - "node_modules/@types/phoenix": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", - "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==", - "license": "MIT" - }, "node_modules/@types/pouchdb": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/@types/pouchdb/-/pouchdb-6.4.2.tgz", "integrity": "sha512-YsI47rASdtzR+3V3JE2UKY58snhm0AglHBpyckQBkRYoCbTvGagXHtV0x5n8nzN04jQmvTG+Sm85cIzKT3KXBA==", "dev": true, + "license": "MIT", "dependencies": { "@types/pouchdb-adapter-cordova-sqlite": "*", "@types/pouchdb-adapter-fruitdown": "*", @@ -5548,19 +5608,21 @@ } }, "node_modules/@types/pouchdb-adapter-cordova-sqlite": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-cordova-sqlite/-/pouchdb-adapter-cordova-sqlite-1.0.1.tgz", - "integrity": "sha512-nqlXpW1ho3KBg1mUQvZgH2755y3z/rw4UA7ZJCPMRTHofxGMY8izRVw5rHBL4/7P615or0J2udpRYxgkT3D02g==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-cordova-sqlite/-/pouchdb-adapter-cordova-sqlite-1.0.4.tgz", + "integrity": "sha512-1MGjmAMux3OIyJ+iXfhJ5hNIzS+KjGJ05O3bF5Gen5TiJUFNK1bOp3VVV9SxXgz+hGwnBruBAWdAqhbB6ZHhSA==", "dev": true, + "license": "MIT", "dependencies": { "@types/pouchdb-core": "*" } }, "node_modules/@types/pouchdb-adapter-fruitdown": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-fruitdown/-/pouchdb-adapter-fruitdown-6.1.3.tgz", - "integrity": "sha512-Wz1Z1JLOW1hgmFQjqnSkmyyfH7by/iWb4abKn684WMvQfmxx6BxKJpJ4+eulkVPQzzgMMSgU1MpnQOm9FgRkbw==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-fruitdown/-/pouchdb-adapter-fruitdown-6.1.6.tgz", + "integrity": "sha512-KaFB29hUI97eTtJI6pjv7EQcqhZ63qHWovKgyiE+HZF5fVmdrBbTmnIrbR87AJXcXKy47+oQFJ7rzxY8TalpLQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/pouchdb-core": "*" } @@ -5570,6 +5632,7 @@ "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-http/-/pouchdb-adapter-http-6.1.6.tgz", "integrity": "sha512-DJur1mt07GJXwGb5K+MOILoCOSgoQpsi7hybcTzRLeR3IO8Y8eq7TnhTkftAJdx9VHJGOiOXFjO+8BYM69j5yA==", "dev": true, + "license": "MIT", "dependencies": { "@types/pouchdb-core": "*" } @@ -5579,52 +5642,58 @@ "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-idb/-/pouchdb-adapter-idb-6.1.7.tgz", "integrity": "sha512-KwjkJ4fTNz5wPXYu20bUoWud7ty0t7tgdo4oc0AJvG+fcURAH7mI7uFmpE4dZIT+hUq5G61xu96AVq9b2q4T3g==", "dev": true, + "license": "MIT", "dependencies": { "@types/pouchdb-core": "*" } }, "node_modules/@types/pouchdb-adapter-leveldb": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-leveldb/-/pouchdb-adapter-leveldb-6.1.3.tgz", - "integrity": "sha512-ex8NFqQGFwEpFi7AaZ5YofmuemfZNsL3nTFZBUCAKYMBkazQij1pe2ILLStSvJr0XS0qxgXjCEW19T5Wqiiskg==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-leveldb/-/pouchdb-adapter-leveldb-6.1.6.tgz", + "integrity": "sha512-mqeTpA2Ni2U4FA5ISRESy4WwhfUahXViUa3jQpXGdSpruaeHlhTLzZJPyz7/mGlvdAfAFv9Vd5d6ys3ASmMujw==", "dev": true, + "license": "MIT", "dependencies": { "@types/pouchdb-core": "*" } }, "node_modules/@types/pouchdb-adapter-localstorage": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-localstorage/-/pouchdb-adapter-localstorage-6.1.3.tgz", - "integrity": "sha512-oor040tye1KKiGLWYtIy7rRT7C2yoyX3Tf6elEJRpjOA7Ja/H8lKc4LaSh9ATbptIcES6MRqZDxtp7ly9hsW3Q==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-localstorage/-/pouchdb-adapter-localstorage-6.1.6.tgz", + "integrity": "sha512-+HQBCpD80XkKJE64r7uLwzkNRgkvMnhDI5rIFLx3USxdrRph/R3awcEubRFndcgtxzcUaL9iYw9KetgFMUqPrg==", "dev": true, + "license": "MIT", "dependencies": { "@types/pouchdb-core": "*" } }, "node_modules/@types/pouchdb-adapter-memory": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-memory/-/pouchdb-adapter-memory-6.1.3.tgz", - "integrity": "sha512-gVbsIMzDzgZYThFVT4eVNsmuZwVm/4jDxP1sjlgc3qtDIxbtBhGgyNfcskwwz9Zu5Lv1avkDsIWvcxQhnvRlHg==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-memory/-/pouchdb-adapter-memory-6.1.6.tgz", + "integrity": "sha512-QCCtW561XuwFACzP/4zYySzs/a4em0EeuQdszen0YOaGV1/fRqJE0dOlmzh8do4sNJomLO6+MFtEzguGljnkgA==", "dev": true, + "license": "MIT", "dependencies": { "@types/pouchdb-core": "*" } }, "node_modules/@types/pouchdb-adapter-node-websql": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-node-websql/-/pouchdb-adapter-node-websql-6.1.3.tgz", - "integrity": "sha512-F/P+os6Jsa7CgHtH64+Z0HfwIcj0hIRB5z8gNhF7L7dxPWoAfkopK5H2gydrP3sQrlGyN4WInF+UJW/Zu1+FKg==", + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-node-websql/-/pouchdb-adapter-node-websql-6.1.5.tgz", + "integrity": "sha512-yi68syUvHs4OM3mzKlh4zfpov64KITIAnxi387zgdby6SEfAJzWPC0dfH77iEVRDGCrKb3cKTNkl/UGHnphaow==", "dev": true, + "license": "MIT", "dependencies": { "@types/pouchdb-adapter-websql": "*", "@types/pouchdb-core": "*" } }, "node_modules/@types/pouchdb-adapter-websql": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-websql/-/pouchdb-adapter-websql-6.1.4.tgz", - "integrity": "sha512-zMJQCtXC40hBsIDRn0GhmpeGMK0f9l/OGWfLguvczROzxxcOD7REI+e6SEmX7gJKw5JuMvlfuHzkQwjmvSJbtg==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-websql/-/pouchdb-adapter-websql-6.1.7.tgz", + "integrity": "sha512-9oNkP5ZCGMkQALO9KmtbHXlkBq8i2hoCEE6/gWzRicAvL1y+WIKjEQiIIEamMhj5u5tARvW3n2/r+JXwLCyYgw==", "dev": true, + "license": "MIT", "dependencies": { "@types/pouchdb-core": "*" } @@ -5634,6 +5703,7 @@ "resolved": "https://registry.npmjs.org/@types/pouchdb-browser/-/pouchdb-browser-6.1.5.tgz", "integrity": "sha512-f+HjxEjYFpgoYWXnMI9AQZZ+SIG8dBiBPrpfWWGsCl+48rumsP5BuBWHq/aXoB8SRKYO0XdP4TNvMBWM3UATCw==", "dev": true, + "license": "MIT", "dependencies": { "@types/pouchdb-adapter-http": "*", "@types/pouchdb-adapter-idb": "*", @@ -5655,19 +5725,21 @@ } }, "node_modules/@types/pouchdb-find": { - "version": "6.3.7", - "resolved": "https://registry.npmjs.org/@types/pouchdb-find/-/pouchdb-find-6.3.7.tgz", - "integrity": "sha512-b2dr9xoZRK5Mwl8UiRA9l5j9mmCxNfqXuu63H1KZHwJLILjoIIz7BntCvM0hnlnl7Q8P8wORq0IskuaMq5Nnnw==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@types/pouchdb-find/-/pouchdb-find-7.3.3.tgz", + "integrity": "sha512-U7zXk67s9Ar+9Pwj5kSbuMnn8zif0AOOIPy4KRFeJ/S/Tk+mNS90soj+3OV21H8xyB7WTxjvS1JLablZC6C6ow==", "dev": true, + "license": "MIT", "dependencies": { "@types/pouchdb-core": "*" } }, "node_modules/@types/pouchdb-http": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@types/pouchdb-http/-/pouchdb-http-6.1.3.tgz", - "integrity": "sha512-0e9E5SqNOyPl/3FnEIbENssB4FlJsNYuOy131nxrZk36S+y1R/6qO7ZVRypWpGTqBWSuVd7gCsq2UDwO/285+w==", + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/@types/pouchdb-http/-/pouchdb-http-6.1.5.tgz", + "integrity": "sha512-9jGCAl6DUsXIl1vjuPu8tzGykAr84549P4IS0zYdrOKq5eXzQRUb/tb2hEVTmmTcYKXu2P1N55ABsdDNZvzGGA==", "dev": true, + "license": "MIT", "dependencies": { "@types/pouchdb-adapter-http": "*", "@types/pouchdb-core": "*" @@ -5678,15 +5750,17 @@ "resolved": "https://registry.npmjs.org/@types/pouchdb-mapreduce/-/pouchdb-mapreduce-6.1.10.tgz", "integrity": "sha512-AgYVqCnaA5D7cWkWyzZVuk0137N4yZsmIQTD/i3DmuMxYYoFrtWUoQu0tbA52SpTRGdL8ubQ7JFQXzA13fA6IQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/pouchdb-core": "*" } }, "node_modules/@types/pouchdb-node": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@types/pouchdb-node/-/pouchdb-node-6.1.4.tgz", - "integrity": "sha512-wnTCH8X1JOPpNOfVhz8HW0AvmdHh6pt40MuRj0jQnK7QEHsHS79WujsKTKSOF8QXtPwpvCNSsI7ut7H7tfxxJQ==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@types/pouchdb-node/-/pouchdb-node-6.1.7.tgz", + "integrity": "sha512-hryc2eCtNB3GbLcHSwU8glLaY66gDMus1AYkcIYAAxufdnK2BAy1oxaRLmnwRn1A1vG41P/t0htFD161LUnfQw==", "dev": true, + "license": "MIT", "dependencies": { "@types/pouchdb-adapter-http": "*", "@types/pouchdb-adapter-leveldb": "*", @@ -5700,15 +5774,16 @@ "resolved": "https://registry.npmjs.org/@types/pouchdb-replication/-/pouchdb-replication-6.4.7.tgz", "integrity": "sha512-slB4zOwri3SAVHioFx/FWC/KqOzzb7nDFtV+qzaKzxkf+U5zTwCbK3uRHaj0d/XQk0DwVeajf1ni3Wiyq3j2OA==", "dev": true, + "license": "MIT", "dependencies": { "@types/pouchdb-core": "*", "@types/pouchdb-find": "*" } }, "node_modules/@types/readable-stream": { - "version": "4.0.22", - "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.22.tgz", - "integrity": "sha512-/FFhJpfCLAPwAcN3mFycNUa77ddnr8jTgF5VmSNetaemWB2cIlfCA9t0YTM3JAT0wOcv8D4tjPo7pkDhK3EJIg==", + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.23.tgz", + "integrity": "sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -5728,10 +5803,11 @@ "license": "MIT" }, "node_modules/@types/tern": { - "version": "0.23.4", - "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz", - "integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==", + "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": "*" } @@ -5741,6 +5817,7 @@ "resolved": "https://registry.npmjs.org/@types/transform-pouch/-/transform-pouch-1.0.6.tgz", "integrity": "sha512-aG0K0Qvd9apKPmewg6OGOQvzVGK9EYVJKdIY+TvctVynLsSnL4X7aLYpH3bKZ364ICarR/x67aRRaUiWnjnQPg==", "dev": true, + "license": "MIT", "dependencies": { "@types/pouchdb-core": "*" } @@ -6017,39 +6094,39 @@ } }, "node_modules/@vitest/browser": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.0.16.tgz", - "integrity": "sha512-t4toy8X/YTnjYEPoY0pbDBg3EvDPg1elCDrfc+VupPHwoN/5/FNQ8Z+xBYIaEnOE2vVEyKwqYBzZ9h9rJtZVcg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.1.tgz", + "integrity": "sha512-gjjrFC4+kPVK/fN9URDJWrssU5Gqh8Az8pKG/NSfQ2V+ky8b/y1BgBg0Ug13+hOGp5pzInonmGRPn7vOgSLgzA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@vitest/mocker": "4.0.16", - "@vitest/utils": "4.0.16", + "@blazediff/core": "1.9.1", + "@vitest/mocker": "4.1.1", + "@vitest/utils": "4.1.1", "magic-string": "^0.30.21", - "pixelmatch": "7.1.0", "pngjs": "^7.0.0", "sirv": "^3.0.2", "tinyrainbow": "^3.0.3", - "ws": "^8.18.3" + "ws": "^8.19.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "4.0.16" + "vitest": "4.1.1" } }, "node_modules/@vitest/browser-playwright": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.0.16.tgz", - "integrity": "sha512-I2Fy/ANdphi1yI46d15o0M1M4M0UJrUiVKkH5oKeRZZCdPg0fw/cfTKZzv9Ge9eobtJYp4BGblMzXdXH0vcl5g==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.1.tgz", + "integrity": "sha512-dtVSBZZha2k/7P7EAXXrEAoxuIKl8Yv9f2Dk4GN/DGfmhf4DQvkvu+57okR2wq/gan1xppKjL/aBxK/kbYrbGw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@vitest/browser": "4.0.16", - "@vitest/mocker": "4.0.16", + "@vitest/browser": "4.1.1", + "@vitest/mocker": "4.1.1", "tinyrainbow": "^3.0.3" }, "funding": { @@ -6057,7 +6134,7 @@ }, "peerDependencies": { "playwright": "*", - "vitest": "4.0.16" + "vitest": "4.1.1" }, "peerDependenciesMeta": { "playwright": { @@ -6066,30 +6143,29 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.16.tgz", - "integrity": "sha512-2rNdjEIsPRzsdu6/9Eq0AYAzYdpP6Bx9cje9tL3FE5XzXRQF1fNU9pe/1yE8fCrS0HD+fBtt6gLPh6LI57tX7A==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.1.tgz", + "integrity": "sha512-nZ4RWwGCoGOQRMmU/Q9wlUY540RVRxJZ9lxFsFfy0QV7Zmo5VVBhB6Sl9Xa0KIp2iIs3zWfPlo9LcY1iqbpzCw==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.0.16", - "ast-v8-to-istanbul": "^0.3.8", + "@vitest/utils": "4.1.1", + "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.2.0", - "magicast": "^0.5.1", + "magicast": "^0.5.2", "obug": "^2.1.1", - "std-env": "^3.10.0", + "std-env": "^4.0.0-rc.1", "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.0.16", - "vitest": "4.0.16" + "@vitest/browser": "4.1.1", + "vitest": "4.1.1" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -6098,17 +6174,17 @@ } }, "node_modules/@vitest/expect": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.16.tgz", - "integrity": "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.1.tgz", + "integrity": "sha512-xAV0fqBTk44Rn6SjJReEQkHP3RrqbJo6JQ4zZ7/uVOiJZRarBtblzrOfFIZeYUrukp2YD6snZG6IBqhOoHTm+A==", "dev": true, "license": "MIT", "dependencies": { - "@standard-schema/spec": "^1.0.0", + "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.16", - "@vitest/utils": "4.0.16", - "chai": "^6.2.1", + "@vitest/spy": "4.1.1", + "@vitest/utils": "4.1.1", + "chai": "^6.2.2", "tinyrainbow": "^3.0.3" }, "funding": { @@ -6126,13 +6202,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", - "integrity": "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.1.tgz", + "integrity": "sha512-h3BOylsfsCLPeceuCPAAJ+BvNwSENgJa4hXoXu4im0bs9Lyp4URc4JYK4pWLZ4pG/UQn7AT92K6IByi6rE6g3A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.0.16", + "@vitest/spy": "4.1.1", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -6141,7 +6217,7 @@ }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0-0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "msw": { @@ -6153,9 +6229,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.16.tgz", - "integrity": "sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.1.tgz", + "integrity": "sha512-GM+TEQN5WhOygr1lp7skeVjdLPqqWMHsfzXrcHAqZJi/lIVh63H0kaRCY8MDhNWikx19zBUK8ceaLB7X5AH9NQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6166,13 +6242,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.16.tgz", - "integrity": "sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.1.tgz", + "integrity": "sha512-f7+FPy75vN91QGWsITueq0gedwUZy1fLtHOCMeQpjs8jTekAHeKP80zfDEnhrleviLHzVSDXIWuCIOFn3D3f8A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.0.16", + "@vitest/utils": "4.1.1", "pathe": "^2.0.3" }, "funding": { @@ -6180,13 +6256,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.16.tgz", - "integrity": "sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.1.tgz", + "integrity": "sha512-kMVSgcegWV2FibXEx9p9WIKgje58lcTbXgnJixfcg15iK8nzCXhmalL0ZLtTWLW9PH1+1NEDShiFFedB3tEgWg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.16", + "@vitest/pretty-format": "4.1.1", + "@vitest/utils": "4.1.1", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -6195,9 +6272,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.16.tgz", - "integrity": "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.1.tgz", + "integrity": "sha512-6Ti/KT5OVaiupdIZEuZN7l3CZcR0cxnxt70Z0//3CtwgObwA6jZhmVBA3yrXSVN3gmwjgd7oDNLlsXz526gpRA==", "dev": true, "license": "MIT", "funding": { @@ -6205,13 +6282,14 @@ } }, "node_modules/@vitest/utils": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.16.tgz", - "integrity": "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.1.tgz", + "integrity": "sha512-cNxAlaB3sHoCdL6pj6yyUXv9Gry1NHNg0kFTXdvSIZXLHsqKH7chiWOkwJ5s5+d/oMwcoG9T0bKU38JZWKusrQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.16", + "@vitest/pretty-format": "4.1.1", + "convert-source-map": "^2.0.0", "tinyrainbow": "^3.0.3" }, "funding": { @@ -6357,6 +6435,18 @@ "uint8arrays": "^5.1.0" } }, + "node_modules/@waku/enr/node_modules/@noble/secp256k1": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.2.tgz", + "integrity": "sha512-/qzwYl5eFLH8OWIecQWM31qld2g1NfjgylK+TNhqtaUKP37Nm+Y+z30Fjhw0Ct8p9yCQEm2N3W/AckdIb3SMcQ==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, "node_modules/@waku/interfaces": { "version": "0.0.34", "resolved": "https://registry.npmjs.org/@waku/interfaces/-/interfaces-0.0.34.tgz", @@ -6523,15 +6613,15 @@ } }, "node_modules/@wdio/config": { - "version": "9.25.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.25.0.tgz", - "integrity": "sha512-EWa7l1rrbSNthCRDpdBw7ESAa1/jAjSsWCGkaVAO0HMOGlQjzvYI6gNi4KUeymnurDZ2IPr0jr+f9We6AWi6QA==", + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.27.0.tgz", + "integrity": "sha512-9y8z7ugIbU6ycKrA2SqCpKh1/hobut2rDq9CLt/BNVzSlebBBVOTMiAt1XroZzcPnA7/ZqpbkpOsbpPUaAQuNQ==", "dev": true, "license": "MIT", "dependencies": { "@wdio/logger": "9.18.0", - "@wdio/types": "9.25.0", - "@wdio/utils": "9.25.0", + "@wdio/types": "9.27.0", + "@wdio/utils": "9.27.0", "deepmerge-ts": "^7.0.3", "glob": "^10.2.2", "import-meta-resolve": "^4.0.0", @@ -6541,6 +6631,13 @@ "node": ">=18.20.0" } }, + "node_modules/@wdio/config/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/@wdio/config/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -6573,6 +6670,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@wdio/config/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/@wdio/config/node_modules/minimatch": { "version": "9.0.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", @@ -6623,19 +6727,6 @@ "node": ">=18.20.0" } }, - "node_modules/@wdio/logger/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/@wdio/logger/node_modules/chalk": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", @@ -6649,26 +6740,10 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/logger/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/@wdio/protocols": { - "version": "9.25.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.25.0.tgz", - "integrity": "sha512-PErbZqdpFmE69bRuku3OR34Ro2xuZNNLXYFOcJnjXJVzf5+ApDyGHYrMlvhtrrSy9/55LUybk851ppjS+3RoDA==", + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.27.0.tgz", + "integrity": "sha512-rIk69BsY1+6uU2PEN5FiRpI6K7HJ86YHzZRFBe4iRzKXQgGNk1zWzbdVJIuNFoOWsnmYUkK42KSSOT4Le6EmiQ==", "dev": true, "license": "MIT" }, @@ -6695,10 +6770,17 @@ "undici-types": "~6.21.0" } }, + "node_modules/@wdio/repl/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@wdio/types": { - "version": "9.25.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.25.0.tgz", - "integrity": "sha512-ovSEcUBLz6gVDIsBZYKQXz8EGU37jS8sqbmlOe5+jB4XbsTBCyTLjQK/rO7LWQAKJcs0vBq+Pd+VrlsFtA7tTQ==", + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.27.0.tgz", + "integrity": "sha512-DQJ+OdRBqUBcQ30DN2Z651hEVh3OoxnlDUSRqlWy9An2AY6v9rYWTj825B6zsj5pLLEToYO1tfwWq0ab183pXg==", "dev": true, "license": "MIT", "dependencies": { @@ -6718,16 +6800,23 @@ "undici-types": "~6.21.0" } }, + "node_modules/@wdio/types/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@wdio/utils": { - "version": "9.25.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.25.0.tgz", - "integrity": "sha512-w/ej8gZkc2tZr8L91ATyA1AWrbPDYDOvblQ7r+zt1uPRobuA4H98GME7Zm7i3FIP695BvV4G35Gcs5NssZW1pw==", + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.27.0.tgz", + "integrity": "sha512-fUasd5OKJTy2seJfWnYZ9xlxTtY0p/Kyeuh7Tbb8kcofBqmBi2fTvM3sfZlo1tGQX9yCh+IS2N7hlfyFMmuZ+w==", "dev": true, "license": "MIT", "dependencies": { "@puppeteer/browsers": "^2.2.0", "@wdio/logger": "9.18.0", - "@wdio/types": "9.25.0", + "@wdio/types": "9.27.0", "decamelize": "^6.0.0", "deepmerge-ts": "^7.0.3", "edgedriver": "^6.1.2", @@ -6775,14 +6864,20 @@ "license": "Apache-2.0 OR MIT" }, "node_modules/abstract-leveldown": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.7.2.tgz", - "integrity": "sha512-+OVvxH2rHVEhWLdbudP6p0+dNMXu8JA1CbhP19T8paTYAcX7oJ4OVjT+ZUVpv7mITxXHqDMej+GdqXBmXkw09w==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", + "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", - "dev": true, "license": "MIT", "dependencies": { + "buffer": "^5.5.0", + "immediate": "^3.2.3", + "level-concat-iterator": "~2.0.0", + "level-supports": "~1.0.0", "xtend": "~4.0.0" + }, + "engines": { + "node": ">=6" } }, "node_modules/acorn": { @@ -6837,18 +6932,23 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -6860,9 +6960,9 @@ } }, "node_modules/any-signal": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/any-signal/-/any-signal-4.1.1.tgz", - "integrity": "sha512-iADenERppdC+A2YKbOXXB2WUeABLaM6qnpZ70kZbPZ1cZMMJ7eF+3CaYm+/PhBizgkzlvssC7QuHS30oOiQYWA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/any-signal/-/any-signal-4.2.0.tgz", + "integrity": "sha512-LndMvYuAPf4rC195lk7oSFuHOYFpOszIYrNYv0gHAvz+aEhE9qPZLhmrIz5pXP2BSsPOXvsuHDXEGaiQhIh9wA==", "license": "Apache-2.0 OR MIT", "engines": { "node": ">=16.0.0", @@ -6907,6 +7007,13 @@ "node": ">= 14" } }, + "node_modules/archiver-utils/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/archiver-utils/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -6917,10 +7024,36 @@ "balanced-match": "^1.0.0" } }, + "node_modules/archiver-utils/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/archiver-utils/node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -6938,6 +7071,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/archiver-utils/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/archiver-utils/node_modules/minimatch": { "version": "9.0.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", @@ -6971,6 +7111,78 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/archiver/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/archiver/node_modules/tar-stream": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", + "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -7142,24 +7354,17 @@ } }, "node_modules/ast-v8-to-istanbul": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz", - "integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", + "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", - "js-tokens": "^9.0.1" + "js-tokens": "^10.0.0" } }, - "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT" - }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -7204,9 +7409,9 @@ } }, "node_modules/b4a": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", - "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", + "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", "dev": true, "license": "Apache-2.0", "peerDependencies": { @@ -7219,10 +7424,13 @@ } }, "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 + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/bare-events": { "version": "2.8.2", @@ -7240,9 +7448,9 @@ } }, "node_modules/bare-fs": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.5.tgz", - "integrity": "sha512-XvwYM6VZqKoqDll8BmSww5luA5eflDzY0uEFfBJtFKe4PAAtxBjU3YIxzIBzhyaEQBy1VXEQBto4cpN5RZJw+w==", + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.6.tgz", + "integrity": "sha512-1QovqDrR80Pmt5HPAsMsXTCFcDYr+NSUKW6nd6WO5v0JBmnItc/irNRzm2KOQ5oZ69P37y+AMujNyNtG+1Rggw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -7285,20 +7493,24 @@ } }, "node_modules/bare-stream": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.8.1.tgz", - "integrity": "sha512-bSeR8RfvbRwDpD7HWZvn8M3uYNDrk7m9DQjYOFkENZlXW8Ju/MPaqUPQq5LqJ3kyjEm07siTaAQ7wBKCU59oHg==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.11.0.tgz", + "integrity": "sha512-Y/+iQ49fL3rIn6w/AVxI/2+BRrpmzJvdWt5Jv8Za6Ngqc6V227c+pYjYYgLdpR3MwQ9ObVXD0ZrqoBztakM0rw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "streamx": "^2.21.0", + "streamx": "^2.25.0", "teex": "^1.0.1" }, "peerDependencies": { + "bare-abort-controller": "*", "bare-buffer": "*", "bare-events": "*" }, "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, "bare-buffer": { "optional": true }, @@ -7308,9 +7520,9 @@ } }, "node_modules/bare-url": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", - "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.0.tgz", + "integrity": "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -7338,9 +7550,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.8", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz", - "integrity": "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==", + "version": "2.10.10", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz", + "integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -7361,15 +7573,14 @@ } }, "node_modules/bl": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.6.tgz", - "integrity": "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "license": "MIT", "dependencies": { - "@types/readable-stream": "^4.0.0", - "buffer": "^6.0.3", + "buffer": "^5.5.0", "inherits": "^2.0.4", - "readable-stream": "^4.2.0" + "readable-stream": "^3.4.0" } }, "node_modules/boolbase": { @@ -7380,20 +7591,21 @@ "license": "ISC" }, "node_modules/bowser": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", - "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/braces": { @@ -7410,15 +7622,15 @@ } }, "node_modules/broker-factory": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.10.tgz", - "integrity": "sha512-BzqK5GYFhvVFvO13uzPN0SCiOsOQuhMUbsGvTXDJMA2/N4GvIlFdxEuueE+60Zk841bBU5G3+fl2cqYEo0wgGg==", + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.14.tgz", + "integrity": "sha512-L45k5HMbPIrMid0nTOZ/UPXG/c0aRuQKVrSDFIb1zOkvfiyHgYmIjc3cSiN1KwQIvRDOtKE0tfb3I9EZ3CmpQQ==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", - "fast-unique-numbers": "^9.0.24", + "@babel/runtime": "^7.29.2", + "fast-unique-numbers": "^9.0.27", "tslib": "^2.8.1", - "worker-factory": "^7.0.46" + "worker-factory": "^7.0.49" } }, "node_modules/browserslist": { @@ -7457,9 +7669,9 @@ } }, "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "funding": [ { "type": "github", @@ -7477,7 +7689,7 @@ "license": "MIT", "dependencies": { "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "ieee754": "^1.1.13" } }, "node_modules/buffer-crc32": { @@ -7493,7 +7705,8 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" }, "node_modules/builtin-modules": { "version": "5.0.0", @@ -7579,9 +7792,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001780", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", - "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", + "version": "1.0.30001781", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", + "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", "dev": true, "funding": [ { @@ -7629,6 +7842,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -7650,9 +7864,9 @@ } }, "node_modules/cheerio": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz", - "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", "dev": true, "license": "MIT", "dependencies": { @@ -7661,11 +7875,11 @@ "domhandler": "^5.0.3", "domutils": "^3.2.2", "encoding-sniffer": "^0.2.1", - "htmlparser2": "^10.0.0", + "htmlparser2": "^10.1.0", "parse5": "^7.3.0", "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", - "undici": "^7.12.0", + "undici": "^7.19.0", "whatwg-mimetype": "^4.0.0" }, "engines": { @@ -7694,10 +7908,11 @@ } }, "node_modules/chokidar": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", - "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "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" }, @@ -7735,6 +7950,27 @@ "node": ">=12" } }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -7749,6 +7985,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -7759,7 +7996,8 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" }, "node_modules/colorette": { "version": "1.4.0", @@ -7787,7 +8025,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/compress-commons": { "version": "6.0.2", @@ -7806,11 +8045,54 @@ "node": ">= 14" } }, + "node_modules/compress-commons/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/concat-stream": { "version": "2.0.0", @@ -7827,20 +8109,6 @@ "typedarray": "^0.0.6" } }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -7881,6 +8149,48 @@ "node": ">= 14" } }, + "node_modules/crc32-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/crelt": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", @@ -8108,7 +8418,8 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/deepmerge": { "version": "4.3.1", @@ -8144,52 +8455,12 @@ "node": ">=6" } }, - "node_modules/deferred-leveldown/node_modules/abstract-leveldown": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", - "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", - "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "immediate": "^3.2.3", - "level-concat-iterator": "~2.0.0", - "level-supports": "~1.0.0", - "xtend": "~4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/deferred-leveldown/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -8207,6 +8478,7 @@ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, + "license": "MIT", "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -8262,7 +8534,8 @@ "node_modules/diff-match-patch": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", - "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", + "license": "Apache-2.0" }, "node_modules/dir-glob": { "version": "3.0.1", @@ -8287,16 +8560,17 @@ "weald": "^1.0.2" } }, - "node_modules/dns-packet": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", - "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", - "license": "MIT", + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" + "esutils": "^2.0.2" }, "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, "node_modules/dom-serializer": { @@ -8512,9 +8786,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.313", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", - "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==", + "version": "1.5.325", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.325.tgz", + "integrity": "sha512-PwfIw7WQSt3xX7yOf5OE/unLzsK9CaN2f/FvV3WjPR1Knoc1T9vePRVV4W1EM301JzzysK51K7FNKcusCr0zYA==", "dev": true, "license": "ISC" }, @@ -8540,47 +8814,6 @@ "node": ">=6" } }, - "node_modules/encoding-down/node_modules/abstract-leveldown": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.3.0.tgz", - "integrity": "sha512-TU5nlYgta8YrBMNpc9FwQzRbiXsj49gsALsXadbGHt9CROPzX5fB0rWDR5mtdpOOKa5XqRFpbj1QroPAoPzVjQ==", - "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "immediate": "^3.2.3", - "level-concat-iterator": "~2.0.0", - "level-supports": "~1.0.0", - "xtend": "~4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/encoding-down/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/encoding-sniffer": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", @@ -8637,9 +8870,9 @@ } }, "node_modules/es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", "dev": true, "license": "MIT", "dependencies": { @@ -8720,6 +8953,7 @@ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -8838,6 +9072,7 @@ "resolved": "https://registry.npmjs.org/esbuild-plugin-inline-worker/-/esbuild-plugin-inline-worker-0.1.1.tgz", "integrity": "sha512-VmFqsQKxUlbM51C1y5bRiMeyc1x2yTdMXhKB6S//++g9aCBg8TfGsbKxl5ZDkCGquqLY+RmEk93TBNd0i35dPA==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "latest", "find-cache-dir": "^3.3.1" @@ -8874,6 +9109,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -8904,26 +9140,26 @@ } }, "node_modules/eslint": { - "version": "9.39.3", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", - "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "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", - "@eslint/config-array": "^0.21.1", + "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.3", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "ajv": "^6.12.4", + "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", @@ -8942,7 +9178,7 @@ "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -8969,6 +9205,7 @@ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", @@ -8980,6 +9217,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.1" } @@ -9046,27 +9284,34 @@ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, + "node_modules/eslint-plugin-import/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-import/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint-plugin-import/node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.1" } }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/eslint-plugin-import/node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -9085,14 +9330,15 @@ "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-svelte": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.15.0.tgz", - "integrity": "sha512-QKB7zqfuB8aChOfBTComgDptMf2yxiJx7FE04nneCmtQzgTHvY8UJkuh8J2Rz7KB9FFV9aTHX6r7rdYGvG8T9Q==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.16.0.tgz", + "integrity": "sha512-DJXxqpYZUxcE0SfYo8EJzV2ZC+zAD7fJp1n1HwcEMRR1cOEUYvjT9GuzJeNghMjgb7uxuK3IJAzI+x6zzUxO5A==", "dev": true, "license": "MIT", "dependencies": { @@ -9208,6 +9454,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -9215,6 +9462,37 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/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/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/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", @@ -9294,10 +9572,11 @@ } }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -9306,13 +9585,14 @@ } }, "node_modules/esrap": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz", - "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.4.tgz", + "integrity": "sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" + "@jridgewell/sourcemap-codec": "^1.4.15", + "@typescript-eslint/types": "^8.2.0" } }, "node_modules/esrecurse": { @@ -9333,6 +9613,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -9352,6 +9633,7 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -9372,15 +9654,16 @@ } }, "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", "engines": { "node": ">=0.8.x" } @@ -9490,15 +9773,16 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-unique-numbers": { - "version": "9.0.24", - "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.24.tgz", - "integrity": "sha512-Dv0BYn4waOWse94j16rsZ5w/0zoaCa74O3q6IZjMqaXbtT92Q+Sb6pPk+phGzD8Xh+nueQmSRI3tSCaHKidzKw==", + "version": "9.0.27", + "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.27.tgz", + "integrity": "sha512-nDA9ADeINN8SA2u2wCtU+siWFTTDqQR37XvgPIDDmboWQeExz7X0mImxuaN+kJddliIqy2FpVRmnvRZ+j8i1/A==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", + "@babel/runtime": "^7.29.2", "tslib": "^2.8.1" }, "engines": { @@ -9506,9 +9790,9 @@ } }, "node_modules/fast-xml-builder": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.3.tgz", - "integrity": "sha512-1o60KoFw2+LWKQu3IdcfcFlGTW4dpqEWmjhYec6H82AYZU2TVBXep6tMl8Z1Y+wM+ZrzCwe3BZ9Vyd9N2rIvmg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", + "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", "funding": [ { "type": "github", @@ -9521,9 +9805,9 @@ } }, "node_modules/fast-xml-parser": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", - "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", + "version": "5.5.8", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.8.tgz", + "integrity": "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==", "funding": [ { "type": "github", @@ -9532,17 +9816,18 @@ ], "license": "MIT", "dependencies": { - "fast-xml-builder": "^1.0.0", - "strnum": "^2.1.2" + "fast-xml-builder": "^1.1.4", + "path-expression-matcher": "^1.2.0", + "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dev": true, "license": "ISC", "dependencies": { @@ -9575,6 +9860,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-2.2.0.tgz", "integrity": "sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==", + "license": "Unlicense", "dependencies": { "set-cookie-parser": "^2.4.8", "tough-cookie": "^4.0.0" @@ -9583,7 +9869,8 @@ "node_modules/fflate": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==" + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" }, "node_modules/file-entry-cache": { "version": "8.0.0", @@ -9616,6 +9903,7 @@ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", "dev": true, + "license": "MIT", "dependencies": { "commondir": "^1.0.1", "make-dir": "^3.0.2", @@ -9633,6 +9921,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -9645,39 +9934,39 @@ } }, "node_modules/firebase": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/firebase/-/firebase-12.4.0.tgz", - "integrity": "sha512-/chNgDQ6ppPPGOQO4jctxOa/5JeQxuhaxA7Y90K0I+n/wPfoO8mRveedhVUdo7ExLcWUivnnow/ouSLYSI5Icw==", + "version": "12.11.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-12.11.0.tgz", + "integrity": "sha512-W9f3Y+cgQYgF9gvCGxt0upec8zwAtiQVcHuU8MfzUIgVU/9fRQWtu48Geiv1lsigtBz9QHML++Km9xAKO5GB5Q==", "license": "Apache-2.0", "dependencies": { - "@firebase/ai": "2.4.0", - "@firebase/analytics": "0.10.19", - "@firebase/analytics-compat": "0.2.25", - "@firebase/app": "0.14.4", - "@firebase/app-check": "0.11.0", - "@firebase/app-check-compat": "0.4.0", - "@firebase/app-compat": "0.5.4", + "@firebase/ai": "2.10.0", + "@firebase/analytics": "0.10.21", + "@firebase/analytics-compat": "0.2.27", + "@firebase/app": "0.14.10", + "@firebase/app-check": "0.11.2", + "@firebase/app-check-compat": "0.4.2", + "@firebase/app-compat": "0.5.10", "@firebase/app-types": "0.9.3", - "@firebase/auth": "1.11.0", - "@firebase/auth-compat": "0.6.0", - "@firebase/data-connect": "0.3.11", - "@firebase/database": "1.1.0", - "@firebase/database-compat": "2.1.0", - "@firebase/firestore": "4.9.2", - "@firebase/firestore-compat": "0.4.2", - "@firebase/functions": "0.13.1", - "@firebase/functions-compat": "0.4.1", - "@firebase/installations": "0.6.19", - "@firebase/installations-compat": "0.2.19", - "@firebase/messaging": "0.12.23", - "@firebase/messaging-compat": "0.2.23", - "@firebase/performance": "0.7.9", - "@firebase/performance-compat": "0.2.22", - "@firebase/remote-config": "0.7.0", - "@firebase/remote-config-compat": "0.2.20", - "@firebase/storage": "0.14.0", - "@firebase/storage-compat": "0.4.0", - "@firebase/util": "1.13.0" + "@firebase/auth": "1.12.2", + "@firebase/auth-compat": "0.6.4", + "@firebase/data-connect": "0.5.0", + "@firebase/database": "1.1.2", + "@firebase/database-compat": "2.1.2", + "@firebase/firestore": "4.13.0", + "@firebase/firestore-compat": "0.4.7", + "@firebase/functions": "0.13.3", + "@firebase/functions-compat": "0.4.3", + "@firebase/installations": "0.6.21", + "@firebase/installations-compat": "0.2.21", + "@firebase/messaging": "0.12.25", + "@firebase/messaging-compat": "0.2.25", + "@firebase/performance": "0.7.11", + "@firebase/performance-compat": "0.2.24", + "@firebase/remote-config": "0.8.2", + "@firebase/remote-config-compat": "0.2.23", + "@firebase/storage": "0.14.2", + "@firebase/storage-compat": "0.4.2", + "@firebase/util": "1.15.0" } }, "node_modules/flat-cache": { @@ -9695,9 +9984,9 @@ } }, "node_modules/flatted": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", - "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -9755,16 +10044,6 @@ "node": ">=6 <7 || >=8" } }, - "node_modules/fs-extra/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -9773,9 +10052,9 @@ "license": "ISC" }, "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -9792,6 +10071,7 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -9936,9 +10216,9 @@ } }, "node_modules/get-port": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", - "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.2.0.tgz", + "integrity": "sha512-afP4W205ONCuMoPBqcR6PSXnzX35KTcJygfJfcp+QY+uwm3p20p1YczWXhlICIzGMCxYBQcySEcOgsJcrkyobg==", "dev": true, "license": "MIT", "engines": { @@ -9997,9 +10277,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", - "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", "dev": true, "license": "MIT", "dependencies": { @@ -10053,6 +10333,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -10110,6 +10391,24 @@ "node": ">=8" } }, + "node_modules/globby/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/globby/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/globby/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -10190,6 +10489,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -10199,6 +10499,7 @@ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" }, @@ -10262,6 +10563,7 @@ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -10296,9 +10598,9 @@ "license": "MIT" }, "node_modules/htmlparser2": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", - "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", @@ -10311,14 +10613,14 @@ "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", - "domutils": "^3.2.1", - "entities": "^6.0.0" + "domutils": "^3.2.2", + "entities": "^7.0.1" } }, "node_modules/htmlparser2/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -10362,6 +10664,15 @@ "node": ">= 14" } }, + "node_modules/iceberg-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -10448,8 +10759,9 @@ "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.19" } @@ -10469,7 +10781,8 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, "node_modules/ini": { "version": "1.3.8", @@ -10509,9 +10822,9 @@ } }, "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "license": "MIT", "engines": { "node": ">= 12" @@ -10684,8 +10997,9 @@ "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -10740,6 +11054,7 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -10771,6 +11086,7 @@ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -10779,9 +11095,9 @@ } }, "node_modules/is-network-error": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", - "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.1.tgz", + "integrity": "sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==", "license": "MIT", "engines": { "node": ">=16" @@ -11079,21 +11395,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-reports": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", @@ -11115,18 +11416,24 @@ "license": "Apache-2.0 OR MIT" }, "node_modules/it-byte-stream": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/it-byte-stream/-/it-byte-stream-2.0.3.tgz", - "integrity": "sha512-h7FFcn4DWiWsJw1dCJhuPdiY8cGi1z8g4aLAfFspTaJbwQxvEMlEBFG/f8lIVGwM8YK26ClM4/9lxLVhF33b8g==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/it-byte-stream/-/it-byte-stream-2.0.4.tgz", + "integrity": "sha512-8pS0OvkBYwQ206pRLgoLDAiHP6c8wYZJ1ig8KDmP5NOrzMxeH2Wv2ktXIjYHwdu7RPOsnxQb0vKo+O784L/m5g==", "license": "Apache-2.0 OR MIT", "dependencies": { "abort-error": "^1.0.1", "it-queueless-pushable": "^2.0.0", "it-stream-types": "^2.0.2", - "race-signal": "^1.1.3", + "race-signal": "^2.0.0", "uint8arraylist": "^2.4.8" } }, + "node_modules/it-byte-stream/node_modules/race-signal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/race-signal/-/race-signal-2.0.0.tgz", + "integrity": "sha512-P31bLhE4ByBX/70QDXMutxnqgwrF1WUXea1O8DXuviAgkdbQ1iQMQotNgzJIBC9yUSn08u/acZrMUhgw7w6GpA==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/it-drain": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/it-drain/-/it-drain-3.0.10.tgz", @@ -11143,9 +11450,9 @@ } }, "node_modules/it-foreach": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/it-foreach/-/it-foreach-2.1.4.tgz", - "integrity": "sha512-gFntBbNLpVK9uDmaHusugICD8/Pp+OCqbF5q1Z8K+B8WaG20YgMePWbMxI1I25+JmNWWr3hk0ecKyiI9pOLgeA==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/it-foreach/-/it-foreach-2.1.5.tgz", + "integrity": "sha512-9tIp+NFVODmGV/49JUKVxW3+8RrPkYrmUaXUM4W6lMC5POM/1gegckNjBmDe5xgBa7+RE9HKBmRTAdY5V+bWSQ==", "license": "Apache-2.0 OR MIT", "dependencies": { "it-peekable": "^3.0.0" @@ -11169,9 +11476,9 @@ } }, "node_modules/it-length-prefixed-stream": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/it-length-prefixed-stream/-/it-length-prefixed-stream-2.0.3.tgz", - "integrity": "sha512-Ns3jNFy2mcFnV59llCYitJnFHapg8wIcOsWkEaAwOkG9v4HBCk24nze/zGDQjiJdDTyFXTT5GOY3M/uaksot3w==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/it-length-prefixed-stream/-/it-length-prefixed-stream-2.0.4.tgz", + "integrity": "sha512-ugHDOQCkC2Dx2pQaJ+W4OIM6nZFBwlpgdQVVOfdX4c1Os47d6PMsfrkTrzRwZdBCMZb+JISZNP2gjU/DHN/z9A==", "license": "Apache-2.0 OR MIT", "dependencies": { "abort-error": "^1.0.1", @@ -11265,29 +11572,41 @@ } }, "node_modules/it-queue": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/it-queue/-/it-queue-1.1.0.tgz", - "integrity": "sha512-aK9unJRIaJc9qiv53LByhF7/I2AuD7Ro4oLfLieVLL9QXNvRx++ANMpv8yCp2UO0KAtBuf70GOxSYb6ElFVRpQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/it-queue/-/it-queue-1.1.1.tgz", + "integrity": "sha512-yeYCV22WF1QDyb3ylw+g3TGEdkmnoHUH2mc12QoGOQuxW4XP1V7Zd3BfsEF1iq2IFBwIK7wCPUcRLTAQVeZ3SQ==", "license": "Apache-2.0 OR MIT", "dependencies": { "abort-error": "^1.0.1", "it-pushable": "^3.2.3", "main-event": "^1.0.0", "race-event": "^1.3.0", - "race-signal": "^1.1.3" + "race-signal": "^2.0.0" } }, + "node_modules/it-queue/node_modules/race-signal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/race-signal/-/race-signal-2.0.0.tgz", + "integrity": "sha512-P31bLhE4ByBX/70QDXMutxnqgwrF1WUXea1O8DXuviAgkdbQ1iQMQotNgzJIBC9yUSn08u/acZrMUhgw7w6GpA==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/it-queueless-pushable": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/it-queueless-pushable/-/it-queueless-pushable-2.0.2.tgz", - "integrity": "sha512-2BqIt7XvDdgEgudLAdJkdseAwbVSBc0yAd8yPVHrll4eBuJPWIj9+8C3OIxzEKwhswLtd3bi+yLrzgw9gCyxMA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/it-queueless-pushable/-/it-queueless-pushable-2.0.3.tgz", + "integrity": "sha512-USa5EzTvmQswOcVE7+o6qsj2o2G+6KHCxSogPOs23sGYkDWFidhqVO7dAvv6ve/Z+Q+nvxpEa9rrRo6VEK7w4Q==", "license": "Apache-2.0 OR MIT", "dependencies": { "abort-error": "^1.0.1", "p-defer": "^4.0.1", - "race-signal": "^1.1.3" + "race-signal": "^2.0.0" } }, + "node_modules/it-queueless-pushable/node_modules/race-signal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/race-signal/-/race-signal-2.0.0.tgz", + "integrity": "sha512-P31bLhE4ByBX/70QDXMutxnqgwrF1WUXea1O8DXuviAgkdbQ1iQMQotNgzJIBC9yUSn08u/acZrMUhgw7w6GpA==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/it-reader": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/it-reader/-/it-reader-6.0.4.tgz", @@ -11384,9 +11703,9 @@ "license": "MIT" }, "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==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", "dev": true, "license": "MIT" }, @@ -11433,14 +11752,16 @@ "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" }, "node_modules/json5": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, + "license": "MIT", "dependencies": { "minimist": "^1.2.0" }, @@ -11494,13 +11815,6 @@ "util-deprecate": "~1.0.1" } }, - "node_modules/jszip/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, "node_modules/jszip/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -11564,13 +11878,6 @@ "util-deprecate": "~1.0.1" } }, - "node_modules/lazystream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, "node_modules/lazystream/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -11612,30 +11919,6 @@ "node": ">=6" } }, - "node_modules/level-codec/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/level-concat-iterator": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", @@ -11673,20 +11956,6 @@ "node": ">=6" } }, - "node_modules/level-iterator-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/level-js": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/level-js/-/level-js-5.0.2.tgz", @@ -11700,47 +11969,6 @@ "ltgt": "^2.1.2" } }, - "node_modules/level-js/node_modules/abstract-leveldown": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", - "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", - "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "immediate": "^3.2.3", - "level-concat-iterator": "~2.0.0", - "level-supports": "~1.0.0", - "xtend": "~4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/level-js/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/level-packager": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.1.1.tgz", @@ -11775,47 +12003,6 @@ "end-stream": "~0.1.0" } }, - "node_modules/level/node_modules/abstract-leveldown": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", - "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", - "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "immediate": "^3.2.3", - "level-concat-iterator": "~2.0.0", - "level-supports": "~1.0.0", - "xtend": "~4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/level/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/level/node_modules/leveldown": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.6.0.tgz", @@ -11877,6 +12064,30 @@ "node": ">=10" } }, + "node_modules/leveldown/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/leveldown/node_modules/level-concat-iterator": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-3.1.0.tgz", @@ -11921,6 +12132,7 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -12031,13 +12243,15 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -12077,7 +12291,8 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.zip": { "version": "4.2.0", @@ -12120,10 +12335,14 @@ "license": "MIT" }, "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "node_modules/ltgt": { "version": "2.2.1", @@ -12142,14 +12361,14 @@ } }, "node_modules/magicast": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", - "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, @@ -12164,6 +12383,7 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, + "license": "MIT", "dependencies": { "semver": "^6.0.0" }, @@ -12179,6 +12399,7 @@ "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" } @@ -12232,12 +12453,16 @@ "safe-buffer": "~5.1.1" } }, - "node_modules/memdown/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "node_modules/memdown/node_modules/abstract-leveldown": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.7.2.tgz", + "integrity": "sha512-+OVvxH2rHVEhWLdbudP6p0+dNMXu8JA1CbhP19T8paTYAcX7oJ4OVjT+ZUVpv7mITxXHqDMej+GdqXBmXkw09w==", + "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "xtend": "~4.0.0" + } }, "node_modules/merge2": { "version": "1.4.1", @@ -12313,27 +12538,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minimatch/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/minimatch/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -12367,9 +12571,9 @@ "license": "MIT" }, "node_modules/modern-tar": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.5.tgz", - "integrity": "sha512-YTefgdpKKFgoTDbEUqXqgUJct2OG6/4hs4XWLsxcHkDLj/x/V8WmKIRppPnXP5feQ7d1vuYWSp3qKkxfwaFaxA==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.6.tgz", + "integrity": "sha512-sweCIVXzx1aIGTCdzcMlSZt1h8k5Tmk08VNAuRk3IU28XamGiOH5ypi11g6De2CH7PhYqSSnGy2A/EFhbWnVKg==", "dev": true, "license": "MIT", "engines": { @@ -12381,6 +12585,7 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -12397,9 +12602,9 @@ } }, "node_modules/mqtt": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.14.1.tgz", - "integrity": "sha512-NxkPxE70Uq3Ph7goefQa7ggSsVzHrayCD0OyxlJgITN/EbzlZN+JEPmaAZdxP1LsIT5FamDyILoQTF72W7Nnbw==", + "version": "5.15.1", + "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.15.1.tgz", + "integrity": "sha512-V1WnkGuJh3ec9QXzy5Iylw8OOBK+Xu1WhxcQ9mMpLThG+/JZIMV1PgLNRgIiqXhZnvnVLsuyxHl5A/3bHHbcAA==", "license": "MIT", "dependencies": { "@types/readable-stream": "^4.0.21", @@ -12439,11 +12644,110 @@ "process-nextick-args": "^2.0.1" } }, + "node_modules/mqtt-packet/node_modules/bl": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.6.tgz", + "integrity": "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==", + "license": "MIT", + "dependencies": { + "@types/readable-stream": "^4.0.0", + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^4.2.0" + } + }, + "node_modules/mqtt-packet/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/mqtt-packet/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/mqtt/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/mqtt/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/mqtt/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -12465,9 +12769,9 @@ "license": "MIT" }, "node_modules/multiformats": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", - "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", + "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", "license": "Apache-2.0 OR MIT" }, "node_modules/nanoid": { @@ -12504,8 +12808,9 @@ "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" }, "node_modules/netmask": { "version": "2.0.2", @@ -12517,9 +12822,9 @@ } }, "node_modules/node-abi": { - "version": "3.88.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.88.0.tgz", - "integrity": "sha512-At6b4UqIEVudaqPsXjmUO1r/N5BUr4yhDGs5PkBE8/oG5+TfLPhFechiskFsnT6Ql0VfUXbalUUCbfXxtj7K+w==", + "version": "3.89.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", + "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", "license": "MIT", "dependencies": { "semver": "^7.3.5" @@ -12545,6 +12850,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -12629,6 +12935,7 @@ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -12752,17 +13059,18 @@ } }, "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "license": "MIT", "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -12838,6 +13146,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -12853,6 +13162,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -12864,9 +13174,9 @@ } }, "node_modules/p-queue": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.0.0.tgz", - "integrity": "sha512-KO1RyxstL9g1mK76530TExamZC/S2Glm080Nx8PE5sTd7nlduDQsAfEl4uXX+qZjLiwvDauvzXavufy3+rJ9zQ==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.1.0.tgz", + "integrity": "sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==", "license": "MIT", "dependencies": { "eventemitter3": "^5.0.1", @@ -12925,6 +13235,7 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -13048,14 +13359,15 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/path-expression-matcher": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz", - "integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.0.tgz", + "integrity": "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==", "funding": [ { "type": "github", @@ -13091,7 +13403,8 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/path-scurry": { "version": "2.0.2", @@ -13110,16 +13423,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -13161,9 +13464,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "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": { @@ -13173,24 +13476,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pixelmatch": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-7.1.0.tgz", - "integrity": "sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==", - "dev": true, - "license": "ISC", - "dependencies": { - "pngjs": "^7.0.0" - }, - "bin": { - "pixelmatch": "bin/pixelmatch" - } - }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, + "license": "MIT", "dependencies": { "find-up": "^4.0.0" }, @@ -13203,6 +13494,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -13216,6 +13508,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -13228,6 +13521,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, + "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -13243,6 +13537,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -13256,6 +13551,7 @@ "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright-core": "1.58.2" }, @@ -13282,21 +13578,6 @@ "node": ">=18" } }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/pngjs": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", @@ -13318,9 +13599,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "dev": true, "funding": [ { @@ -13362,6 +13643,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "peer": true, "dependencies": { "lilconfig": "^3.1.1" @@ -13445,9 +13727,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, "license": "MIT", "dependencies": { @@ -13463,6 +13745,7 @@ "resolved": "https://registry.npmjs.org/pouchdb-abstract-mapreduce/-/pouchdb-abstract-mapreduce-9.0.0.tgz", "integrity": "sha512-SnTtqwAEiAa3uxKbc1J7LfiBViwEkKe2xkK92zxyTXPqWBvMnh4UU3GXxx7GrXTM4L9llsQ3lSjpbH4CNqG1Mw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "pouchdb-binary-utils": "9.0.0", "pouchdb-collate": "9.0.0", @@ -13478,6 +13761,7 @@ "resolved": "https://registry.npmjs.org/pouchdb-adapter-http/-/pouchdb-adapter-http-9.0.0.tgz", "integrity": "sha512-2eL008XeRZkdyp3hMHHOhdIPqK9H6Mn4SLlQvit4zCbqnOFfAswzPjUmHULGMbDUCrQBTu6y82FnV6NHXv9kgw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "pouchdb-binary-utils": "9.0.0", "pouchdb-errors": "9.0.0", @@ -13490,6 +13774,7 @@ "resolved": "https://registry.npmjs.org/pouchdb-adapter-idb/-/pouchdb-adapter-idb-9.0.0.tgz", "integrity": "sha512-2oLlgwMyOQwdKuzrEmOv8T7jFVgX7JgT4Cr81zX3eiiRClp7xXGgjv41ZRdVCAbM530sIN8BudafaQRVFKRVmA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "pouchdb-adapter-utils": "9.0.0", "pouchdb-binary-utils": "9.0.0", @@ -13504,6 +13789,7 @@ "resolved": "https://registry.npmjs.org/pouchdb-adapter-indexeddb/-/pouchdb-adapter-indexeddb-9.0.0.tgz", "integrity": "sha512-/mcCbnVR0VKwtVZWKf8lVSdADLD0yApjFudu4d+0jeLWAeBSGZBRKYlogz2PGs4uTA7GVc2TXjVCNGUdkCM9ZQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "pouchdb-adapter-utils": "9.0.0", "pouchdb-binary-utils": "9.0.0", @@ -13562,6 +13848,7 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-adapter-utils/-/pouchdb-adapter-utils-9.0.0.tgz", "integrity": "sha512-hmbm4ey0HL0vtoY1tRTPIt2FfYjvMh3DWoGGSxXDTS73qTFQ+Fhhi5I0AnN9PcD2omfKQAVXiYks4kkMvlAHqA==", + "license": "Apache-2.0", "dependencies": { "pouchdb-binary-utils": "9.0.0", "pouchdb-errors": "9.0.0", @@ -13573,12 +13860,14 @@ "node_modules/pouchdb-binary-utils": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-binary-utils/-/pouchdb-binary-utils-9.0.0.tgz", - "integrity": "sha512-2OMtgDZi82vqs+zNDE0YiYjOaWkYCUcZJZKK3WkRr+XYRu+2B7umJrnygJFhUwoGedBbHSrlQBLhdNV3F1AX1A==" + "integrity": "sha512-2OMtgDZi82vqs+zNDE0YiYjOaWkYCUcZJZKK3WkRr+XYRu+2B7umJrnygJFhUwoGedBbHSrlQBLhdNV3F1AX1A==", + "license": "Apache-2.0" }, "node_modules/pouchdb-changes-filter": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-changes-filter/-/pouchdb-changes-filter-9.0.0.tgz", "integrity": "sha512-ig0fo0WLgIjAniFJ19Uw1Y+oxiypqC+Skhd8BCETRVXOhLBzueRwEQR4thffyo0UayYVqldJfSR5wHSDvEVk/A==", + "license": "Apache-2.0", "dependencies": { "pouchdb-errors": "9.0.0", "pouchdb-selector-core": "9.0.0", @@ -13590,6 +13879,7 @@ "resolved": "https://registry.npmjs.org/pouchdb-checkpointer/-/pouchdb-checkpointer-9.0.0.tgz", "integrity": "sha512-yu1OlWw78oTHKOkg1GoxxF2qB7YUsjK3rUDJOChMs/sVlZwOTZ4mGdWFPBr3udxSGvR77E+g89kpdmAWhPpHvA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "pouchdb-collate": "9.0.0", "pouchdb-utils": "9.0.0" @@ -13598,12 +13888,14 @@ "node_modules/pouchdb-collate": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-collate/-/pouchdb-collate-9.0.0.tgz", - "integrity": "sha512-TrnEDNZEmIIl+W3xKUO8h+geqVLQ90oZe5ujPkl8myUzpREULWXWQBnV5EzPXVEKDBpJlb8T3I6oy/zdWGQpdA==" + "integrity": "sha512-TrnEDNZEmIIl+W3xKUO8h+geqVLQ90oZe5ujPkl8myUzpREULWXWQBnV5EzPXVEKDBpJlb8T3I6oy/zdWGQpdA==", + "license": "Apache-2.0" }, "node_modules/pouchdb-core": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-core/-/pouchdb-core-9.0.0.tgz", "integrity": "sha512-98SJgs8bqXhr4gMGuOTR8yVeLlMYy797zlOtdlvlXIxIicvocyA8ColhVVhdBXPNOGxT2HwReIMywdIVAgibpg==", + "license": "Apache-2.0", "dependencies": { "pouchdb-changes-filter": "9.0.0", "pouchdb-errors": "9.0.0", @@ -13616,12 +13908,14 @@ "node_modules/pouchdb-errors": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-errors/-/pouchdb-errors-9.0.0.tgz", - "integrity": "sha512-961PSMLhW0UqqdJ566g+CdLZ5pkBJRd6l4WWpCDdD0USvE4xYfYGzv43w7nZZBw1k3Xdy092yqPge7yX/tfnyw==" + "integrity": "sha512-961PSMLhW0UqqdJ566g+CdLZ5pkBJRd6l4WWpCDdD0USvE4xYfYGzv43w7nZZBw1k3Xdy092yqPge7yX/tfnyw==", + "license": "Apache-2.0" }, "node_modules/pouchdb-fetch": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-fetch/-/pouchdb-fetch-9.0.0.tgz", "integrity": "sha512-TbE3cUcAJQrwb9kr44tDP0X+NAbcqgjsTvcL30L4xzBNJeCPTIRjukYX80s154SHJUXBxcWRiPsMmNqpXsjfCA==", + "license": "Apache-2.0", "dependencies": { "fetch-cookie": "2.2.0", "node-fetch": "2.6.9" @@ -13632,6 +13926,7 @@ "resolved": "https://registry.npmjs.org/pouchdb-find/-/pouchdb-find-9.0.0.tgz", "integrity": "sha512-vvVhq4eEOmSkwSRwf2NBYtdhURB7ryJ7sUI4WDN00GuLUj2g8jAXBJuZIryVgdYt/5S5cfn70iRL6Eow+LFhpA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "pouchdb-abstract-mapreduce": "9.0.0", "pouchdb-collate": "9.0.0", @@ -13647,6 +13942,7 @@ "resolved": "https://registry.npmjs.org/pouchdb-generate-replication-id/-/pouchdb-generate-replication-id-9.0.0.tgz", "integrity": "sha512-wetxjU0W/qNYtfHIoKwBO73ddUr0/eqzYOkoKHSFXCgOzYmTglDeqXiVY9LPysRXTgaHUJPKC5LoknZZw7e+Dw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "pouchdb-collate": "9.0.0", "pouchdb-md5": "9.0.0" @@ -13656,6 +13952,7 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-json/-/pouchdb-json-9.0.0.tgz", "integrity": "sha512-aI41mYVyI195GXuT1Ys7mLIB/Mvrz11ihoTP6km6hYqVgSuaUxuZcFUozlyTJiZXr7H5kdhNgclhlVnjir4JAA==", + "license": "Apache-2.0", "dependencies": { "vuvuzela": "1.0.3" } @@ -13665,6 +13962,7 @@ "resolved": "https://registry.npmjs.org/pouchdb-mapreduce/-/pouchdb-mapreduce-9.0.0.tgz", "integrity": "sha512-ZD8PleQ9atzQAzT2LZWsvooUVEfsen5QGv/SDfci20IleCaFW2A2q7OERrqY0YWKDCCNRsWhPWPmsFvZC9K8DQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "pouchdb-abstract-mapreduce": "9.0.0", "pouchdb-mapreduce-utils": "9.0.0", @@ -13676,6 +13974,7 @@ "resolved": "https://registry.npmjs.org/pouchdb-mapreduce-utils/-/pouchdb-mapreduce-utils-9.0.0.tgz", "integrity": "sha512-Bjh8W6QXqp1j7MKmHhYYp5cYlcQsm5drD8Jd/F+ZlfNt18uiD2SQXWzGM5797+tiW/LszFGb8ttw0uHWjxufCQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "pouchdb-utils": "9.0.0" } @@ -13694,6 +13993,7 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-merge/-/pouchdb-merge-9.0.0.tgz", "integrity": "sha512-Xh+TgOZCkGoZpI589btKf/cTiuQ5CsnPl9YpdW4h0cAPusniN6XNsR62F+/HbL9wirI6XTEPHUrk7MsQbk3S3A==", + "license": "Apache-2.0", "dependencies": { "pouchdb-utils": "9.0.0" } @@ -13703,6 +14003,7 @@ "resolved": "https://registry.npmjs.org/pouchdb-replication/-/pouchdb-replication-9.0.0.tgz", "integrity": "sha512-EZ68KJ3ZUWuPe35NxP6WnRw8J6Zudf0j/tZ/6mOSrCcp3EbtBNt8Ke2FaAThUgiFahVnHD5Y8nd53EGs2DLygg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "pouchdb-checkpointer": "9.0.0", "pouchdb-errors": "9.0.0", @@ -13714,6 +14015,7 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-selector-core/-/pouchdb-selector-core-9.0.0.tgz", "integrity": "sha512-ZYHYsdoedwm8j5tYofz+3+uUSK8i+7tRCBb01T0OuqDQb17+w5mzjHF8Ppi160xdPUPaWCo1Un+nLWGJzkmA3g==", + "license": "Apache-2.0", "dependencies": { "pouchdb-collate": "9.0.0", "pouchdb-utils": "9.0.0" @@ -13723,6 +14025,7 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/pouchdb-utils/-/pouchdb-utils-9.0.0.tgz", "integrity": "sha512-xWZE5c+nAslgmLC8JBZbky8AYgdz7pKtv7KTSi6CD2tuQD0WyNKib0YnhZndeE84dksTeZlqlg56RQHsHoB2LQ==", + "license": "Apache-2.0", "dependencies": { "pouchdb-errors": "9.0.0", "pouchdb-md5": "9.0.0", @@ -13733,7 +14036,8 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/pouchdb-wrappers/-/pouchdb-wrappers-5.0.0.tgz", "integrity": "sha512-fXqsVn+rmlPtxaAIGaQP5TkiaT39OMwvMk+ScLLtHrmfXD2KBO6fe/qBl38N/rpTn0h/A058dPN4fLAHt550zA==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/prebuild-install": { "version": "7.1.3", @@ -13762,41 +14066,6 @@ "node": ">=10" } }, - "node_modules/prebuild-install/node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/prebuild-install/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/prebuild-install/node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -13806,53 +14075,12 @@ "node": ">=8" } }, - "node_modules/prebuild-install/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prebuild-install/node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/prebuild-install/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } @@ -13983,14 +14211,21 @@ "license": "MIT" }, "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } }, "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", @@ -13998,9 +14233,10 @@ } }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", "engines": { "node": ">=6" } @@ -14030,7 +14266,8 @@ "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" }, "node_modules/queue-microtask": { "version": "1.2.3", @@ -14104,19 +14341,17 @@ } }, "node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 6" } }, "node_modules/readdir-glob": { @@ -14129,6 +14364,13 @@ "minimatch": "^5.1.0" } }, + "node_modules/readdir-glob/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/readdir-glob/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -14153,12 +14395,13 @@ } }, "node_modules/readdirp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", - "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "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.16.0" + "node": ">= 14.18.0" }, "funding": { "type": "individual", @@ -14221,21 +14464,26 @@ "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, + "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.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" } @@ -14321,9 +14569,9 @@ "license": "MIT" }, "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", + "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==", "dev": true, "license": "MIT", "dependencies": { @@ -14337,31 +14585,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", + "@rollup/rollup-android-arm-eabi": "4.60.0", + "@rollup/rollup-android-arm64": "4.60.0", + "@rollup/rollup-darwin-arm64": "4.60.0", + "@rollup/rollup-darwin-x64": "4.60.0", + "@rollup/rollup-freebsd-arm64": "4.60.0", + "@rollup/rollup-freebsd-x64": "4.60.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", + "@rollup/rollup-linux-arm-musleabihf": "4.60.0", + "@rollup/rollup-linux-arm64-gnu": "4.60.0", + "@rollup/rollup-linux-arm64-musl": "4.60.0", + "@rollup/rollup-linux-loong64-gnu": "4.60.0", + "@rollup/rollup-linux-loong64-musl": "4.60.0", + "@rollup/rollup-linux-ppc64-gnu": "4.60.0", + "@rollup/rollup-linux-ppc64-musl": "4.60.0", + "@rollup/rollup-linux-riscv64-gnu": "4.60.0", + "@rollup/rollup-linux-riscv64-musl": "4.60.0", + "@rollup/rollup-linux-s390x-gnu": "4.60.0", + "@rollup/rollup-linux-x64-gnu": "4.60.0", + "@rollup/rollup-linux-x64-musl": "4.60.0", + "@rollup/rollup-openbsd-x64": "4.60.0", + "@rollup/rollup-openharmony-arm64": "4.60.0", + "@rollup/rollup-win32-arm64-msvc": "4.60.0", + "@rollup/rollup-win32-ia32-msvc": "4.60.0", + "@rollup/rollup-win32-x64-gnu": "4.60.0", + "@rollup/rollup-win32-x64-msvc": "4.60.0", "fsevents": "~2.3.2" } }, @@ -14411,6 +14659,7 @@ "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", "dev": true, + "license": "MIT", "dependencies": { "mri": "^1.1.0" }, @@ -14449,23 +14698,9 @@ } }, "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, "node_modules/safe-push-apply": { @@ -14575,15 +14810,17 @@ } }, "node_modules/set-cookie-parser": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", - "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==" + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, + "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -14867,6 +15104,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -14886,6 +15124,7 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -14911,7 +15150,8 @@ "node_modules/spark-md5": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.2.tgz", - "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==" + "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==", + "license": "(WTFPL OR MIT)" }, "node_modules/split2": { "version": "4.2.0", @@ -14937,9 +15177,9 @@ "license": "MIT" }, "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", "dev": true, "license": "MIT" }, @@ -14958,9 +15198,9 @@ } }, "node_modules/streamx": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", - "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "version": "2.25.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz", + "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==", "dev": true, "license": "MIT", "dependencies": { @@ -14978,6 +15218,26 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -15008,6 +15268,50 @@ "node": ">=8" } }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", @@ -15068,15 +15372,19 @@ } }, "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^6.2.2" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/strip-ansi-cjs": { @@ -15093,11 +15401,22 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -15116,9 +15435,9 @@ } }, "node_modules/strnum": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz", - "integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.2.tgz", + "integrity": "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==", "funding": [ { "type": "github", @@ -15174,6 +15493,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -15186,6 +15506,7 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -15221,9 +15542,9 @@ } }, "node_modules/svelte-check": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.3.tgz", - "integrity": "sha512-4HtdEv2hOoLCEsSXI+RDELk9okP/4sImWa7X02OjMFFOWeSdFF3NFy3vqpw0z+eH9C88J9vxZfUXz/Uv2A1ANw==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.5.tgz", + "integrity": "sha512-1bSwIRCvvmSHrlK52fOlZmVtUZgil43jNL/2H18pRpa+eQjzGt6e3zayxhp1S7GajPFKNM/2PMCG+DZFHlG9fw==", "dev": true, "license": "MIT", "dependencies": { @@ -15263,9 +15584,9 @@ } }, "node_modules/svelte-eslint-parser": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.4.0.tgz", - "integrity": "sha512-fjPzOfipR5S7gQ/JvI9r2H8y9gMGXO3JtmrylHLLyahEMquXI0lrebcjT+9/hNgDej0H7abTyox5HpHmW1PSWA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.6.0.tgz", + "integrity": "sha512-qoB1ehychT6OxEtQAqc/guSqLS20SlA53Uijl7x375s8nlUT0lb9ol/gzraEEatQwsyPTJo87s2CmKL9Xab+Uw==", "dev": true, "license": "MIT", "dependencies": { @@ -15274,11 +15595,12 @@ "espree": "^10.0.0", "postcss": "^8.4.49", "postcss-scss": "^4.0.9", - "postcss-selector-parser": "^7.0.0" + "postcss-selector-parser": "^7.0.0", + "semver": "^7.7.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0", - "pnpm": "10.18.3" + "pnpm": "10.30.3" }, "funding": { "url": "https://github.com/sponsors/ota-meshi" @@ -15362,31 +15684,31 @@ } }, "node_modules/tar-fs": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz", - "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", - "dev": true, + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "license": "MIT", "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^3.1.5" - }, - "optionalDependencies": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0" + "tar-stream": "^2.1.4" } }, "node_modules/tar-stream": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", - "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==", - "dev": true, + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "license": "MIT", "dependencies": { - "b4a": "^1.6.4", - "bare-fs": "^4.5.5", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" } }, "node_modules/teex": { @@ -15400,14 +15722,14 @@ } }, "node_modules/terser": { - "version": "5.39.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", - "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", + "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -15441,9 +15763,9 @@ } }, "node_modules/text-decoder": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", - "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -15460,20 +15782,6 @@ "readable-stream": "2 || 3" } }, - "node_modules/through2/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -15482,9 +15790,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", "dev": true, "license": "MIT", "engines": { @@ -15541,9 +15849,9 @@ } }, "node_modules/tinyrainbow": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", - "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, "license": "MIT", "engines": { @@ -15577,6 +15885,7 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "license": "BSD-3-Clause", "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -15587,16 +15896,27 @@ "node": ">=6" } }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" }, "node_modules/transform-pouch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/transform-pouch/-/transform-pouch-2.0.0.tgz", "integrity": "sha512-nDZovo0U5o0UdMNL93fMQgGjrwH9h4F/a7qqRTnF6cVA+FfgyXiJPTrSuD+LmWSO7r2deZt0P0oeCD8hkgxl5g==", "dev": true, + "license": "Apache-2.0", "dependencies": { "pouchdb-wrappers": "^5.0.0" } @@ -15614,19 +15934,10 @@ "mqtt": "^5.14.1" } }, - "node_modules/trystero/node_modules/@noble/secp256k1": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-3.0.0.tgz", - "integrity": "sha512-NJBaR352KyIvj3t6sgT/+7xrNyF9Xk9QlLSIqUGVUYlsnDTAUqY8LOmwpcgEx4AMJXRITQ5XEVHD+mMaPfr3mg==", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -15641,6 +15952,7 @@ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, + "license": "MIT", "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -16142,6 +16454,21 @@ "@esbuild/win32-x64": "0.27.4" } }, + "node_modules/tsx/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -16159,6 +16486,7 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -16351,16 +16679,17 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "license": "MIT" }, "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 4.0.0" } @@ -16410,6 +16739,7 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -16432,6 +16762,12 @@ "node": ">= 0.8.0" } }, + "node_modules/utf8-codec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utf8-codec/-/utf8-codec-1.0.0.tgz", + "integrity": "sha512-S/QSLezp3qvG4ld5PUfXiH7mCFxLKjSVZRFkB3DOjgwHuJPFDkInAXc/anf7BAbHt/D38ozDzL+QMZ6/7gsI6w==", + "license": "MIT" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -16442,6 +16778,7 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } @@ -17068,6 +17405,21 @@ } } }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/vite/node_modules/picomatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", @@ -17083,9 +17435,9 @@ } }, "node_modules/vitefu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", - "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.2.tgz", + "integrity": "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==", "dev": true, "license": "MIT", "workspaces": [ @@ -17094,7 +17446,7 @@ "tests/projects/workspace/packages/*" ], "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" }, "peerDependenciesMeta": { "vite": { @@ -17103,32 +17455,32 @@ } }, "node_modules/vitest": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.16.tgz", - "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.1.tgz", + "integrity": "sha512-yF+o4POL41rpAzj5KVILUxm1GCjKnELvaqmU9TLLUbMfDzuN0UpUR9uaDs+mCtjPe+uYPksXDRLQGGPvj1cTmA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@vitest/expect": "4.0.16", - "@vitest/mocker": "4.0.16", - "@vitest/pretty-format": "4.0.16", - "@vitest/runner": "4.0.16", - "@vitest/snapshot": "4.0.16", - "@vitest/spy": "4.0.16", - "@vitest/utils": "4.0.16", - "es-module-lexer": "^1.7.0", - "expect-type": "^1.2.2", + "@vitest/expect": "4.1.1", + "@vitest/mocker": "4.1.1", + "@vitest/pretty-format": "4.1.1", + "@vitest/runner": "4.1.1", + "@vitest/snapshot": "4.1.1", + "@vitest/spy": "4.1.1", + "@vitest/utils": "4.1.1", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", - "std-env": "^3.10.0", + "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", - "vite": "^6.0.0 || ^7.0.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "bin": { @@ -17144,12 +17496,13 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.16", - "@vitest/browser-preview": "4.0.16", - "@vitest/browser-webdriverio": "4.0.16", - "@vitest/ui": "4.0.16", + "@vitest/browser-playwright": "4.1.1", + "@vitest/browser-preview": "4.1.1", + "@vitest/browser-webdriverio": "4.1.1", + "@vitest/ui": "4.1.1", "happy-dom": "*", - "jsdom": "*" + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { @@ -17178,9 +17531,19 @@ }, "jsdom": { "optional": true + }, + "vite": { + "optional": false } } }, + "node_modules/vitest/node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, "node_modules/vitest/node_modules/picomatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", @@ -17197,7 +17560,8 @@ "node_modules/vuvuzela": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/vuvuzela/-/vuvuzela-1.0.3.tgz", - "integrity": "sha512-Tm7jR1xTzBbPW+6y1tknKiEhz04Wf/1iZkcTJjSFcpNko43+dFW6+OOeQe9taJIug3NdfUAjFKgUSyQrIKaDvQ==" + "integrity": "sha512-Tm7jR1xTzBbPW+6y1tknKiEhz04Wf/1iZkcTJjSFcpNko43+dFW6+OOeQe9taJIug3NdfUAjFKgUSyQrIKaDvQ==", + "license": "Apache-2.0" }, "node_modules/w3c-keyname": { "version": "2.2.8", @@ -17272,19 +17636,19 @@ "license": "Apache-2.0" }, "node_modules/webdriver": { - "version": "9.25.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.25.0.tgz", - "integrity": "sha512-XnABKdrp83zX3xVltmX0OcFzn8zOzWGtZQxIUKY0+INB0g9Nnnfu7G75W0G+0y4nyb3zH8mavGzDBiXctdEd3Q==", + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.27.0.tgz", + "integrity": "sha512-w07ThZND48SIr0b4S7eFougYUyclmoUwdmju8yXvEJiXYjDjeYUpl8wZrYPEYRBylxpSx+sBHfEUBrPQkcTTRQ==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "^20.1.0", "@types/ws": "^8.5.3", - "@wdio/config": "9.25.0", + "@wdio/config": "9.27.0", "@wdio/logger": "9.18.0", - "@wdio/protocols": "9.25.0", - "@wdio/types": "9.25.0", - "@wdio/utils": "9.25.0", + "@wdio/protocols": "9.27.0", + "@wdio/types": "9.27.0", + "@wdio/utils": "9.27.0", "deepmerge-ts": "^7.0.3", "https-proxy-agent": "^7.0.6", "undici": "^6.21.3", @@ -17314,21 +17678,28 @@ "node": ">=18.17" } }, + "node_modules/webdriver/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/webdriverio": { - "version": "9.25.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.25.0.tgz", - "integrity": "sha512-ualC/LtWGjL5rwGAbUUzURKqKoHJG2/qecEppcS9k4n1IX3MlbzGXuL/qpXiRbs/h4981HpRbZAKBxRYqwUe3g==", + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.27.0.tgz", + "integrity": "sha512-Y4FbMf4bKBXpPB0lYpglzQ2GfDDe6uojmMZl85uPyrDx18NW7mqN84ZawGoIg/FRvcLaVhcOzc98WOPo725Rag==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "^20.11.30", "@types/sinonjs__fake-timers": "^8.1.5", - "@wdio/config": "9.25.0", + "@wdio/config": "9.27.0", "@wdio/logger": "9.18.0", - "@wdio/protocols": "9.25.0", + "@wdio/protocols": "9.27.0", "@wdio/repl": "9.16.2", - "@wdio/types": "9.25.0", - "@wdio/utils": "9.25.0", + "@wdio/types": "9.27.0", + "@wdio/utils": "9.27.0", "archiver": "^7.0.1", "aria-query": "^5.3.0", "cheerio": "^1.0.0-rc.12", @@ -17345,7 +17716,7 @@ "rgb2hex": "0.2.5", "serialize-error": "^12.0.0", "urlpattern-polyfill": "^10.0.0", - "webdriver": "9.25.0" + "webdriver": "9.27.0" }, "engines": { "node": ">=18.20.0" @@ -17369,10 +17740,18 @@ "undici-types": "~6.21.0" } }, + "node_modules/webdriverio/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" }, "node_modules/websocket-driver": { "version": "0.7.4", @@ -17425,6 +17804,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -17527,9 +17907,9 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", "dev": true, "license": "MIT", "dependencies": { @@ -17565,51 +17945,61 @@ "node": ">=8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/worker-factory": { - "version": "7.0.46", - "resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.46.tgz", - "integrity": "sha512-Sr1hq2FMgNa04UVhYQacsw+i58BtMimzDb4+CqYphZ97OfefRpURu0UZ+JxMr/H36VVJBfuVkxTK7MytsanC3w==", + "version": "7.0.49", + "resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.49.tgz", + "integrity": "sha512-lW7tpgy6aUv2dFsQhv1yv+XFzdkCf/leoKRTGMPVK5/die6RrUjqgJHJf556qO+ZfytNG6wPXc17E8zzsOLUDw==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", - "fast-unique-numbers": "^9.0.24", + "@babel/runtime": "^7.29.2", + "fast-unique-numbers": "^9.0.27", "tslib": "^2.8.1" } }, "node_modules/worker-timers": { - "version": "8.0.25", - "resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-8.0.25.tgz", - "integrity": "sha512-X7Z5dmM6PlrEnaadtFQOyXHGD/IysPA3HZzaC2koqsU1VI+RvyGmjiiLiUBQixK8PH5R7ilkOzZupWskNRaXmA==", + "version": "8.0.31", + "resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-8.0.31.tgz", + "integrity": "sha512-ngkq5S6JuZyztom8tDgBzorLo9byhBMko/sXfgiUD945AuzKGg1GCgDMCC3NaYkicLpGKXutONM36wEX8UbBCA==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", + "@babel/runtime": "^7.29.2", "tslib": "^2.8.1", - "worker-timers-broker": "^8.0.11", - "worker-timers-worker": "^9.0.11" + "worker-timers-broker": "^8.0.16", + "worker-timers-worker": "^9.0.14" } }, "node_modules/worker-timers-broker": { - "version": "8.0.11", - "resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-8.0.11.tgz", - "integrity": "sha512-uwhxKru8BI9m2tsogxr2fB6POZ8LB2xH+Pu3R0mvQnAZLPgLD6K3IX4LNKPTEgTJ/j5VsuQPB+gLI1NBNKkPlg==", + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-8.0.16.tgz", + "integrity": "sha512-JyP3AvUGyPGbBGW7XiUewm2+0pN/aYo1QpVf5kdXAfkDZcN3p7NbWrG6XnyDEpDIvfHk/+LCnOW/NsuiU9riYA==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", - "broker-factory": "^3.1.10", - "fast-unique-numbers": "^9.0.24", + "@babel/runtime": "^7.29.2", + "broker-factory": "^3.1.14", + "fast-unique-numbers": "^9.0.27", "tslib": "^2.8.1", - "worker-timers-worker": "^9.0.11" + "worker-timers-worker": "^9.0.14" } }, "node_modules/worker-timers-worker": { - "version": "9.0.11", - "resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-9.0.11.tgz", - "integrity": "sha512-pArb5xtgHWImYpXhjg1OFv7JFG0ubmccb73TFoXHXjG830fFj+16N57q9YeBnZX52dn+itRrMoJZ9HaZBVzDaA==", + "version": "9.0.14", + "resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-9.0.14.tgz", + "integrity": "sha512-/qF06C60sXmSLfUl7WglvrDIbspmPOM8UrG63Dnn4bi2x4/DfqHS/+dxF5B+MdHnYO5tVuZYLHdAodrKdabTIg==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.28.4", + "@babel/runtime": "^7.29.2", "tslib": "^2.8.1", - "worker-factory": "^7.0.46" + "worker-factory": "^7.0.49" } }, "node_modules/wrap-ansi": { @@ -17648,6 +18038,50 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -17669,9 +18103,9 @@ "license": "BSD" }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -17700,9 +18134,10 @@ }, "node_modules/xxhash-wasm-102": { "name": "xxhash-wasm", - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.0.2.tgz", - "integrity": "sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", + "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", + "license": "MIT" }, "node_modules/y18n": { "version": "5.0.8", @@ -17790,6 +18225,7 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -17798,9 +18234,9 @@ } }, "node_modules/zimmerframe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", - "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", "dev": true, "license": "MIT" }, @@ -17819,11852 +18255,46 @@ "node": ">= 14" } }, - "src/lib/src/patches/pouchdb-utils": { - "version": "8.0.0", - "extraneous": true, - "license": "Apache-2.0", - "dependencies": { - "clone-buffer": "1.0.0", - "immediate": "3.3.0", - "pouchdb-collections": "8.0.0", - "pouchdb-errors": "8.0.0", - "pouchdb-md5": "8.0.0", - "uuid": "8.3.2" - } - } - }, - "dependencies": { - "@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true - }, - "@aws-crypto/crc32": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", - "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", - "requires": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - } - }, - "@aws-crypto/crc32c": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", - "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", - "requires": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - } - }, - "@aws-crypto/sha1-browser": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", - "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", - "requires": { - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "requires": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "requires": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - } - } - } - }, - "@aws-crypto/sha256-browser": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "requires": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "requires": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "requires": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - } - } - } - }, - "@aws-crypto/sha256-js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", - "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "requires": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - } - }, - "@aws-crypto/supports-web-crypto": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", - "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@aws-crypto/util": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "requires": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "requires": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "requires": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/client-s3": { - "version": "3.983.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.983.0.tgz", - "integrity": "sha512-V40PT2irPh3lj+Z95tZI6batVrjaTrWEOXRNVBuoZSgpM3Ak1jiE9ZXwVLkMcbb9/GH4xVpB3EsGM7gbxmgFLQ==", - "requires": { - "@aws-crypto/sha1-browser": "5.2.0", - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.6", - "@aws-sdk/credential-provider-node": "^3.972.5", - "@aws-sdk/middleware-bucket-endpoint": "^3.972.3", - "@aws-sdk/middleware-expect-continue": "^3.972.3", - "@aws-sdk/middleware-flexible-checksums": "^3.972.4", - "@aws-sdk/middleware-host-header": "^3.972.3", - "@aws-sdk/middleware-location-constraint": "^3.972.3", - "@aws-sdk/middleware-logger": "^3.972.3", - "@aws-sdk/middleware-recursion-detection": "^3.972.3", - "@aws-sdk/middleware-sdk-s3": "^3.972.6", - "@aws-sdk/middleware-ssec": "^3.972.3", - "@aws-sdk/middleware-user-agent": "^3.972.6", - "@aws-sdk/region-config-resolver": "^3.972.3", - "@aws-sdk/signature-v4-multi-region": "3.983.0", - "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.983.0", - "@aws-sdk/util-user-agent-browser": "^3.972.3", - "@aws-sdk/util-user-agent-node": "^3.972.4", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.22.0", - "@smithy/eventstream-serde-browser": "^4.2.8", - "@smithy/eventstream-serde-config-resolver": "^4.3.8", - "@smithy/eventstream-serde-node": "^4.2.8", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-blob-browser": "^4.2.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/hash-stream-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/md5-js": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.12", - "@smithy/middleware-retry": "^4.4.29", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.11.1", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.28", - "@smithy/util-defaults-mode-node": "^4.2.31", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-stream": "^4.5.10", - "@smithy/util-utf8": "^4.2.0", - "@smithy/util-waiter": "^4.2.8", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/core": { - "version": "3.973.15", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.15.tgz", - "integrity": "sha512-AlC0oQ1/mdJ8vCIqu524j5RB7M8i8E24bbkZmya1CuiQxkY7SdIZAyw7NDNMGaNINQFq/8oGRMX0HeOfCVsl/A==", - "requires": { - "@aws-sdk/types": "^3.973.4", - "@aws-sdk/xml-builder": "^3.972.8", - "@smithy/core": "^3.23.6", - "@smithy/node-config-provider": "^4.3.10", - "@smithy/property-provider": "^4.2.10", - "@smithy/protocol-http": "^5.3.10", - "@smithy/signature-v4": "^5.3.10", - "@smithy/smithy-client": "^4.12.0", - "@smithy/types": "^4.13.0", - "@smithy/util-base64": "^4.3.1", - "@smithy/util-middleware": "^4.2.10", - "@smithy/util-utf8": "^4.2.1", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/crc64-nvme": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.3.tgz", - "integrity": "sha512-UExeK+EFiq5LAcbHm96CQLSia+5pvpUVSAsVApscBzayb7/6dJBJKwV4/onsk4VbWSmqxDMcfuTD+pC4RxgZHg==", - "requires": { - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-env": { - "version": "3.972.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.13.tgz", - "integrity": "sha512-6ljXKIQ22WFKyIs1jbORIkGanySBHaPPTOI4OxACP5WXgbcR0nDYfqNJfXEGwCK7IzHdNbCSFsNKKs0qCexR8Q==", - "requires": { - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/types": "^3.973.4", - "@smithy/property-provider": "^4.2.10", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-http": { - "version": "3.972.15", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.15.tgz", - "integrity": "sha512-dJuSTreu/T8f24SHDNTjd7eQ4rabr0TzPh2UTCwYexQtzG3nTDKm1e5eIdhiroTMDkPEJeY+WPkA6F9wod/20A==", - "requires": { - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/types": "^3.973.4", - "@smithy/fetch-http-handler": "^5.3.11", - "@smithy/node-http-handler": "^4.4.12", - "@smithy/property-provider": "^4.2.10", - "@smithy/protocol-http": "^5.3.10", - "@smithy/smithy-client": "^4.12.0", - "@smithy/types": "^4.13.0", - "@smithy/util-stream": "^4.5.15", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-ini": { - "version": "3.972.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.13.tgz", - "integrity": "sha512-JKSoGb7XeabZLBJptpqoZIFbROUIS65NuQnEHGOpuT9GuuZwag2qciKANiDLFiYk4u8nSrJC9JIOnWKVvPVjeA==", - "requires": { - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/credential-provider-env": "^3.972.13", - "@aws-sdk/credential-provider-http": "^3.972.15", - "@aws-sdk/credential-provider-login": "^3.972.13", - "@aws-sdk/credential-provider-process": "^3.972.13", - "@aws-sdk/credential-provider-sso": "^3.972.13", - "@aws-sdk/credential-provider-web-identity": "^3.972.13", - "@aws-sdk/nested-clients": "^3.996.3", - "@aws-sdk/types": "^3.973.4", - "@smithy/credential-provider-imds": "^4.2.10", - "@smithy/property-provider": "^4.2.10", - "@smithy/shared-ini-file-loader": "^4.4.5", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-login": { - "version": "3.972.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.13.tgz", - "integrity": "sha512-RtYcrxdnJHKY8MFQGLltCURcjuMjnaQpAxPE6+/QEdDHHItMKZgabRe/KScX737F9vJMQsmJy9EmMOkCnoC1JQ==", - "requires": { - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/nested-clients": "^3.996.3", - "@aws-sdk/types": "^3.973.4", - "@smithy/property-provider": "^4.2.10", - "@smithy/protocol-http": "^5.3.10", - "@smithy/shared-ini-file-loader": "^4.4.5", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-node": { - "version": "3.972.14", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.14.tgz", - "integrity": "sha512-WqoC2aliIjQM/L3oFf6j+op/enT2i9Cc4UTxxMEKrJNECkq4/PlKE5BOjSYFcq6G9mz65EFbXJh7zOU4CvjSKQ==", - "requires": { - "@aws-sdk/credential-provider-env": "^3.972.13", - "@aws-sdk/credential-provider-http": "^3.972.15", - "@aws-sdk/credential-provider-ini": "^3.972.13", - "@aws-sdk/credential-provider-process": "^3.972.13", - "@aws-sdk/credential-provider-sso": "^3.972.13", - "@aws-sdk/credential-provider-web-identity": "^3.972.13", - "@aws-sdk/types": "^3.973.4", - "@smithy/credential-provider-imds": "^4.2.10", - "@smithy/property-provider": "^4.2.10", - "@smithy/shared-ini-file-loader": "^4.4.5", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-process": { - "version": "3.972.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.13.tgz", - "integrity": "sha512-rsRG0LQA4VR+jnDyuqtXi2CePYSmfm5GNL9KxiW8DSe25YwJSr06W8TdUfONAC+rjsTI+aIH2rBGG5FjMeANrw==", - "requires": { - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/types": "^3.973.4", - "@smithy/property-provider": "^4.2.10", - "@smithy/shared-ini-file-loader": "^4.4.5", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-sso": { - "version": "3.972.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.13.tgz", - "integrity": "sha512-fr0UU1wx8kNHDhTQBXioc/YviSW8iXuAxHvnH7eQUtn8F8o/FU3uu6EUMvAQgyvn7Ne5QFnC0Cj0BFlwCk+RFw==", - "requires": { - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/nested-clients": "^3.996.3", - "@aws-sdk/token-providers": "3.999.0", - "@aws-sdk/types": "^3.973.4", - "@smithy/property-provider": "^4.2.10", - "@smithy/shared-ini-file-loader": "^4.4.5", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-web-identity": { - "version": "3.972.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.13.tgz", - "integrity": "sha512-a6iFMh1pgUH0TdcouBppLJUfPM7Yd3R9S1xFodPtCRoLqCz2RQFA3qjA8x4112PVYXEd4/pHX2eihapq39w0rA==", - "requires": { - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/nested-clients": "^3.996.3", - "@aws-sdk/types": "^3.973.4", - "@smithy/property-provider": "^4.2.10", - "@smithy/shared-ini-file-loader": "^4.4.5", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/middleware-bucket-endpoint": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.3.tgz", - "integrity": "sha512-fmbgWYirF67YF1GfD7cg5N6HHQ96EyRNx/rDIrTF277/zTWVuPI2qS/ZHgofwR1NZPe/NWvoppflQY01LrbVLg==", - "requires": { - "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-arn-parser": "^3.972.2", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-config-provider": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/middleware-expect-continue": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.3.tgz", - "integrity": "sha512-4msC33RZsXQpUKR5QR4HnvBSNCPLGHmB55oDiROqqgyOc+TOfVu2xgi5goA7ms6MdZLeEh2905UfWMnMMF4mRg==", - "requires": { - "@aws-sdk/types": "^3.973.1", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/middleware-flexible-checksums": { - "version": "3.973.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.973.1.tgz", - "integrity": "sha512-QLXsxsI6VW8LuGK+/yx699wzqP/NMCGk/hSGP+qtB+Lcff+23UlbahyouLlk+nfT7Iu021SkXBhnAuVd6IZcPw==", - "requires": { - "@aws-crypto/crc32": "5.2.0", - "@aws-crypto/crc32c": "5.2.0", - "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/crc64-nvme": "^3.972.3", - "@aws-sdk/types": "^3.973.4", - "@smithy/is-array-buffer": "^4.2.1", - "@smithy/node-config-provider": "^4.3.10", - "@smithy/protocol-http": "^5.3.10", - "@smithy/types": "^4.13.0", - "@smithy/util-middleware": "^4.2.10", - "@smithy/util-stream": "^4.5.15", - "@smithy/util-utf8": "^4.2.1", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/middleware-host-header": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.6.tgz", - "integrity": "sha512-5XHwjPH1lHB+1q4bfC7T8Z5zZrZXfaLcjSMwTd1HPSPrCmPFMbg3UQ5vgNWcVj0xoX4HWqTGkSf2byrjlnRg5w==", - "requires": { - "@aws-sdk/types": "^3.973.4", - "@smithy/protocol-http": "^5.3.10", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/middleware-location-constraint": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.3.tgz", - "integrity": "sha512-nIg64CVrsXp67vbK0U1/Is8rik3huS3QkRHn2DRDx4NldrEFMgdkZGI/+cZMKD9k4YOS110Dfu21KZLHrFA/1g==", - "requires": { - "@aws-sdk/types": "^3.973.1", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/middleware-logger": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.6.tgz", - "integrity": "sha512-iFnaMFMQdljAPrvsCVKYltPt2j40LQqukAbXvW7v0aL5I+1GO7bZ/W8m12WxW3gwyK5p5u1WlHg8TSAizC5cZw==", - "requires": { - "@aws-sdk/types": "^3.973.4", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/middleware-recursion-detection": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.6.tgz", - "integrity": "sha512-dY4v3of5EEMvik6+UDwQ96KfUFDk8m1oZDdkSc5lwi4o7rFrjnv0A+yTV+gu230iybQZnKgDLg/rt2P3H+Vscw==", - "requires": { - "@aws-sdk/types": "^3.973.4", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.10", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/middleware-sdk-s3": { - "version": "3.972.15", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.15.tgz", - "integrity": "sha512-WDLgssevOU5BFx1s8jA7jj6cE5HuImz28sy9jKOaVtz0AW1lYqSzotzdyiybFaBcQTs5zxXOb2pUfyMxgEKY3Q==", - "requires": { - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/types": "^3.973.4", - "@aws-sdk/util-arn-parser": "^3.972.2", - "@smithy/core": "^3.23.6", - "@smithy/node-config-provider": "^4.3.10", - "@smithy/protocol-http": "^5.3.10", - "@smithy/signature-v4": "^5.3.10", - "@smithy/smithy-client": "^4.12.0", - "@smithy/types": "^4.13.0", - "@smithy/util-config-provider": "^4.2.1", - "@smithy/util-middleware": "^4.2.10", - "@smithy/util-stream": "^4.5.15", - "@smithy/util-utf8": "^4.2.1", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/middleware-ssec": { - "version": "3.972.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.3.tgz", - "integrity": "sha512-dU6kDuULN3o3jEHcjm0c4zWJlY1zWVkjG9NPe9qxYLLpcbdj5kRYBS2DdWYD+1B9f910DezRuws7xDEqKkHQIg==", - "requires": { - "@aws-sdk/types": "^3.973.1", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/middleware-user-agent": { - "version": "3.972.15", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.15.tgz", - "integrity": "sha512-ABlFVcIMmuRAwBT+8q5abAxOr7WmaINirDJBnqGY5b5jSDo00UMlg/G4a0xoAgwm6oAECeJcwkvDlxDwKf58fQ==", - "requires": { - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/types": "^3.973.4", - "@aws-sdk/util-endpoints": "^3.996.3", - "@smithy/core": "^3.23.6", - "@smithy/protocol-http": "^5.3.10", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - }, - "dependencies": { - "@aws-sdk/util-endpoints": { - "version": "3.996.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz", - "integrity": "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==", - "requires": { - "@aws-sdk/types": "^3.973.6", - "@smithy/types": "^4.13.1", - "@smithy/url-parser": "^4.2.12", - "@smithy/util-endpoints": "^3.3.3", - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/nested-clients": { - "version": "3.996.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.3.tgz", - "integrity": "sha512-AU5TY1V29xqwg/MxmA2odwysTez+ccFAhmfRJk+QZT5HNv90UTA9qKd1J9THlsQkvmH7HWTEV1lDNxkQO5PzNw==", - "requires": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/middleware-host-header": "^3.972.6", - "@aws-sdk/middleware-logger": "^3.972.6", - "@aws-sdk/middleware-recursion-detection": "^3.972.6", - "@aws-sdk/middleware-user-agent": "^3.972.15", - "@aws-sdk/region-config-resolver": "^3.972.6", - "@aws-sdk/types": "^3.973.4", - "@aws-sdk/util-endpoints": "^3.996.3", - "@aws-sdk/util-user-agent-browser": "^3.972.6", - "@aws-sdk/util-user-agent-node": "^3.973.0", - "@smithy/config-resolver": "^4.4.9", - "@smithy/core": "^3.23.6", - "@smithy/fetch-http-handler": "^5.3.11", - "@smithy/hash-node": "^4.2.10", - "@smithy/invalid-dependency": "^4.2.10", - "@smithy/middleware-content-length": "^4.2.10", - "@smithy/middleware-endpoint": "^4.4.20", - "@smithy/middleware-retry": "^4.4.37", - "@smithy/middleware-serde": "^4.2.11", - "@smithy/middleware-stack": "^4.2.10", - "@smithy/node-config-provider": "^4.3.10", - "@smithy/node-http-handler": "^4.4.12", - "@smithy/protocol-http": "^5.3.10", - "@smithy/smithy-client": "^4.12.0", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.10", - "@smithy/util-base64": "^4.3.1", - "@smithy/util-body-length-browser": "^4.2.1", - "@smithy/util-body-length-node": "^4.2.2", - "@smithy/util-defaults-mode-browser": "^4.3.36", - "@smithy/util-defaults-mode-node": "^4.2.39", - "@smithy/util-endpoints": "^3.3.1", - "@smithy/util-middleware": "^4.2.10", - "@smithy/util-retry": "^4.2.10", - "@smithy/util-utf8": "^4.2.1", - "tslib": "^2.6.2" - }, - "dependencies": { - "@aws-sdk/util-endpoints": { - "version": "3.996.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz", - "integrity": "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==", - "requires": { - "@aws-sdk/types": "^3.973.6", - "@smithy/types": "^4.13.1", - "@smithy/url-parser": "^4.2.12", - "@smithy/util-endpoints": "^3.3.3", - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/region-config-resolver": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.6.tgz", - "integrity": "sha512-Aa5PusHLXAqLTX1UKDvI3pHQJtIsF7Q+3turCHqfz/1F61/zDMWfbTC8evjhrrYVAtz9Vsv3SJ/waSUeu7B6gw==", - "requires": { - "@aws-sdk/types": "^3.973.4", - "@smithy/config-resolver": "^4.4.9", - "@smithy/node-config-provider": "^4.3.10", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/signature-v4-multi-region": { - "version": "3.983.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.983.0.tgz", - "integrity": "sha512-11FCcxI/WKRufKDdPgKPXtrhjDArhkOPb4mf66rICZUnPHlD8Cb7cjZZS/eFC+iuwoHBosrxo0hYsvK3s7DxGw==", - "requires": { - "@aws-sdk/middleware-sdk-s3": "^3.972.6", - "@aws-sdk/types": "^3.973.1", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/token-providers": { - "version": "3.999.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.999.0.tgz", - "integrity": "sha512-cx0hHUlgXULfykx4rdu/ciNAJaa3AL5xz3rieCz7NKJ68MJwlj3664Y8WR5MGgxfyYJBdamnkjNSx5Kekuc0cg==", - "requires": { - "@aws-sdk/core": "^3.973.15", - "@aws-sdk/nested-clients": "^3.996.3", - "@aws-sdk/types": "^3.973.4", - "@smithy/property-provider": "^4.2.10", - "@smithy/shared-ini-file-loader": "^4.4.5", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/types": { - "version": "3.973.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.6.tgz", - "integrity": "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw==", - "requires": { - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/util-arn-parser": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.2.tgz", - "integrity": "sha512-VkykWbqMjlSgBFDyrY3nOSqupMc6ivXuGmvci6Q3NnLq5kC+mKQe2QBZ4nrWRE/jqOxeFP2uYzLtwncYYcvQDg==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@aws-sdk/util-endpoints": { - "version": "3.983.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.983.0.tgz", - "integrity": "sha512-t/VbL2X3gvDEjC4gdySOeFFOZGQEBKwa23pRHeB7hBLBZ119BB/2OEFtTFWKyp3bnMQgxpeVeGS7/hxk6wpKJw==", - "requires": { - "@aws-sdk/types": "^3.973.1", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/util-locate-window": { - "version": "3.568.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.568.0.tgz", - "integrity": "sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@aws-sdk/util-user-agent-browser": { - "version": "3.972.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.6.tgz", - "integrity": "sha512-Fwr/llD6GOrFgQnKaI2glhohdGuBDfHfora6iG9qsBBBR8xv1SdCSwbtf5CWlUdCw5X7g76G/9Hf0Inh0EmoxA==", - "requires": { - "@aws-sdk/types": "^3.973.4", - "@smithy/types": "^4.13.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/util-user-agent-node": { - "version": "3.973.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.0.tgz", - "integrity": "sha512-A9J2G4Nf236e9GpaC1JnA8wRn6u6GjnOXiTwBLA6NUJhlBTIGfrTy+K1IazmF8y+4OFdW3O5TZlhyspJMqiqjA==", - "requires": { - "@aws-sdk/middleware-user-agent": "^3.972.15", - "@aws-sdk/types": "^3.973.4", - "@smithy/node-config-provider": "^4.3.10", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/xml-builder": { - "version": "3.972.11", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.11.tgz", - "integrity": "sha512-iitV/gZKQMvY9d7ovmyFnFuTHbBAtrmLnvaSb/3X8vOKyevwtpmEtyc8AdhVWZe0pI/1GsHxlEvQeOePFzy7KQ==", - "requires": { - "@smithy/types": "^4.13.1", - "fast-xml-parser": "5.4.1", - "tslib": "^2.6.2" - } - }, - "@aws/lambda-invoke-store": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", - "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==" - }, - "@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - } - }, - "@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true - }, - "@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, - "peer": true, - "requires": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "dependencies": { - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "requires": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "dependencies": { - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true - }, - "@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, - "requires": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - } - }, - "@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - } - }, - "@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true - }, - "@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", - "dev": true, - "requires": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" - } - }, - "@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", - "dev": true, - "requires": { - "@babel/types": "^7.29.0" - } - }, - "@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==" - }, - "@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - } - }, - "@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - } - }, - "@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "requires": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - } - }, - "@bcoe/v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", - "dev": true - }, - "@chainsafe/as-chacha20poly1305": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@chainsafe/as-chacha20poly1305/-/as-chacha20poly1305-0.1.0.tgz", - "integrity": "sha512-BpNcL8/lji/GM3+vZ/bgRWqJ1q5kwvTFmGPk7pxm/QQZDbaMI98waOHjEymTjq2JmdD/INdNBFOVSyJofXg7ew==" - }, - "@chainsafe/as-sha256": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-1.2.0.tgz", - "integrity": "sha512-H2BNHQ5C3RS+H0ZvOdovK6GjFAyq5T6LClad8ivwj9Oaiy28uvdsGVS7gNJKuZmg0FGHAI+n7F0Qju6U0QkKDA==" - }, - "@chainsafe/is-ip": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@chainsafe/is-ip/-/is-ip-2.1.0.tgz", - "integrity": "sha512-KIjt+6IfysQ4GCv66xihEitBjvhU/bixbbbFxdJ1sqCp4uJ0wuZiYBPhksZoy4lfaF0k9cwNzY5upEW/VWdw3w==" - }, - "@chainsafe/libp2p-noise": { - "version": "16.1.3", - "resolved": "https://registry.npmjs.org/@chainsafe/libp2p-noise/-/libp2p-noise-16.1.3.tgz", - "integrity": "sha512-YLonKdIUFk/0keKRfzlmdrsObi8r0EaZC14Vjh3qdLy4+W7NaQAs1sSMt8aDP07oE78pa51NyejmQLKOnt7tOw==", - "requires": { - "@chainsafe/as-chacha20poly1305": "^0.1.0", - "@chainsafe/as-sha256": "^1.0.0", - "@libp2p/crypto": "^5.0.0", - "@libp2p/interface": "^2.9.0", - "@libp2p/peer-id": "^5.0.0", - "@noble/ciphers": "^1.1.3", - "@noble/curves": "^1.1.0", - "@noble/hashes": "^1.3.1", - "it-length-prefixed": "^10.0.1", - "it-length-prefixed-stream": "^2.0.1", - "it-pair": "^2.0.6", - "it-pipe": "^3.0.1", - "it-stream-types": "^2.0.1", - "protons-runtime": "^5.5.0", - "uint8arraylist": "^2.4.3", - "uint8arrays": "^5.0.0", - "wherearewe": "^2.0.1" - } - }, - "@chainsafe/netmask": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@chainsafe/netmask/-/netmask-2.0.0.tgz", - "integrity": "sha512-I3Z+6SWUoaljh3TBzCnCxjlUyN8tA+NAk5L6m9IxvCf1BENQTePzPMis97CoN/iMW1St3WN+AWCCRp+TTBRiDg==", - "requires": { - "@chainsafe/is-ip": "^2.0.1" - } - }, - "@chialab/esbuild-plugin-meta-url": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@chialab/esbuild-plugin-meta-url/-/esbuild-plugin-meta-url-0.19.0.tgz", - "integrity": "sha512-VzXuFNaUW9v4bQeFVFVM/KL4/hB/xwMNGGxy1E8CHZAuA+6xHGSqJK7gf17vyWC+GulwIEfzTqfIHU8H8Ic/Zg==", - "dev": true, - "requires": { - "@chialab/esbuild-rna": "^0.19.0", - "@chialab/estransform": "^0.19.0", - "mime-types": "^2.1.35" - } - }, - "@chialab/esbuild-plugin-worker": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@chialab/esbuild-plugin-worker/-/esbuild-plugin-worker-0.19.0.tgz", - "integrity": "sha512-atbjSLe9GcG1vMABKai0uwmfsZmSpdjSvLh9pn5CxnI4p57VqSxp5aW24hGV2wwTna78DhtP/spUFUlaWaqVgg==", - "dev": true, - "requires": { - "@chialab/esbuild-plugin-meta-url": "^0.19.0", - "@chialab/esbuild-rna": "^0.19.0", - "@chialab/estransform": "^0.19.0" - } - }, - "@chialab/esbuild-rna": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@chialab/esbuild-rna/-/esbuild-rna-0.19.0.tgz", - "integrity": "sha512-701y6cN/F9JEjMsJW7PcLxUq1kcReNJ69DTcw7TXLA5f5flBH7zZf+U0F/bwn3vQ2StszT8scTmlflPP4J8m3Q==", - "dev": true, - "requires": { - "@chialab/estransform": "^0.19.0", - "@chialab/node-resolve": "^0.19.0" - } - }, - "@chialab/estransform": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@chialab/estransform/-/estransform-0.19.0.tgz", - "integrity": "sha512-w6i/RZ3SddSCDIYZLkwnK+KAseCKW3BT627San3EbzH3NRY0Gn5RqXCjX9J/mNGe2FydwAevG7O605z50woWuA==", - "dev": true, - "requires": { - "@napi-rs/magic-string": "^0.3.4", - "@parcel/source-map": "^2.0.0", - "cjs-module-lexer": "^1.2.2", - "es-module-lexer": "^1.0.0", - "oxc-parser": "^0.8.0" - } - }, - "@chialab/node-resolve": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@chialab/node-resolve/-/node-resolve-0.19.0.tgz", - "integrity": "sha512-VaYOPbgIEVPu7ic+LAA8z2syEpEQhO1nLce0BSPs2eMubQ+mxSrH3RyXKH+CREhOnBWH3Rd3uuFZn2Rofypsbg==", - "dev": true - }, - "@codemirror/state": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz", - "integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==", - "dev": true, - "peer": true, - "requires": { - "@marijn/find-cluster-break": "^1.0.0" - } - }, - "@codemirror/view": { - "version": "6.38.6", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz", - "integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==", - "dev": true, - "peer": true, - "requires": { - "@codemirror/state": "^6.5.0", - "crelt": "^1.0.6", - "style-mod": "^4.1.0", - "w3c-keyname": "^2.2.4" - } - }, - "@esbuild/aix-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", - "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", - "dev": true, - "optional": true - }, - "@esbuild/openharmony-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", - "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", - "dev": true, - "optional": true - }, - "@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^3.4.3" - } - }, - "@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true - }, - "@eslint/compat": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.2.tgz", - "integrity": "sha512-pR1DoD0h3HfF675QZx0xsyrsU8q70Z/plx7880NOhS02NuWLgBCOMDL787nUeQ7EWLkxv3bPQJaarjcPQb2Dwg==", - "dev": true, - "requires": { - "@eslint/core": "^1.1.0" - }, - "dependencies": { - "@eslint/core": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz", - "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.15" - } - } - } - }, - "@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", - "dev": true, - "requires": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "dependencies": { - "minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, - "requires": { - "@eslint/core": "^0.17.0" - } - }, - "@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, - "requires": { - "@types/json-schema": "^7.0.15" - } - }, - "@eslint/eslintrc": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz", - "integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==", - "dev": true, - "requires": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.3", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "@eslint/js": { - "version": "9.39.3", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", - "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", - "dev": true - }, - "@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true - }, - "@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", - "dev": true, - "requires": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - } - }, - "@ethersproject/bytes": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.8.0.tgz", - "integrity": "sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==", - "requires": { - "@ethersproject/logger": "^5.8.0" - } - }, - "@ethersproject/logger": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.8.0.tgz", - "integrity": "sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==" - }, - "@ethersproject/rlp": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.8.0.tgz", - "integrity": "sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==", - "requires": { - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/logger": "^5.8.0" - } - }, - "@firebase/ai": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-2.4.0.tgz", - "integrity": "sha512-YilG6AJ/nYpCKtxZyvEzBRAQv5bU+2tBOKX4Ps0rNNSdxN39aT37kGhjATbk1kq1z5Lq7mkWglw/ajAF3lOWUg==", - "requires": { - "@firebase/app-check-interop-types": "0.3.3", - "@firebase/component": "0.7.0", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/analytics": { - "version": "0.10.19", - "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.19.tgz", - "integrity": "sha512-3wU676fh60gaiVYQEEXsbGS4HbF2XsiBphyvvqDbtC1U4/dO4coshbYktcCHq+HFaGIK07iHOh4pME0hEq1fcg==", - "requires": { - "@firebase/component": "0.7.0", - "@firebase/installations": "0.6.19", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/analytics-compat": { - "version": "0.2.25", - "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.25.tgz", - "integrity": "sha512-fdzoaG0BEKbqksRDhmf4JoyZf16Wosrl0Y7tbZtJyVDOOwziE0vrFjmZuTdviL0yhak+Nco6rMsUUbkbD+qb6Q==", - "requires": { - "@firebase/analytics": "0.10.19", - "@firebase/analytics-types": "0.8.3", - "@firebase/component": "0.7.0", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/analytics-types": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.3.tgz", - "integrity": "sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg==" - }, - "@firebase/app": { - "version": "0.14.4", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.4.tgz", - "integrity": "sha512-pUxEGmR+uu21OG/icAovjlu1fcYJzyVhhT0rsCrn+zi+nHtrS43Bp9KPn9KGa4NMspCUE++nkyiqziuIvJdwzw==", - "peer": true, - "requires": { - "@firebase/component": "0.7.0", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", - "idb": "7.1.1", - "tslib": "^2.1.0" - }, - "dependencies": { - "idb": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", - "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" - } - } - }, - "@firebase/app-check": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.11.0.tgz", - "integrity": "sha512-XAvALQayUMBJo58U/rxW02IhsesaxxfWVmVkauZvGEz3vOAjMEQnzFlyblqkc2iAaO82uJ2ZVyZv9XzPfxjJ6w==", - "requires": { - "@firebase/component": "0.7.0", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/app-check-compat": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.4.0.tgz", - "integrity": "sha512-UfK2Q8RJNjYM/8MFORltZRG9lJj11k0nW84rrffiKvcJxLf1jf6IEjCIkCamykHE73C6BwqhVfhIBs69GXQV0g==", - "requires": { - "@firebase/app-check": "0.11.0", - "@firebase/app-check-types": "0.5.3", - "@firebase/component": "0.7.0", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/app-check-interop-types": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz", - "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==" - }, - "@firebase/app-check-types": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.3.tgz", - "integrity": "sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng==" - }, - "@firebase/app-compat": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.4.tgz", - "integrity": "sha512-T7ifGmb+awJEcp542Ek4HtNfBxcBrnuk1ggUdqyFEdsXHdq7+wVlhvE6YukTL7NS8hIkEfL7TMAPx/uCNqt30g==", - "peer": true, - "requires": { - "@firebase/app": "0.14.4", - "@firebase/component": "0.7.0", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/app-types": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", - "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", - "peer": true - }, - "@firebase/auth": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.11.0.tgz", - "integrity": "sha512-5j7+ua93X+IRcJ1oMDTClTo85l7Xe40WSkoJ+shzPrX7OISlVWLdE1mKC57PSD+/LfAbdhJmvKixINBw2ESK6w==", - "requires": { - "@firebase/component": "0.7.0", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/auth-compat": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.6.0.tgz", - "integrity": "sha512-J0lGSxXlG/lYVi45wbpPhcWiWUMXevY4fvLZsN1GHh+po7TZVng+figdHBVhFheaiipU8HZyc7ljw1jNojM2nw==", - "requires": { - "@firebase/auth": "1.11.0", - "@firebase/auth-types": "0.13.0", - "@firebase/component": "0.7.0", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/auth-interop-types": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz", - "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==" - }, - "@firebase/auth-types": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.0.tgz", - "integrity": "sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==", - "requires": {} - }, - "@firebase/component": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.0.tgz", - "integrity": "sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg==", - "requires": { - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/data-connect": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.11.tgz", - "integrity": "sha512-G258eLzAD6im9Bsw+Qm1Z+P4x0PGNQ45yeUuuqe5M9B1rn0RJvvsQCRHXgE52Z+n9+WX1OJd/crcuunvOGc7Vw==", - "requires": { - "@firebase/auth-interop-types": "0.2.4", - "@firebase/component": "0.7.0", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/database": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.0.tgz", - "integrity": "sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg==", - "requires": { - "@firebase/app-check-interop-types": "0.3.3", - "@firebase/auth-interop-types": "0.2.4", - "@firebase/component": "0.7.0", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", - "faye-websocket": "0.11.4", - "tslib": "^2.1.0" - } - }, - "@firebase/database-compat": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.0.tgz", - "integrity": "sha512-8nYc43RqxScsePVd1qe1xxvWNf0OBnbwHxmXJ7MHSuuTVYFO3eLyLW3PiCKJ9fHnmIz4p4LbieXwz+qtr9PZDg==", - "requires": { - "@firebase/component": "0.7.0", - "@firebase/database": "1.1.0", - "@firebase/database-types": "1.0.16", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/database-types": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.16.tgz", - "integrity": "sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw==", - "requires": { - "@firebase/app-types": "0.9.3", - "@firebase/util": "1.13.0" - } - }, - "@firebase/firestore": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.9.2.tgz", - "integrity": "sha512-iuA5+nVr/IV/Thm0Luoqf2mERUvK9g791FZpUJV1ZGXO6RL2/i/WFJUj5ZTVXy5pRjpWYO+ZzPcReNrlilmztA==", - "requires": { - "@firebase/component": "0.7.0", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", - "@firebase/webchannel-wrapper": "1.0.5", - "@grpc/grpc-js": "~1.9.0", - "@grpc/proto-loader": "^0.7.8", - "tslib": "^2.1.0" - } - }, - "@firebase/firestore-compat": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.2.tgz", - "integrity": "sha512-cy7ov6SpFBx+PHwFdOOjbI7kH00uNKmIFurAn560WiPCZXy9EMnil1SOG7VF4hHZKdenC+AHtL4r3fNpirpm0w==", - "requires": { - "@firebase/component": "0.7.0", - "@firebase/firestore": "4.9.2", - "@firebase/firestore-types": "3.0.3", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/firestore-types": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz", - "integrity": "sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==", - "requires": {} - }, - "@firebase/functions": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.13.1.tgz", - "integrity": "sha512-sUeWSb0rw5T+6wuV2o9XNmh9yHxjFI9zVGFnjFi+n7drTEWpl7ZTz1nROgGrSu472r+LAaj+2YaSicD4R8wfbw==", - "requires": { - "@firebase/app-check-interop-types": "0.3.3", - "@firebase/auth-interop-types": "0.2.4", - "@firebase/component": "0.7.0", - "@firebase/messaging-interop-types": "0.2.3", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/functions-compat": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.4.1.tgz", - "integrity": "sha512-AxxUBXKuPrWaVNQ8o1cG1GaCAtXT8a0eaTDfqgS5VsRYLAR0ALcfqDLwo/QyijZj1w8Qf8n3Qrfy/+Im245hOQ==", - "requires": { - "@firebase/component": "0.7.0", - "@firebase/functions": "0.13.1", - "@firebase/functions-types": "0.6.3", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/functions-types": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz", - "integrity": "sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg==" - }, - "@firebase/installations": { - "version": "0.6.19", - "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.19.tgz", - "integrity": "sha512-nGDmiwKLI1lerhwfwSHvMR9RZuIH5/8E3kgUWnVRqqL7kGVSktjLTWEMva7oh5yxQ3zXfIlIwJwMcaM5bK5j8Q==", - "requires": { - "@firebase/component": "0.7.0", - "@firebase/util": "1.13.0", - "idb": "7.1.1", - "tslib": "^2.1.0" - }, - "dependencies": { - "idb": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", - "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" - } - } - }, - "@firebase/installations-compat": { - "version": "0.2.19", - "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.19.tgz", - "integrity": "sha512-khfzIY3EI5LePePo7vT19/VEIH1E3iYsHknI/6ek9T8QCozAZshWT9CjlwOzZrKvTHMeNcbpo/VSOSIWDSjWdQ==", - "requires": { - "@firebase/component": "0.7.0", - "@firebase/installations": "0.6.19", - "@firebase/installations-types": "0.5.3", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/installations-types": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.3.tgz", - "integrity": "sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==", - "requires": {} - }, - "@firebase/logger": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.0.tgz", - "integrity": "sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g==", - "requires": { - "tslib": "^2.1.0" - } - }, - "@firebase/messaging": { - "version": "0.12.23", - "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.23.tgz", - "integrity": "sha512-cfuzv47XxqW4HH/OcR5rM+AlQd1xL/VhuaeW/wzMW1LFrsFcTn0GND/hak1vkQc2th8UisBcrkVcQAnOnKwYxg==", - "requires": { - "@firebase/component": "0.7.0", - "@firebase/installations": "0.6.19", - "@firebase/messaging-interop-types": "0.2.3", - "@firebase/util": "1.13.0", - "idb": "7.1.1", - "tslib": "^2.1.0" - }, - "dependencies": { - "idb": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", - "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" - } - } - }, - "@firebase/messaging-compat": { - "version": "0.2.23", - "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.23.tgz", - "integrity": "sha512-SN857v/kBUvlQ9X/UjAqBoQ2FEaL1ZozpnmL1ByTe57iXkmnVVFm9KqAsTfmf+OEwWI4kJJe9NObtN/w22lUgg==", - "requires": { - "@firebase/component": "0.7.0", - "@firebase/messaging": "0.12.23", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/messaging-interop-types": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz", - "integrity": "sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q==" - }, - "@firebase/performance": { - "version": "0.7.9", - "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.9.tgz", - "integrity": "sha512-UzybENl1EdM2I1sjYm74xGt/0JzRnU/0VmfMAKo2LSpHJzaj77FCLZXmYQ4oOuE+Pxtt8Wy2BVJEENiZkaZAzQ==", - "requires": { - "@firebase/component": "0.7.0", - "@firebase/installations": "0.6.19", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0", - "web-vitals": "^4.2.4" - } - }, - "@firebase/performance-compat": { - "version": "0.2.22", - "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.22.tgz", - "integrity": "sha512-xLKxaSAl/FVi10wDX/CHIYEUP13jXUjinL+UaNXT9ByIvxII5Ne5150mx6IgM8G6Q3V+sPiw9C8/kygkyHUVxg==", - "requires": { - "@firebase/component": "0.7.0", - "@firebase/logger": "0.5.0", - "@firebase/performance": "0.7.9", - "@firebase/performance-types": "0.2.3", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/performance-types": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz", - "integrity": "sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ==" - }, - "@firebase/remote-config": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.7.0.tgz", - "integrity": "sha512-dX95X6WlW7QlgNd7aaGdjAIZUiQkgWgNS+aKNu4Wv92H1T8Ue/NDUjZHd9xb8fHxLXIHNZeco9/qbZzr500MjQ==", - "requires": { - "@firebase/component": "0.7.0", - "@firebase/installations": "0.6.19", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/remote-config-compat": { - "version": "0.2.20", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.20.tgz", - "integrity": "sha512-P/ULS9vU35EL9maG7xp66uljkZgcPMQOxLj3Zx2F289baTKSInE6+YIkgHEi1TwHoddC/AFePXPpshPlEFkbgg==", - "requires": { - "@firebase/component": "0.7.0", - "@firebase/logger": "0.5.0", - "@firebase/remote-config": "0.7.0", - "@firebase/remote-config-types": "0.5.0", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/remote-config-types": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.5.0.tgz", - "integrity": "sha512-vI3bqLoF14L/GchtgayMiFpZJF+Ao3uR8WCde0XpYNkSokDpAKca2DxvcfeZv7lZUqkUwQPL2wD83d3vQ4vvrg==" - }, - "@firebase/storage": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.14.0.tgz", - "integrity": "sha512-xWWbb15o6/pWEw8H01UQ1dC5U3rf8QTAzOChYyCpafV6Xki7KVp3Yaw2nSklUwHEziSWE9KoZJS7iYeyqWnYFA==", - "requires": { - "@firebase/component": "0.7.0", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/storage-compat": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.4.0.tgz", - "integrity": "sha512-vDzhgGczr1OfcOy285YAPur5pWDEvD67w4thyeCUh6Ys0izN9fNYtA1MJERmNBfqjqu0lg0FM5GLbw0Il21M+g==", - "requires": { - "@firebase/component": "0.7.0", - "@firebase/storage": "0.14.0", - "@firebase/storage-types": "0.8.3", - "@firebase/util": "1.13.0", - "tslib": "^2.1.0" - } - }, - "@firebase/storage-types": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.3.tgz", - "integrity": "sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==", - "requires": {} - }, - "@firebase/util": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.13.0.tgz", - "integrity": "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==", - "peer": true, - "requires": { - "tslib": "^2.1.0" - } - }, - "@firebase/webchannel-wrapper": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.5.tgz", - "integrity": "sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw==" - }, - "@grpc/grpc-js": { - "version": "1.9.15", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz", - "integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==", - "requires": { - "@grpc/proto-loader": "^0.7.8", - "@types/node": ">=12.12.47" - } - }, - "@grpc/proto-loader": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", - "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", - "requires": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.2.5", - "yargs": "^17.7.2" - } - }, - "@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true - }, - "@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, - "requires": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, - "dependencies": { - "@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true - } - } - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true - }, - "@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true - }, - "@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "requires": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true - }, - "ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true - }, - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "requires": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - } - }, - "strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "requires": { - "ansi-regex": "^6.2.2" - } - }, - "wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "requires": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - } - } - } - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "requires": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true - }, - "@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" - }, - "@libp2p/bootstrap": { - "version": "11.0.42", - "resolved": "https://registry.npmjs.org/@libp2p/bootstrap/-/bootstrap-11.0.42.tgz", - "integrity": "sha512-xe5LMZrXR2cnFcR69ax/cHqNCc8zwSvqDWOf82u/ifAsUae7M0eeoEVi15lfrPewWst4YPkTqYDkZSPWC65I3Q==", - "requires": { - "@libp2p/interface": "^2.10.4", - "@libp2p/interface-internal": "^2.3.17", - "@libp2p/peer-id": "^5.1.7", - "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.4.4", - "main-event": "^1.0.1" - } - }, - "@libp2p/crypto": { - "version": "5.1.13", - "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-5.1.13.tgz", - "integrity": "sha512-8NN9cQP3jDn+p9+QE9ByiEoZ2lemDFf/unTgiKmS3JF93ph240EUVdbCyyEgOMfykzb0okTM4gzvwfx9osJebQ==", - "requires": { - "@libp2p/interface": "^3.1.0", - "@noble/curves": "^2.0.1", - "@noble/hashes": "^2.0.1", - "multiformats": "^13.4.0", - "protons-runtime": "^5.6.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - }, - "dependencies": { - "@libp2p/interface": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-3.1.0.tgz", - "integrity": "sha512-RE7/XyvC47fQBe1cHxhMvepYKa5bFCUyFrrpj8PuM0E7JtzxU7F+Du5j4VXbg2yLDcToe0+j8mB7jvwE2AThYw==", - "requires": { - "@multiformats/dns": "^1.0.6", - "@multiformats/multiaddr": "^13.0.1", - "main-event": "^1.0.1", - "multiformats": "^13.4.0", - "progress-events": "^1.0.1", - "uint8arraylist": "^2.4.8" - } - }, - "@multiformats/multiaddr": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-13.0.1.tgz", - "integrity": "sha512-XToN915cnfr6Lr9EdGWakGJbPT0ghpg/850HvdC+zFX8XvpLZElwa8synCiwa8TuvKNnny6m8j8NVBNCxhIO3g==", - "requires": { - "@chainsafe/is-ip": "^2.0.1", - "multiformats": "^13.0.0", - "uint8-varint": "^2.0.1", - "uint8arrays": "^5.0.0" - } - }, - "@noble/curves": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz", - "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==", - "requires": { - "@noble/hashes": "2.0.1" - } - }, - "@noble/hashes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", - "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==" - } - } - }, - "@libp2p/identify": { - "version": "3.0.36", - "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-3.0.36.tgz", - "integrity": "sha512-swpgKzZ8SihHeguIEf3LxYlEcD7C9cSB7DE1XGTuCxNXkXksv2ieQTSiJ2xURMRw0nwe0wEGS5vzmshJh3kzrA==", - "requires": { - "@libp2p/crypto": "^5.1.6", - "@libp2p/interface": "^2.10.4", - "@libp2p/interface-internal": "^2.3.17", - "@libp2p/peer-id": "^5.1.7", - "@libp2p/peer-record": "^8.0.33", - "@libp2p/utils": "^6.7.0", - "@multiformats/multiaddr": "^12.4.4", - "@multiformats/multiaddr-matcher": "^1.7.2", - "it-drain": "^3.0.9", - "it-parallel": "^3.0.11", - "it-protobuf-stream": "^2.0.2", - "main-event": "^1.0.1", - "protons-runtime": "^5.5.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "@libp2p/interface": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.11.0.tgz", - "integrity": "sha512-0MUFKoXWHTQW3oWIgSHApmYMUKWO/Y02+7Hpyp+n3z+geD4Xo2Rku2gYWmxcq+Pyjkz6Q9YjDWz3Yb2SoV2E8Q==", - "requires": { - "@multiformats/dns": "^1.0.6", - "@multiformats/multiaddr": "^12.4.4", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.2", - "main-event": "^1.0.1", - "multiformats": "^13.3.6", - "progress-events": "^1.0.1", - "uint8arraylist": "^2.4.8" - } - }, - "@libp2p/interface-internal": { - "version": "2.3.19", - "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-2.3.19.tgz", - "integrity": "sha512-v335EB0i5CaNF+0SqT01CTBp0VyjJizpy46KprcshFFjX16UQ8+/QzoTZqmot9WiAmAzwR0b87oKmlAE9cpxzQ==", - "requires": { - "@libp2p/interface": "^2.11.0", - "@libp2p/peer-collections": "^6.0.35", - "@multiformats/multiaddr": "^12.4.4", - "progress-events": "^1.0.1" - } - }, - "@libp2p/logger": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-5.2.0.tgz", - "integrity": "sha512-OEFS529CnIKfbWEHmuCNESw9q0D0hL8cQ8klQfjIVPur15RcgAEgc1buQ7Y6l0B6tCYg120bp55+e9tGvn8c0g==", - "requires": { - "@libp2p/interface": "^2.11.0", - "@multiformats/multiaddr": "^12.4.4", - "interface-datastore": "^8.3.1", - "multiformats": "^13.3.6", - "weald": "^1.0.4" - } - }, - "@libp2p/mplex": { - "version": "11.0.42", - "resolved": "https://registry.npmjs.org/@libp2p/mplex/-/mplex-11.0.42.tgz", - "integrity": "sha512-06HXNQS02GOEx1796Nsk640tIHwnHtJvd4TWnslUEeVi7SxLA8LqCttT7qJ2P3Y1tmm3E2a74kxtHqaJDqypig==", - "requires": { - "@libp2p/interface": "^2.10.4", - "@libp2p/utils": "^6.7.0", - "it-pipe": "^3.0.1", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.2", - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "@libp2p/multistream-select": { - "version": "6.0.29", - "resolved": "https://registry.npmjs.org/@libp2p/multistream-select/-/multistream-select-6.0.29.tgz", - "integrity": "sha512-SWQbPcABOIpznEY7+vAp0Y3HNrE2PlaVY4EywN0lUZ7zvTv9VnAb7av3/gMvfaLI+YrOvhCr1mZ9qbSB93k4kA==", - "requires": { - "@libp2p/interface": "^2.11.0", - "it-length-prefixed": "^10.0.1", - "it-length-prefixed-stream": "^2.0.2", - "it-stream-types": "^2.0.2", - "p-defer": "^4.0.1", - "race-signal": "^1.1.3", - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "@libp2p/peer-collections": { - "version": "6.0.35", - "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-6.0.35.tgz", - "integrity": "sha512-QiloK3T7DXW7R2cpL38dBnALCHf5pMzs/TyFzlEK33WezA2YFVoj7CtOJKqbn29bmV9uspWOxMgfmLUXf8ALvA==", - "requires": { - "@libp2p/interface": "^2.11.0", - "@libp2p/peer-id": "^5.1.9", - "@libp2p/utils": "^6.7.2", - "multiformats": "^13.3.6" - } - }, - "@libp2p/peer-id": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-5.1.9.tgz", - "integrity": "sha512-cVDp7lX187Epmi/zr0Qq2RsEMmueswP9eIxYSFoMcHL/qcvRFhsxOfUGB8361E26s2WJvC9sXZ0oJS9XVueJhQ==", - "requires": { - "@libp2p/crypto": "^5.1.8", - "@libp2p/interface": "^2.11.0", - "multiformats": "^13.3.6", - "uint8arrays": "^5.1.0" - } - }, - "@libp2p/peer-record": { - "version": "8.0.35", - "resolved": "https://registry.npmjs.org/@libp2p/peer-record/-/peer-record-8.0.35.tgz", - "integrity": "sha512-0818zvjKbucq5XBnusG8oSWxJ992rVry/2qlfcn/nyK/uDrZ12tjDYHNMCoOWTNeFvFUVkMg9pRkvXvTNp6Yiw==", - "requires": { - "@libp2p/crypto": "^5.1.8", - "@libp2p/interface": "^2.11.0", - "@libp2p/peer-id": "^5.1.9", - "@libp2p/utils": "^6.7.2", - "@multiformats/multiaddr": "^12.4.4", - "multiformats": "^13.3.6", - "protons-runtime": "^5.5.0", - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "@libp2p/peer-store": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/@libp2p/peer-store/-/peer-store-11.2.7.tgz", - "integrity": "sha512-dwTM+0i7mAgAnZvMHghgGcFoWPGaTbKx2nBueMd2Yg38mCs9WeambmR6gQdjwvYpybvNgFDAA+XesCKCotuczg==", - "requires": { - "@libp2p/crypto": "^5.1.8", - "@libp2p/interface": "^2.11.0", - "@libp2p/peer-collections": "^6.0.35", - "@libp2p/peer-id": "^5.1.9", - "@libp2p/peer-record": "^8.0.35", - "@multiformats/multiaddr": "^12.4.4", - "interface-datastore": "^8.3.1", - "it-all": "^3.0.8", - "main-event": "^1.0.1", - "mortice": "^3.2.1", - "multiformats": "^13.3.6", - "protons-runtime": "^5.5.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "@libp2p/ping": { - "version": "2.0.35", - "resolved": "https://registry.npmjs.org/@libp2p/ping/-/ping-2.0.35.tgz", - "integrity": "sha512-f9z5drqjaRRTu1dK/3gshtth/xdE2YwZ6qhBUpqLX4x5s3k/X8ds4aRx2lzvznQMmOMaT8e1VEjhbfFlcJmUOA==", - "requires": { - "@libp2p/crypto": "^5.1.6", - "@libp2p/interface": "^2.10.4", - "@libp2p/interface-internal": "^2.3.17", - "@multiformats/multiaddr": "^12.4.4", - "it-byte-stream": "^2.0.2", - "main-event": "^1.0.1", - "uint8arrays": "^5.1.0" - } - }, - "@libp2p/utils": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-6.7.2.tgz", - "integrity": "sha512-yglVPcYErb4al3MMTdedVLLsdUvr5KaqrrxohxTl/FXMFBvBs0o3w8lo29nfnTUpnNSHFhWZ9at0ZGNnpT/C/w==", - "requires": { - "@chainsafe/is-ip": "^2.1.0", - "@chainsafe/netmask": "^2.0.0", - "@libp2p/crypto": "^5.1.8", - "@libp2p/interface": "^2.11.0", - "@libp2p/logger": "^5.2.0", - "@multiformats/multiaddr": "^12.4.4", - "@sindresorhus/fnv1a": "^3.1.0", - "any-signal": "^4.1.1", - "delay": "^6.0.0", - "get-iterator": "^2.0.1", - "is-loopback-addr": "^2.0.2", - "is-plain-obj": "^4.1.0", - "it-foreach": "^2.1.3", - "it-pipe": "^3.0.1", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.2", - "main-event": "^1.0.1", - "netmask": "^2.0.2", - "p-defer": "^4.0.1", - "race-event": "^1.3.0", - "race-signal": "^1.1.3", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "@libp2p/websockets": { - "version": "9.2.16", - "resolved": "https://registry.npmjs.org/@libp2p/websockets/-/websockets-9.2.16.tgz", - "integrity": "sha512-jD96ClKeaZvTs+YyGJxOOQSWVe9e9SHmacJ9uE9dqWZjCbPICdCJnOj2pLg258WxVrc+/MRFKSHE/v5f2ZJGCA==", - "requires": { - "@libp2p/interface": "^2.10.4", - "@libp2p/utils": "^6.7.0", - "@multiformats/multiaddr": "^12.4.4", - "@multiformats/multiaddr-matcher": "^1.7.2", - "@multiformats/multiaddr-to-uri": "^11.0.0", - "@types/ws": "^8.18.1", - "it-ws": "^6.1.5", - "main-event": "^1.0.1", - "p-defer": "^4.0.1", - "p-event": "^6.0.1", - "progress-events": "^1.0.1", - "race-signal": "^1.1.3", - "ws": "^8.18.2" - } - }, - "@marijn/find-cluster-break": { - "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 - }, - "@multiformats/dns": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.10.tgz", - "integrity": "sha512-6X200ceQLns0b/CU0S/So16tGjB5eIXHJ1xvJMPoWaKFHWSgfpW2EhkWJrqap4U3+c37zcowVR0ToPXeYEL7Vw==", - "requires": { - "buffer": "^6.0.3", - "dns-packet": "^5.6.1", - "hashlru": "^2.3.0", - "p-queue": "^9.0.0", - "progress-events": "^1.0.0", - "uint8arrays": "^5.0.2" - } - }, - "@multiformats/mafmt": { - "version": "12.1.6", - "resolved": "https://registry.npmjs.org/@multiformats/mafmt/-/mafmt-12.1.6.tgz", - "integrity": "sha512-tlJRfL21X+AKn9b5i5VnaTD6bNttpSpcqwKVmDmSHLwxoz97fAHaepqFOk/l1fIu94nImIXneNbhsJx/RQNIww==", - "requires": { - "@multiformats/multiaddr": "^12.0.0" - } - }, - "@multiformats/multiaddr": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.5.1.tgz", - "integrity": "sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==", - "requires": { - "@chainsafe/is-ip": "^2.0.1", - "@chainsafe/netmask": "^2.0.0", - "@multiformats/dns": "^1.0.3", - "abort-error": "^1.0.1", - "multiformats": "^13.0.0", - "uint8-varint": "^2.0.1", - "uint8arrays": "^5.0.0" - } - }, - "@multiformats/multiaddr-matcher": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-matcher/-/multiaddr-matcher-1.8.0.tgz", - "integrity": "sha512-tR/HFhDucXjvwCef5lfXT7kikqR2ffUjliuYlg/RKYGPySVKVlvrDufz86cIuHNc+i/fNR16FWWgD/pMJ6RW4w==", - "requires": { - "@chainsafe/is-ip": "^2.0.1", - "@multiformats/multiaddr": "^12.0.0", - "multiformats": "^13.0.0" - } - }, - "@multiformats/multiaddr-to-uri": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-to-uri/-/multiaddr-to-uri-11.0.2.tgz", - "integrity": "sha512-SiLFD54zeOJ0qMgo9xv1Tl9O5YktDKAVDP4q4hL16mSq4O4sfFNagNADz8eAofxd6TfQUzGQ3TkRRG9IY2uHRg==", - "requires": { - "@multiformats/multiaddr": "^12.3.0" - } - }, - "@napi-rs/magic-string": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@napi-rs/magic-string/-/magic-string-0.3.4.tgz", - "integrity": "sha512-DEWl/B99RQsyMT3F9bvrXuhL01/eIQp/dtNSE3G1jQ4mTGRcP4iHWxoPZ577WrbjUinrNgvRA5+08g8fkPgimQ==", - "dev": true, - "requires": { - "@napi-rs/magic-string-android-arm-eabi": "0.3.4", - "@napi-rs/magic-string-android-arm64": "0.3.4", - "@napi-rs/magic-string-darwin-arm64": "0.3.4", - "@napi-rs/magic-string-darwin-x64": "0.3.4", - "@napi-rs/magic-string-freebsd-x64": "0.3.4", - "@napi-rs/magic-string-linux-arm-gnueabihf": "0.3.4", - "@napi-rs/magic-string-linux-arm64-gnu": "0.3.4", - "@napi-rs/magic-string-linux-arm64-musl": "0.3.4", - "@napi-rs/magic-string-linux-x64-gnu": "0.3.4", - "@napi-rs/magic-string-linux-x64-musl": "0.3.4", - "@napi-rs/magic-string-win32-arm64-msvc": "0.3.4", - "@napi-rs/magic-string-win32-ia32-msvc": "0.3.4", - "@napi-rs/magic-string-win32-x64-msvc": "0.3.4" - } - }, - "@napi-rs/magic-string-android-arm-eabi": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-android-arm-eabi/-/magic-string-android-arm-eabi-0.3.4.tgz", - "integrity": "sha512-sszAYxqtzzJ4FDerDNHcqL9NhqPhj8W4DNiOanXYy50mA5oojlRtaAFPiB5ZMrWDBM32v5Q30LrmxQ4eTtu2Dg==", - "dev": true, - "optional": true - }, - "@napi-rs/magic-string-android-arm64": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-android-arm64/-/magic-string-android-arm64-0.3.4.tgz", - "integrity": "sha512-jdQ6HuO0X5rkX4MauTcWR4HWdgjakTOmmzqXg8L26+jOHVVG1LZE+Su5qvV4bP8vMb2h+vPE+JsnwqSmWymu3Q==", - "dev": true, - "optional": true - }, - "@napi-rs/magic-string-darwin-arm64": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-darwin-arm64/-/magic-string-darwin-arm64-0.3.4.tgz", - "integrity": "sha512-6NmMtvURce9/oq09XBZmuIeI6lPLGtEJ2ZPO/QzL3nLZa6wygiCnO/sFACKYNg5/73ET5HMMTeuogE1JI+r2Lw==", - "dev": true, - "optional": true - }, - "@napi-rs/magic-string-darwin-x64": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-darwin-x64/-/magic-string-darwin-x64-0.3.4.tgz", - "integrity": "sha512-f9LmfMiUAKDOtl0meOuLYeVb6OERrgGzrTg1Tn3R3fTAShM2kxRbfAuPE9ljuXxIFzOv/uqRNLSl/LqCJwpREA==", - "dev": true, - "optional": true - }, - "@napi-rs/magic-string-freebsd-x64": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-freebsd-x64/-/magic-string-freebsd-x64-0.3.4.tgz", - "integrity": "sha512-rqduQ4odiDK4QdM45xHWRTU4wtFIfpp8g8QGpz+3qqg7ivldDqbbNOrBaf6Oeu77uuEvWggnkyuChotfKgJdJQ==", - "dev": true, - "optional": true - }, - "@napi-rs/magic-string-linux-arm-gnueabihf": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-linux-arm-gnueabihf/-/magic-string-linux-arm-gnueabihf-0.3.4.tgz", - "integrity": "sha512-pVaJEdEpiPqIfq3M4+yMAATS7Z9muDcWYn8H7GFH1ygh8GwgLgKfy/n/lG2M6zp18Mwd0x7E2E/qg9GgCyUzoQ==", - "dev": true, - "optional": true - }, - "@napi-rs/magic-string-linux-arm64-gnu": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-linux-arm64-gnu/-/magic-string-linux-arm64-gnu-0.3.4.tgz", - "integrity": "sha512-9FwoAih/0tzEZx0BjYYIxWkSRMjonIn91RFM3q3MBs/evmThXUYXUqLNa1PPIkK1JoksswtDi48qWWLt8nGflQ==", - "dev": true, - "optional": true - }, - "@napi-rs/magic-string-linux-arm64-musl": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-linux-arm64-musl/-/magic-string-linux-arm64-musl-0.3.4.tgz", - "integrity": "sha512-wCR7R+WPOcAKmVQc1s6h6HwfwW1vL9pM8BjUY9Ljkdb8wt1LmZEmV2Sgfc1SfbRQzbyl+pKeufP6adRRQVzYDA==", - "dev": true, - "optional": true - }, - "@napi-rs/magic-string-linux-x64-gnu": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-linux-x64-gnu/-/magic-string-linux-x64-gnu-0.3.4.tgz", - "integrity": "sha512-sbxFDpYnt5WFbxQ1xozwOvh5A7IftqSI0WnE9O7KsQIOi0ej2dvFbfOW4tmFkvH/YP8KJELo5AhP2+kEq1DpYA==", - "dev": true, - "optional": true - }, - "@napi-rs/magic-string-linux-x64-musl": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-linux-x64-musl/-/magic-string-linux-x64-musl-0.3.4.tgz", - "integrity": "sha512-jN4h/7e2Ul8v3UK5IZu38NXLMdzVWhY4uEDlnwuUAhwRh26wBQ1/pLD97Uy/Z3dFNBQPcsv60XS9fOM1YDNT6w==", - "dev": true, - "optional": true - }, - "@napi-rs/magic-string-win32-arm64-msvc": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-win32-arm64-msvc/-/magic-string-win32-arm64-msvc-0.3.4.tgz", - "integrity": "sha512-gMUyTRHLWpzX2ntJFCbW2Gnla9Y/WUmbkZuW5SBAo/Jo8QojHn76Y4PNgnoXdzcsV9b/45RBxurYKAfFg9WTyg==", - "dev": true, - "optional": true - }, - "@napi-rs/magic-string-win32-ia32-msvc": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-win32-ia32-msvc/-/magic-string-win32-ia32-msvc-0.3.4.tgz", - "integrity": "sha512-QIMauMOvEHgL00K9np/c9CT/CRtLOz3mRTQqcZ9XGzSoAMrpxH71KSpDJrKl7h7Ro6TZ+hJ0C3T+JVuTCZNv4A==", - "dev": true, - "optional": true - }, - "@napi-rs/magic-string-win32-x64-msvc": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@napi-rs/magic-string-win32-x64-msvc/-/magic-string-win32-x64-msvc-0.3.4.tgz", - "integrity": "sha512-V8FMSf828MzOI3P6/765MR7zHU6CUZqiyPhmAnwYoKFNxfv7oCviN/G6NcENeCdcYOvNgh5fYzaNLB96ndId5A==", - "dev": true, - "optional": true - }, - "@noble/ciphers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", - "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==" - }, - "@noble/curves": { - "version": "1.9.7", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", - "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", - "requires": { - "@noble/hashes": "1.8.0" - } - }, - "@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==" - }, - "@noble/secp256k1": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.2.tgz", - "integrity": "sha512-/qzwYl5eFLH8OWIecQWM31qld2g1NfjgylK+TNhqtaUKP37Nm+Y+z30Fjhw0Ct8p9yCQEm2N3W/AckdIb3SMcQ==" - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@oxc-parser/binding-darwin-arm64": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.8.0.tgz", - "integrity": "sha512-3Dws5Wzj9efojjqvhS4ZF+Abh0EoiI5ciOE2kdLifMzSg4fnmYAIOktoUnPEo87TNIb4SiFJ5JgPBgEyq42Eow==", - "dev": true, - "optional": true - }, - "@oxc-parser/binding-darwin-x64": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.8.0.tgz", - "integrity": "sha512-DAUJ/mfq0Jn2VDYn69bhHTsIWj+aZ/viamexFwaLL7ntkIFmGpzAJZUlWofpY1IRJynKWW+P5AOLYXMllw4qUw==", - "dev": true, - "optional": true - }, - "@oxc-parser/binding-linux-arm64-gnu": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.8.0.tgz", - "integrity": "sha512-ZHQVey/O4K3zTIKtpfsbtJIE8MPTRHRxgY3dejaoeFQGf9C3HasgF132Yp4zN/jOUx+x8czKPVa/Af40ViyhGQ==", - "dev": true, - "optional": true - }, - "@oxc-parser/binding-linux-arm64-musl": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.8.0.tgz", - "integrity": "sha512-Diw+Tnf5v+zAYXzDoSKCZsMaroU6GoqZMS7smfDtFnZYTHWZrsTmPBLUQe7AFiG7O7tkhsCdcWjOYgbVkrSVOA==", - "dev": true, - "optional": true - }, - "@oxc-parser/binding-linux-x64-gnu": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.8.0.tgz", - "integrity": "sha512-WloqcRrtQUVEP/Sy8ZeEgF0HgBKQjOv3zLFZqbC5ipkerKriGcVbsq3fOIMOi/55AM6/UhIAjeZGnoeco72JjQ==", - "dev": true, - "optional": true - }, - "@oxc-parser/binding-linux-x64-musl": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.8.0.tgz", - "integrity": "sha512-2j7BD9szwSXTvSj0Q8VE98UHGYvrgZzdLy4EyB0FilhQnopEfz+YV674rWGY2Il1VYxHJwGctrTJHvARolu37g==", - "dev": true, - "optional": true - }, - "@oxc-parser/binding-win32-arm64-msvc": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.8.0.tgz", - "integrity": "sha512-mcomr1og17yCmnwn8Q7CRzrH9Va0HccWe4Ld3/u/elBsw0SEzYGVvECRzCyRglYAbKTtusz7as9Jee0RiMOMmg==", - "dev": true, - "optional": true - }, - "@oxc-parser/binding-win32-x64-msvc": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.8.0.tgz", - "integrity": "sha512-nIBkc1KZOVYUaHT3+U+gM354P3byMAIXMvlmLMbs0kWVRcI4vrzL8qwWpC6QdBQxWKZGqPEqGolv8H4dDYA9nQ==", - "dev": true, - "optional": true - }, - "@parcel/source-map": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@parcel/source-map/-/source-map-2.1.1.tgz", - "integrity": "sha512-Ejx1P/mj+kMjQb8/y5XxDUn4reGdr+WyKYloBljpppUy8gs42T+BNoEOuRYqDVdgPc6NxduzIDoJS9pOFfV5Ew==", - "dev": true, - "requires": { - "detect-libc": "^1.0.3" - } - }, - "@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "optional": true - }, - "@polka/url": { - "version": "1.0.0-next.29", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "dev": true - }, - "@promptbook/utils": { - "version": "0.69.5", - "resolved": "https://registry.npmjs.org/@promptbook/utils/-/utils-0.69.5.tgz", - "integrity": "sha512-xm5Ti/Hp3o4xHrsK9Yy3MS6KbDxYbq485hDsFvxqaNA7equHLPdo8H8faTitTeb14QCDfLW4iwCxdVYu5sn6YQ==", - "dev": true, - "requires": { - "spacetrim": "0.11.59" - } - }, - "@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "requires": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, - "@puppeteer/browsers": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.0.tgz", - "integrity": "sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==", - "dev": true, - "requires": { - "debug": "^4.4.3", - "extract-zip": "^2.0.1", - "progress": "^2.0.3", - "proxy-agent": "^6.5.0", - "semver": "^7.7.4", - "tar-fs": "^3.1.1", - "yargs": "^17.7.2" - } - }, - "@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", - "dev": true, - "optional": true - }, - "@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", - "dev": true, - "optional": true - }, - "@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "dev": true, - "optional": true - }, - "@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", - "dev": true, - "optional": true - }, - "@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "dev": true, - "optional": true - }, - "@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true - }, - "@sindresorhus/fnv1a": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/fnv1a/-/fnv1a-3.1.0.tgz", - "integrity": "sha512-KV321z5m/0nuAg83W1dPLy85HpHDk7Sdi4fJbwvacWsEhAh+rZUW4ZfGcXmUIvjZg4ss2bcwNlRhJ7GBEUG08w==" - }, - "@smithy/abort-controller": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.10.tgz", - "integrity": "sha512-qocxM/X4XGATqQtUkbE9SPUB6wekBi+FyJOMbPj0AhvyvFGYEmOlz6VB22iMePCQsFmMIvFSeViDvA7mZJG47g==", - "requires": { - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@smithy/chunked-blob-reader": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.0.tgz", - "integrity": "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/chunked-blob-reader-native": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.1.tgz", - "integrity": "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==", - "requires": { - "@smithy/util-base64": "^4.3.0", - "tslib": "^2.6.2" - } - }, - "@smithy/config-resolver": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.9.tgz", - "integrity": "sha512-ejQvXqlcU30h7liR9fXtj7PIAau1t/sFbJpgWPfiYDs7zd16jpH0IsSXKcba2jF6ChTXvIjACs27kNMc5xxE2Q==", - "requires": { - "@smithy/node-config-provider": "^4.3.10", - "@smithy/types": "^4.13.0", - "@smithy/util-config-provider": "^4.2.1", - "@smithy/util-endpoints": "^3.3.1", - "@smithy/util-middleware": "^4.2.10", - "tslib": "^2.6.2" - } - }, - "@smithy/core": { - "version": "3.23.6", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.6.tgz", - "integrity": "sha512-4xE+0L2NrsFKpEVFlFELkIHQddBvMbQ41LRIP74dGCXnY1zQ9DgksrBcRBDJT+iOzGy4VEJIeU3hkUK5mn06kg==", - "requires": { - "@smithy/middleware-serde": "^4.2.11", - "@smithy/protocol-http": "^5.3.10", - "@smithy/types": "^4.13.0", - "@smithy/util-base64": "^4.3.1", - "@smithy/util-body-length-browser": "^4.2.1", - "@smithy/util-middleware": "^4.2.10", - "@smithy/util-stream": "^4.5.15", - "@smithy/util-utf8": "^4.2.1", - "@smithy/uuid": "^1.1.1", - "tslib": "^2.6.2" - } - }, - "@smithy/credential-provider-imds": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.10.tgz", - "integrity": "sha512-3bsMLJJLTZGZqVGGeBVFfLzuRulVsGTj12BzRKODTHqUABpIr0jMN1vN3+u6r2OfyhAQ2pXaMZWX/swBK5I6PQ==", - "requires": { - "@smithy/node-config-provider": "^4.3.10", - "@smithy/property-provider": "^4.2.10", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.10", - "tslib": "^2.6.2" - } - }, - "@smithy/eventstream-codec": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.8.tgz", - "integrity": "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw==", - "requires": { - "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.12.0", - "@smithy/util-hex-encoding": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/eventstream-serde-browser": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.8.tgz", - "integrity": "sha512-MTfQT/CRQz5g24ayXdjg53V0mhucZth4PESoA5IhvaWVDTOQLfo8qI9vzqHcPsdd2v6sqfTYqF5L/l+pea5Uyw==", - "requires": { - "@smithy/eventstream-serde-universal": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - } - }, - "@smithy/eventstream-serde-config-resolver": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.8.tgz", - "integrity": "sha512-ah12+luBiDGzBruhu3efNy1IlbwSEdNiw8fOZksoKoWW1ZHvO/04MQsdnws/9Aj+5b0YXSSN2JXKy/ClIsW8MQ==", - "requires": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - } - }, - "@smithy/eventstream-serde-node": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.8.tgz", - "integrity": "sha512-cYpCpp29z6EJHa5T9WL0KAlq3SOKUQkcgSoeRfRVwjGgSFl7Uh32eYGt7IDYCX20skiEdRffyDpvF2efEZPC0A==", - "requires": { - "@smithy/eventstream-serde-universal": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - } - }, - "@smithy/eventstream-serde-universal": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.8.tgz", - "integrity": "sha512-iJ6YNJd0bntJYnX6s52NC4WFYcZeKrPUr1Kmmr5AwZcwCSzVpS7oavAmxMR7pMq7V+D1G4s9F5NJK0xwOsKAlQ==", - "requires": { - "@smithy/eventstream-codec": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - } - }, - "@smithy/fetch-http-handler": { - "version": "5.3.11", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.11.tgz", - "integrity": "sha512-wbTRjOxdFuyEg0CpumjZO0hkUl+fetJFqxNROepuLIoijQh51aMBmzFLfoQdwRjxsuuS2jizzIUTjPWgd8pd7g==", - "requires": { - "@smithy/protocol-http": "^5.3.10", - "@smithy/querystring-builder": "^4.2.10", - "@smithy/types": "^4.13.0", - "@smithy/util-base64": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/hash-blob-browser": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.9.tgz", - "integrity": "sha512-m80d/iicI7DlBDxyQP6Th7BW/ejDGiF0bgI754+tiwK0lgMkcaIBgvwwVc7OFbY4eUzpGtnig52MhPAEJ7iNYg==", - "requires": { - "@smithy/chunked-blob-reader": "^5.2.0", - "@smithy/chunked-blob-reader-native": "^4.2.1", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - } - }, - "@smithy/hash-node": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.10.tgz", - "integrity": "sha512-1VzIOI5CcsvMDvP3iv1vG/RfLJVVVc67dCRyLSB2Hn9SWCZrDO3zvcIzj3BfEtqRW5kcMg5KAeVf1K3dR6nD3w==", - "requires": { - "@smithy/types": "^4.13.0", - "@smithy/util-buffer-from": "^4.2.1", - "@smithy/util-utf8": "^4.2.1", - "tslib": "^2.6.2" - } - }, - "@smithy/hash-stream-node": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.8.tgz", - "integrity": "sha512-v0FLTXgHrTeheYZFGhR+ehX5qUm4IQsjAiL9qehad2cyjMWcN2QG6/4mSwbSgEQzI7jwfoXj7z4fxZUx/Mhj2w==", - "requires": { - "@smithy/types": "^4.12.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/invalid-dependency": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.10.tgz", - "integrity": "sha512-vy9KPNSFUU0ajFYk0sDZIYiUlAWGEAhRfehIr5ZkdFrRFTAuXEPUd41USuqHU6vvLX4r6Q9X7MKBco5+Il0Org==", - "requires": { - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@smithy/is-array-buffer": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", - "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/md5-js": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.9.tgz", - "integrity": "sha512-ZCCWfGj4wvqV+5OS9e/GvR5jlR7j1mMB1UkGE+V7P1USFMwcL4Z4j5mO9nGvQGkfe20KM87ymbvZIcU9tHNlIg==", - "requires": { - "@smithy/types": "^4.12.1", - "@smithy/util-utf8": "^4.2.1", - "tslib": "^2.6.2" - } - }, - "@smithy/middleware-apply-body-checksum": { - "version": "4.3.9", - "resolved": "https://registry.npmjs.org/@smithy/middleware-apply-body-checksum/-/middleware-apply-body-checksum-4.3.9.tgz", - "integrity": "sha512-53wIgp1Jz5F886zUHTnVnXEUvDocPUs0icN5ja9oPIUWRXN9PpXOIQFUqEMQlU5zKMi+sfUz8yKnlsxYHkWjqw==", - "requires": { - "@smithy/is-array-buffer": "^4.2.1", - "@smithy/protocol-http": "^5.3.9", - "@smithy/types": "^4.12.1", - "tslib": "^2.6.2" - } - }, - "@smithy/middleware-content-length": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.10.tgz", - "integrity": "sha512-TQZ9kX5c6XbjhaEBpvhSvMEZ0klBs1CFtOdPFwATZSbC9UeQfKHPLPN9Y+I6wZGMOavlYTOlHEPDrt42PMSH9w==", - "requires": { - "@smithy/protocol-http": "^5.3.10", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@smithy/middleware-endpoint": { - "version": "4.4.20", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.20.tgz", - "integrity": "sha512-9W6Np4ceBP3XCYAGLoMCmn8t2RRVzuD1ndWPLBbv7H9CrwM9Bprf6Up6BM9ZA/3alodg0b7Kf6ftBK9R1N04vw==", - "requires": { - "@smithy/core": "^3.23.6", - "@smithy/middleware-serde": "^4.2.11", - "@smithy/node-config-provider": "^4.3.10", - "@smithy/shared-ini-file-loader": "^4.4.5", - "@smithy/types": "^4.13.0", - "@smithy/url-parser": "^4.2.10", - "@smithy/util-middleware": "^4.2.10", - "tslib": "^2.6.2" - } - }, - "@smithy/middleware-retry": { - "version": "4.4.37", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.37.tgz", - "integrity": "sha512-/1psZZllBBSQ7+qo5+hhLz7AEPGLx3Z0+e3ramMBEuPK2PfvLK4SrncDB9VegX5mBn+oP/UTDrM6IHrFjvX1ZA==", - "requires": { - "@smithy/node-config-provider": "^4.3.10", - "@smithy/protocol-http": "^5.3.10", - "@smithy/service-error-classification": "^4.2.10", - "@smithy/smithy-client": "^4.12.0", - "@smithy/types": "^4.13.0", - "@smithy/util-middleware": "^4.2.10", - "@smithy/util-retry": "^4.2.10", - "@smithy/uuid": "^1.1.1", - "tslib": "^2.6.2" - } - }, - "@smithy/middleware-serde": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.11.tgz", - "integrity": "sha512-STQdONGPwbbC7cusL60s7vOa6He6A9w2jWhoapL0mgVjmR19pr26slV+yoSP76SIssMTX/95e5nOZ6UQv6jolg==", - "requires": { - "@smithy/protocol-http": "^5.3.10", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@smithy/middleware-stack": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.10.tgz", - "integrity": "sha512-pmts/WovNcE/tlyHa8z/groPeOtqtEpp61q3W0nW1nDJuMq/x+hWa/OVQBtgU0tBqupeXq0VBOLA4UZwE8I0YA==", - "requires": { - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@smithy/node-config-provider": { - "version": "4.3.12", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.12.tgz", - "integrity": "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw==", - "requires": { - "@smithy/property-provider": "^4.2.12", - "@smithy/shared-ini-file-loader": "^4.4.7", - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" - } - }, - "@smithy/node-http-handler": { - "version": "4.4.12", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.12.tgz", - "integrity": "sha512-zo1+WKJkR9x7ZtMeMDAAsq2PufwiLDmkhcjpWPRRkmeIuOm6nq1qjFICSZbnjBvD09ei8KMo26BWxsu2BUU+5w==", - "requires": { - "@smithy/abort-controller": "^4.2.10", - "@smithy/protocol-http": "^5.3.10", - "@smithy/querystring-builder": "^4.2.10", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@smithy/property-provider": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.12.tgz", - "integrity": "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A==", - "requires": { - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" - } - }, - "@smithy/protocol-http": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.10.tgz", - "integrity": "sha512-2NzVWpYY0tRdfeCJLsgrR89KE3NTWT2wGulhNUxYlRmtRmPwLQwKzhrfVaiNlA9ZpJvbW7cjTVChYKgnkqXj1A==", - "requires": { - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@smithy/querystring-builder": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.10.tgz", - "integrity": "sha512-HeN7kEvuzO2DmAzLukE9UryiUvejD3tMp9a1D1NJETerIfKobBUCLfviP6QEk500166eD2IATaXM59qgUI+YDA==", - "requires": { - "@smithy/types": "^4.13.0", - "@smithy/util-uri-escape": "^4.2.1", - "tslib": "^2.6.2" - } - }, - "@smithy/querystring-parser": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.12.tgz", - "integrity": "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw==", - "requires": { - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" - } - }, - "@smithy/service-error-classification": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.10.tgz", - "integrity": "sha512-0R/+/Il5y8nB/By90o8hy/bWVYptbIfvoTYad0igYQO5RefhNCDmNzqxaMx7K1t/QWo0d6UynqpqN5cCQt1MCg==", - "requires": { - "@smithy/types": "^4.13.0" - } - }, - "@smithy/shared-ini-file-loader": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.7.tgz", - "integrity": "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw==", - "requires": { - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" - } - }, - "@smithy/signature-v4": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.10.tgz", - "integrity": "sha512-Wab3wW8468WqTKIxI+aZe3JYO52/RYT/8sDOdzkUhjnLakLe9qoQqIcfih/qxcF4qWEFoWBszY0mj5uxffaVXA==", - "requires": { - "@smithy/is-array-buffer": "^4.2.1", - "@smithy/protocol-http": "^5.3.10", - "@smithy/types": "^4.13.0", - "@smithy/util-hex-encoding": "^4.2.1", - "@smithy/util-middleware": "^4.2.10", - "@smithy/util-uri-escape": "^4.2.1", - "@smithy/util-utf8": "^4.2.1", - "tslib": "^2.6.2" - } - }, - "@smithy/smithy-client": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.0.tgz", - "integrity": "sha512-R8bQ9K3lCcXyZmBnQqUZJF4ChZmtWT5NLi6x5kgWx5D+/j0KorXcA0YcFg/X5TOgnTCy1tbKc6z2g2y4amFupQ==", - "requires": { - "@smithy/core": "^3.23.6", - "@smithy/middleware-endpoint": "^4.4.20", - "@smithy/middleware-stack": "^4.2.10", - "@smithy/protocol-http": "^5.3.10", - "@smithy/types": "^4.13.0", - "@smithy/util-stream": "^4.5.15", - "tslib": "^2.6.2" - } - }, - "@smithy/types": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", - "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/url-parser": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.12.tgz", - "integrity": "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA==", - "requires": { - "@smithy/querystring-parser": "^4.2.12", - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" - } - }, - "@smithy/util-base64": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.1.tgz", - "integrity": "sha512-BKGuawX4Doq/bI/uEmg+Zyc36rJKWuin3py89PquXBIBqmbnJwBBsmKhdHfNEp0+A4TDgLmT/3MSKZ1SxHcR6w==", - "requires": { - "@smithy/util-buffer-from": "^4.2.1", - "@smithy/util-utf8": "^4.2.1", - "tslib": "^2.6.2" - } - }, - "@smithy/util-body-length-browser": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.1.tgz", - "integrity": "sha512-SiJeLiozrAoCrgDBUgsVbmqHmMgg/2bA15AzcbcW+zan7SuyAVHN4xTSbq0GlebAIwlcaX32xacnrG488/J/6g==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-body-length-node": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.2.tgz", - "integrity": "sha512-4rHqBvxtJEBvsZcFQSPQqXP2b/yy/YlB66KlcEgcH2WNoOKCKB03DSLzXmOsXjbl8dJ4OEYTn31knhdznwk7zw==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-buffer-from": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", - "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", - "requires": { - "@smithy/is-array-buffer": "^4.2.2", - "tslib": "^2.6.2" - } - }, - "@smithy/util-config-provider": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.1.tgz", - "integrity": "sha512-462id/00U8JWFw6qBuTSWfN5TxOHvDu4WliI97qOIOnuC/g+NDAknTU8eoGXEPlLkRVgWEr03jJBLV4o2FL8+A==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-defaults-mode-browser": { - "version": "4.3.36", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.36.tgz", - "integrity": "sha512-R0smq7EHQXRVMxkAxtH5akJ/FvgAmNF6bUy/GwY/N20T4GrwjT633NFm0VuRpC+8Bbv8R9A0DoJ9OiZL/M3xew==", - "requires": { - "@smithy/property-provider": "^4.2.10", - "@smithy/smithy-client": "^4.12.0", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@smithy/util-defaults-mode-node": { - "version": "4.2.39", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.39.tgz", - "integrity": "sha512-otWuoDm35btJV1L8MyHrPl462B07QCdMTktKc7/yM+Psv6KbED/ziXiHnmr7yPHUjfIwE9S8Max0LO24Mo3ZVg==", - "requires": { - "@smithy/config-resolver": "^4.4.9", - "@smithy/credential-provider-imds": "^4.2.10", - "@smithy/node-config-provider": "^4.3.10", - "@smithy/property-provider": "^4.2.10", - "@smithy/smithy-client": "^4.12.0", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@smithy/util-endpoints": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.3.tgz", - "integrity": "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig==", - "requires": { - "@smithy/node-config-provider": "^4.3.12", - "@smithy/types": "^4.13.1", - "tslib": "^2.6.2" - } - }, - "@smithy/util-hex-encoding": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.1.tgz", - "integrity": "sha512-c1hHtkgAWmE35/50gmdKajgGAKV3ePJ7t6UtEmpfCWJmQE9BQAQPz0URUVI89eSkcDqCtzqllxzG28IQoZPvwA==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-middleware": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.10.tgz", - "integrity": "sha512-LxaQIWLp4y0r72eA8mwPNQ9va4h5KeLM0I3M/HV9klmFaY2kN766wf5vsTzmaOpNNb7GgXAd9a25P3h8T49PSA==", - "requires": { - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@smithy/util-retry": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.10.tgz", - "integrity": "sha512-HrBzistfpyE5uqTwiyLsFHscgnwB0kgv8vySp7q5kZ0Eltn/tjosaSGGDj/jJ9ys7pWzIP/icE2d+7vMKXLv7A==", - "requires": { - "@smithy/service-error-classification": "^4.2.10", - "@smithy/types": "^4.13.0", - "tslib": "^2.6.2" - } - }, - "@smithy/util-stream": { - "version": "4.5.15", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.15.tgz", - "integrity": "sha512-OlOKnaqnkU9X+6wEkd7mN+WB7orPbCVDauXOj22Q7VtiTkvy7ZdSsOg4QiNAZMgI4OkvNf+/VLUC3VXkxuWJZw==", - "requires": { - "@smithy/fetch-http-handler": "^5.3.11", - "@smithy/node-http-handler": "^4.4.12", - "@smithy/types": "^4.13.0", - "@smithy/util-base64": "^4.3.1", - "@smithy/util-buffer-from": "^4.2.1", - "@smithy/util-hex-encoding": "^4.2.1", - "@smithy/util-utf8": "^4.2.1", - "tslib": "^2.6.2" - } - }, - "@smithy/util-uri-escape": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.1.tgz", - "integrity": "sha512-YmiUDn2eo2IOiWYYvGQkgX5ZkBSiTQu4FlDo5jNPpAxng2t6Sjb6WutnZV9l6VR4eJul1ABmCrnWBC9hKHQa6Q==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-utf8": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", - "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", - "requires": { - "@smithy/util-buffer-from": "^4.2.2", - "tslib": "^2.6.2" - } - }, - "@smithy/util-waiter": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.8.tgz", - "integrity": "sha512-n+lahlMWk+aejGuax7DPWtqav8HYnWxQwR+LCG2BgCUmaGcTe9qZCFsmw8TMg9iG75HOwhrJCX9TCJRLH+Yzqg==", - "requires": { - "@smithy/abort-controller": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - } - }, - "@smithy/uuid": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.1.tgz", - "integrity": "sha512-dSfDCeihDmZlV2oyr0yWPTUfh07suS+R5OB+FZGiv/hHyK3hrFBW5rR1UYjfa57vBsrP9lciFkRPzebaV1Qujw==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true - }, - "@supabase/auth-js": { - "version": "2.77.0", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.77.0.tgz", - "integrity": "sha512-IRxyj2l46EutSX7AbGkHA7LSmrgqnXjPfKouBlGT6NqS1YOm+jMMwfdhf6zP5EZ4giUfdt2u+yHIBgyXU/LEtg==", - "requires": { - "@supabase/node-fetch": "2.6.15", - "tslib": "2.8.1" - } - }, - "@supabase/functions-js": { - "version": "2.77.0", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.77.0.tgz", - "integrity": "sha512-MxNW3YoQysbiVEiLAozCTqk1urswtVjAZggeZ5Sw+vJ+u1EvFNmTnirzwTj7M8XjTOMmorheruPmNtfHEwudvw==", - "requires": { - "@supabase/node-fetch": "2.6.15", - "tslib": "2.8.1" - } - }, - "@supabase/node-fetch": { - "version": "2.6.15", - "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", - "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "@supabase/postgrest-js": { - "version": "2.77.0", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.77.0.tgz", - "integrity": "sha512-Ly8C48x875JcUXBdML7SPRjO1Bpmjo6Sax/Tz4Ij6YU5paCrGKxEYDBVLP2eHKkQvf+LQ+GIbRFF1DorRvyfwQ==", - "requires": { - "@supabase/node-fetch": "2.6.15", - "tslib": "2.8.1" - } - }, - "@supabase/realtime-js": { - "version": "2.77.0", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.77.0.tgz", - "integrity": "sha512-Ycv2VZ8yTjvlR2NQecGJUlP0Dh4LhF1Y1oZ3IMQcjjTbDriWSQgfc9HSLIQUaY/eTdtfXfyVKOGE+tieWneV8Q==", - "requires": { - "@supabase/node-fetch": "2.6.15", - "@types/phoenix": "^1.6.6", - "@types/ws": "^8.18.1", - "tslib": "2.8.1", - "ws": "^8.18.2" - } - }, - "@supabase/storage-js": { - "version": "2.77.0", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.77.0.tgz", - "integrity": "sha512-4+OpVA4U0C4HM1QuINlgjqFxnRsmqPnuurTDN4m6nRanafuqQQ/UtMjdUU57iygBo70IGBlBefZ8gGNWzD1sLg==", - "requires": { - "@supabase/node-fetch": "2.6.15", - "tslib": "2.8.1" - } - }, - "@supabase/supabase-js": { - "version": "2.77.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.77.0.tgz", - "integrity": "sha512-s6OQ8RZ0ioQCwVDh2Tv502XaUQCuRbbjpujYJB1h0JWELRsqjLDsgB5kZUkETPgtTAjJk7z97YPUsRg80PohfA==", - "requires": { - "@supabase/auth-js": "2.77.0", - "@supabase/functions-js": "2.77.0", - "@supabase/node-fetch": "2.6.15", - "@supabase/postgrest-js": "2.77.0", - "@supabase/realtime-js": "2.77.0", - "@supabase/storage-js": "2.77.0" - } - }, - "@sveltejs/acorn-typescript": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", - "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", - "dev": true, - "requires": {} - }, - "@sveltejs/vite-plugin-svelte": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz", - "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", - "dev": true, - "peer": true, - "requires": { - "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", - "deepmerge": "^4.3.1", - "magic-string": "^0.30.21", - "obug": "^2.1.0", - "vitefu": "^1.1.1" - } - }, - "@sveltejs/vite-plugin-svelte-inspector": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.1.tgz", - "integrity": "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==", - "dev": true, - "requires": { - "debug": "^4.4.1" - } - }, - "@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "dev": true - }, - "@tsconfig/svelte": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.8.tgz", - "integrity": "sha512-UkNnw1/oFEfecR8ypyHIQuWYdkPvHiwcQ78sh+ymIiYoF+uc5H1UBetbjyqT+vgGJ3qQN6nhucJviX6HesWtKQ==", - "dev": true - }, - "@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "requires": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "@types/codemirror": { - "version": "5.60.8", - "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", - "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", - "dev": true, - "requires": { - "@types/tern": "*" - } - }, - "@types/debug": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", - "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", - "dev": true, - "requires": { - "@types/ms": "*" - } - }, - "@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true - }, - "@types/deno": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@types/deno/-/deno-2.5.0.tgz", - "integrity": "sha512-g8JS38vmc0S87jKsFzre+0ZyMOUDHPVokEJymSCRlL57h6f/FdKPWBXgdFh3Z8Ees9sz11qt9VWELU9Y9ZkiVw==", - "dev": true - }, - "@types/diff-match-patch": { - "version": "1.0.36", - "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", - "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==", - "dev": true - }, - "@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 - }, - "@types/fs-extra": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.5.tgz", - "integrity": "sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", - "dev": true, - "requires": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true - }, - "@types/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", - "dev": true - }, - "@types/lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==" - }, - "@types/lodash.debounce": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz", - "integrity": "sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==", - "requires": { - "@types/lodash": "*" - } - }, - "@types/markdown-it": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", - "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", - "dev": true, - "requires": { - "@types/linkify-it": "^5", - "@types/mdurl": "^2" - } - }, - "@types/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", - "dev": true - }, - "@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true - }, - "@types/ms": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", - "dev": true - }, - "@types/node": { - "version": "24.12.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", - "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", - "requires": { - "undici-types": "~7.16.0" - }, - "dependencies": { - "undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==" - } - } - }, - "@types/phoenix": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", - "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==" - }, - "@types/pouchdb": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/@types/pouchdb/-/pouchdb-6.4.2.tgz", - "integrity": "sha512-YsI47rASdtzR+3V3JE2UKY58snhm0AglHBpyckQBkRYoCbTvGagXHtV0x5n8nzN04jQmvTG+Sm85cIzKT3KXBA==", - "dev": true, - "requires": { - "@types/pouchdb-adapter-cordova-sqlite": "*", - "@types/pouchdb-adapter-fruitdown": "*", - "@types/pouchdb-adapter-http": "*", - "@types/pouchdb-adapter-idb": "*", - "@types/pouchdb-adapter-leveldb": "*", - "@types/pouchdb-adapter-localstorage": "*", - "@types/pouchdb-adapter-memory": "*", - "@types/pouchdb-adapter-node-websql": "*", - "@types/pouchdb-adapter-websql": "*", - "@types/pouchdb-browser": "*", - "@types/pouchdb-core": "*", - "@types/pouchdb-http": "*", - "@types/pouchdb-mapreduce": "*", - "@types/pouchdb-node": "*", - "@types/pouchdb-replication": "*" - } - }, - "@types/pouchdb-adapter-cordova-sqlite": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-cordova-sqlite/-/pouchdb-adapter-cordova-sqlite-1.0.1.tgz", - "integrity": "sha512-nqlXpW1ho3KBg1mUQvZgH2755y3z/rw4UA7ZJCPMRTHofxGMY8izRVw5rHBL4/7P615or0J2udpRYxgkT3D02g==", - "dev": true, - "requires": { - "@types/pouchdb-core": "*" - } - }, - "@types/pouchdb-adapter-fruitdown": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-fruitdown/-/pouchdb-adapter-fruitdown-6.1.3.tgz", - "integrity": "sha512-Wz1Z1JLOW1hgmFQjqnSkmyyfH7by/iWb4abKn684WMvQfmxx6BxKJpJ4+eulkVPQzzgMMSgU1MpnQOm9FgRkbw==", - "dev": true, - "requires": { - "@types/pouchdb-core": "*" - } - }, - "@types/pouchdb-adapter-http": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-http/-/pouchdb-adapter-http-6.1.6.tgz", - "integrity": "sha512-DJur1mt07GJXwGb5K+MOILoCOSgoQpsi7hybcTzRLeR3IO8Y8eq7TnhTkftAJdx9VHJGOiOXFjO+8BYM69j5yA==", - "dev": true, - "requires": { - "@types/pouchdb-core": "*" - } - }, - "@types/pouchdb-adapter-idb": { - "version": "6.1.7", - "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-idb/-/pouchdb-adapter-idb-6.1.7.tgz", - "integrity": "sha512-KwjkJ4fTNz5wPXYu20bUoWud7ty0t7tgdo4oc0AJvG+fcURAH7mI7uFmpE4dZIT+hUq5G61xu96AVq9b2q4T3g==", - "dev": true, - "requires": { - "@types/pouchdb-core": "*" - } - }, - "@types/pouchdb-adapter-leveldb": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-leveldb/-/pouchdb-adapter-leveldb-6.1.3.tgz", - "integrity": "sha512-ex8NFqQGFwEpFi7AaZ5YofmuemfZNsL3nTFZBUCAKYMBkazQij1pe2ILLStSvJr0XS0qxgXjCEW19T5Wqiiskg==", - "dev": true, - "requires": { - "@types/pouchdb-core": "*" - } - }, - "@types/pouchdb-adapter-localstorage": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-localstorage/-/pouchdb-adapter-localstorage-6.1.3.tgz", - "integrity": "sha512-oor040tye1KKiGLWYtIy7rRT7C2yoyX3Tf6elEJRpjOA7Ja/H8lKc4LaSh9ATbptIcES6MRqZDxtp7ly9hsW3Q==", - "dev": true, - "requires": { - "@types/pouchdb-core": "*" - } - }, - "@types/pouchdb-adapter-memory": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-memory/-/pouchdb-adapter-memory-6.1.3.tgz", - "integrity": "sha512-gVbsIMzDzgZYThFVT4eVNsmuZwVm/4jDxP1sjlgc3qtDIxbtBhGgyNfcskwwz9Zu5Lv1avkDsIWvcxQhnvRlHg==", - "dev": true, - "requires": { - "@types/pouchdb-core": "*" - } - }, - "@types/pouchdb-adapter-node-websql": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-node-websql/-/pouchdb-adapter-node-websql-6.1.3.tgz", - "integrity": "sha512-F/P+os6Jsa7CgHtH64+Z0HfwIcj0hIRB5z8gNhF7L7dxPWoAfkopK5H2gydrP3sQrlGyN4WInF+UJW/Zu1+FKg==", - "dev": true, - "requires": { - "@types/pouchdb-adapter-websql": "*", - "@types/pouchdb-core": "*" - } - }, - "@types/pouchdb-adapter-websql": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-websql/-/pouchdb-adapter-websql-6.1.4.tgz", - "integrity": "sha512-zMJQCtXC40hBsIDRn0GhmpeGMK0f9l/OGWfLguvczROzxxcOD7REI+e6SEmX7gJKw5JuMvlfuHzkQwjmvSJbtg==", - "dev": true, - "requires": { - "@types/pouchdb-core": "*" - } - }, - "@types/pouchdb-browser": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/@types/pouchdb-browser/-/pouchdb-browser-6.1.5.tgz", - "integrity": "sha512-f+HjxEjYFpgoYWXnMI9AQZZ+SIG8dBiBPrpfWWGsCl+48rumsP5BuBWHq/aXoB8SRKYO0XdP4TNvMBWM3UATCw==", - "dev": true, - "requires": { - "@types/pouchdb-adapter-http": "*", - "@types/pouchdb-adapter-idb": "*", - "@types/pouchdb-adapter-websql": "*", - "@types/pouchdb-core": "*", - "@types/pouchdb-mapreduce": "*", - "@types/pouchdb-replication": "*" - } - }, - "@types/pouchdb-core": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/pouchdb-core/-/pouchdb-core-7.0.15.tgz", - "integrity": "sha512-gq1Qbqn9nCaAKRRv6fRHZ4/ER+QYEwSXBZlDQcxwdbPrtZO8EhIn2Bct0AlguaSEdFcABfbaxxyQwFINkNQ9dQ==", - "dev": true, - "requires": { - "@types/debug": "*", - "@types/pouchdb-find": "*" - } - }, - "@types/pouchdb-find": { - "version": "6.3.7", - "resolved": "https://registry.npmjs.org/@types/pouchdb-find/-/pouchdb-find-6.3.7.tgz", - "integrity": "sha512-b2dr9xoZRK5Mwl8UiRA9l5j9mmCxNfqXuu63H1KZHwJLILjoIIz7BntCvM0hnlnl7Q8P8wORq0IskuaMq5Nnnw==", - "dev": true, - "requires": { - "@types/pouchdb-core": "*" - } - }, - "@types/pouchdb-http": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@types/pouchdb-http/-/pouchdb-http-6.1.3.tgz", - "integrity": "sha512-0e9E5SqNOyPl/3FnEIbENssB4FlJsNYuOy131nxrZk36S+y1R/6qO7ZVRypWpGTqBWSuVd7gCsq2UDwO/285+w==", - "dev": true, - "requires": { - "@types/pouchdb-adapter-http": "*", - "@types/pouchdb-core": "*" - } - }, - "@types/pouchdb-mapreduce": { - "version": "6.1.10", - "resolved": "https://registry.npmjs.org/@types/pouchdb-mapreduce/-/pouchdb-mapreduce-6.1.10.tgz", - "integrity": "sha512-AgYVqCnaA5D7cWkWyzZVuk0137N4yZsmIQTD/i3DmuMxYYoFrtWUoQu0tbA52SpTRGdL8ubQ7JFQXzA13fA6IQ==", - "dev": true, - "requires": { - "@types/pouchdb-core": "*" - } - }, - "@types/pouchdb-node": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@types/pouchdb-node/-/pouchdb-node-6.1.4.tgz", - "integrity": "sha512-wnTCH8X1JOPpNOfVhz8HW0AvmdHh6pt40MuRj0jQnK7QEHsHS79WujsKTKSOF8QXtPwpvCNSsI7ut7H7tfxxJQ==", - "dev": true, - "requires": { - "@types/pouchdb-adapter-http": "*", - "@types/pouchdb-adapter-leveldb": "*", - "@types/pouchdb-core": "*", - "@types/pouchdb-mapreduce": "*", - "@types/pouchdb-replication": "*" - } - }, - "@types/pouchdb-replication": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/@types/pouchdb-replication/-/pouchdb-replication-6.4.7.tgz", - "integrity": "sha512-slB4zOwri3SAVHioFx/FWC/KqOzzb7nDFtV+qzaKzxkf+U5zTwCbK3uRHaj0d/XQk0DwVeajf1ni3Wiyq3j2OA==", - "dev": true, - "requires": { - "@types/pouchdb-core": "*", - "@types/pouchdb-find": "*" - } - }, - "@types/readable-stream": { - "version": "4.0.22", - "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.22.tgz", - "integrity": "sha512-/FFhJpfCLAPwAcN3mFycNUa77ddnr8jTgF5VmSNetaemWB2cIlfCA9t0YTM3JAT0wOcv8D4tjPo7pkDhK3EJIg==", - "requires": { - "@types/node": "*" - } - }, - "@types/retry": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", - "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==" - }, - "@types/sinonjs__fake-timers": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", - "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", - "dev": true - }, - "@types/tern": { - "version": "0.23.4", - "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz", - "integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==", - "dev": true, - "requires": { - "@types/estree": "*" - } - }, - "@types/transform-pouch": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/transform-pouch/-/transform-pouch-1.0.6.tgz", - "integrity": "sha512-aG0K0Qvd9apKPmewg6OGOQvzVGK9EYVJKdIY+TvctVynLsSnL4X7aLYpH3bKZ364ICarR/x67aRRaUiWnjnQPg==", - "dev": true, - "requires": { - "@types/pouchdb-core": "*" - } - }, - "@types/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", - "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", - "dev": true - }, - "@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "requires": { - "@types/node": "*" - } - }, - "@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "optional": true, - "requires": { - "@types/node": "*" - } - }, - "@typescript-eslint/eslint-plugin": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", - "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", - "dev": true, - "requires": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/type-utils": "8.56.1", - "@typescript-eslint/utils": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" - }, - "dependencies": { - "ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true - } - } - }, - "@typescript-eslint/parser": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", - "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", - "dev": true, - "peer": true, - "requires": { - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", - "debug": "^4.4.3" - } - }, - "@typescript-eslint/project-service": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", - "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", - "dev": true, - "requires": { - "@typescript-eslint/tsconfig-utils": "^8.56.1", - "@typescript-eslint/types": "^8.56.1", - "debug": "^4.4.3" - } - }, - "@typescript-eslint/scope-manager": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", - "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", - "dev": true, - "requires": { - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1" - } - }, - "@typescript-eslint/tsconfig-utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", - "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", - "dev": true, - "requires": {} - }, - "@typescript-eslint/type-utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", - "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/utils": "8.56.1", - "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" - } - }, - "@typescript-eslint/types": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", - "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", - "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", - "dev": true, - "requires": { - "@typescript-eslint/project-service": "8.56.1", - "@typescript-eslint/tsconfig-utils": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" - } - }, - "@typescript-eslint/utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", - "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", - "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "8.56.1", - "eslint-visitor-keys": "^5.0.0" - }, - "dependencies": { - "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 - } - } - }, - "@vitest/browser": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.0.16.tgz", - "integrity": "sha512-t4toy8X/YTnjYEPoY0pbDBg3EvDPg1elCDrfc+VupPHwoN/5/FNQ8Z+xBYIaEnOE2vVEyKwqYBzZ9h9rJtZVcg==", - "dev": true, - "peer": true, - "requires": { - "@vitest/mocker": "4.0.16", - "@vitest/utils": "4.0.16", - "magic-string": "^0.30.21", - "pixelmatch": "7.1.0", - "pngjs": "^7.0.0", - "sirv": "^3.0.2", - "tinyrainbow": "^3.0.3", - "ws": "^8.18.3" - } - }, - "@vitest/browser-playwright": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.0.16.tgz", - "integrity": "sha512-I2Fy/ANdphi1yI46d15o0M1M4M0UJrUiVKkH5oKeRZZCdPg0fw/cfTKZzv9Ge9eobtJYp4BGblMzXdXH0vcl5g==", - "dev": true, - "peer": true, - "requires": { - "@vitest/browser": "4.0.16", - "@vitest/mocker": "4.0.16", - "tinyrainbow": "^3.0.3" - } - }, - "@vitest/coverage-v8": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.16.tgz", - "integrity": "sha512-2rNdjEIsPRzsdu6/9Eq0AYAzYdpP6Bx9cje9tL3FE5XzXRQF1fNU9pe/1yE8fCrS0HD+fBtt6gLPh6LI57tX7A==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.0.16", - "ast-v8-to-istanbul": "^0.3.8", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.6", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.1", - "obug": "^2.1.1", - "std-env": "^3.10.0", - "tinyrainbow": "^3.0.3" - } - }, - "@vitest/expect": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.16.tgz", - "integrity": "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==", - "dev": true, - "requires": { - "@standard-schema/spec": "^1.0.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.16", - "@vitest/utils": "4.0.16", - "chai": "^6.2.1", - "tinyrainbow": "^3.0.3" - }, - "dependencies": { - "chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true - } - } - }, - "@vitest/mocker": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", - "integrity": "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==", - "dev": true, - "requires": { - "@vitest/spy": "4.0.16", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - } - }, - "@vitest/pretty-format": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.16.tgz", - "integrity": "sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==", - "dev": true, - "requires": { - "tinyrainbow": "^3.0.3" - } - }, - "@vitest/runner": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.16.tgz", - "integrity": "sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==", - "dev": true, - "requires": { - "@vitest/utils": "4.0.16", - "pathe": "^2.0.3" - } - }, - "@vitest/snapshot": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.16.tgz", - "integrity": "sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==", - "dev": true, - "requires": { - "@vitest/pretty-format": "4.0.16", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - } - }, - "@vitest/spy": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.16.tgz", - "integrity": "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==", - "dev": true - }, - "@vitest/utils": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.16.tgz", - "integrity": "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==", - "dev": true, - "requires": { - "@vitest/pretty-format": "4.0.16", - "tinyrainbow": "^3.0.3" - } - }, - "@waku/core": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.39.tgz", - "integrity": "sha512-Vgb52md4GOzM5z9xfULzjN2tvVHKszFmj5zc2mVDoIgySH4cFBgDTHtVtGEwrFRFWadWYKBtpKBdmG3X+W7SNA==", - "requires": { - "@libp2p/ping": "2.0.35", - "@noble/hashes": "^1.3.2", - "@waku/enr": "^0.0.33", - "@waku/interfaces": "0.0.34", - "@waku/proto": "0.0.14", - "@waku/utils": "0.0.27", - "debug": "^4.3.4", - "it-all": "^3.0.4", - "it-length-prefixed": "^9.0.4", - "it-pipe": "^3.0.1", - "uint8arraylist": "^2.4.3", - "uuid": "^9.0.0" - }, - "dependencies": { - "it-length-prefixed": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/it-length-prefixed/-/it-length-prefixed-9.1.1.tgz", - "integrity": "sha512-O88nBweT6M9ozsmok68/auKH7ik/slNM4pYbM9lrfy2z5QnpokW5SlrepHZDKtN71llhG2sZvd6uY4SAl+lAQg==", - "requires": { - "it-reader": "^6.0.1", - "it-stream-types": "^2.0.1", - "uint8-varint": "^2.0.1", - "uint8arraylist": "^2.0.0", - "uint8arrays": "^5.0.1" - } - }, - "uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" - } - } - }, - "@waku/discovery": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/@waku/discovery/-/discovery-0.0.12.tgz", - "integrity": "sha512-4ItzLMQA79xveu5I9ymx3Q1A/Aj0fGjdi8TnPnb9xnm2O927w71efnkxmHo3rwATEYonXiB34NpMN2ecAp1enA==", - "requires": { - "@waku/core": "0.0.39", - "@waku/enr": "0.0.33", - "@waku/interfaces": "0.0.34", - "@waku/proto": "^0.0.14", - "@waku/utils": "0.0.27", - "debug": "^4.3.4", - "dns-over-http-resolver": "^3.0.8", - "hi-base32": "^0.5.1", - "uint8arrays": "^5.0.1" - } - }, - "@waku/enr": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@waku/enr/-/enr-0.0.33.tgz", - "integrity": "sha512-OVZoCXc9Lto2tUfo+iSqQ61pmRm/QikYSJWc3InKmsL3qtfpMShiChK/X/PafwdRFVA28b46itm++KUqMjGi+A==", - "requires": { - "@ethersproject/rlp": "^5.7.0", - "@libp2p/crypto": "5.1.6", - "@libp2p/peer-id": "5.1.7", - "@multiformats/multiaddr": "^12.0.0", - "@noble/secp256k1": "^1.7.1", - "@waku/utils": "0.0.27", - "debug": "^4.3.4", - "js-sha3": "^0.9.2" - }, - "dependencies": { - "@libp2p/crypto": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-5.1.6.tgz", - "integrity": "sha512-hCNDInAsjfFTOr1ZlVTVuRKpkGEbR1GC+cDbmn2Vslwd0dHZHqhKv5ye7l6NZaiNUxxqUCVmqvJIWqVLuTPDdg==", - "requires": { - "@libp2p/interface": "^2.10.4", - "@noble/curves": "^1.9.1", - "@noble/hashes": "^1.8.0", - "multiformats": "^13.3.6", - "protons-runtime": "^5.5.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "@libp2p/peer-id": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-5.1.7.tgz", - "integrity": "sha512-KBT8Edx/Sqxj0vKe5mPM2PQx06VDmGzx2BZ1M+LiDAM94q9Sag4tyaUugHyTrJKGG8V+7lx1Fz46kfbezuwR9g==", - "requires": { - "@libp2p/crypto": "^5.1.6", - "@libp2p/interface": "^2.10.4", - "multiformats": "^13.3.6", - "uint8arrays": "^5.1.0" - } - } - } - }, - "@waku/interfaces": { - "version": "0.0.34", - "resolved": "https://registry.npmjs.org/@waku/interfaces/-/interfaces-0.0.34.tgz", - "integrity": "sha512-15+SOfr8cKk5J2ukSucy/T6j23jIudRt1hr/N09YaNUvQ19iXofjne5MU/P8otmgP8daedCijCagRB0rwoHKKQ==" - }, - "@waku/proto": { - "version": "0.0.14", - "resolved": "https://registry.npmjs.org/@waku/proto/-/proto-0.0.14.tgz", - "integrity": "sha512-8zKVHrKzzKQfZBVnpSmJ6G8H1Zd4Gqms1tj3L6K2WCE/NQDR8wJtFwziab3dJ/5rKUTjfPAWFJ57RN97ltzxGA==", - "requires": { - "protons-runtime": "^5.4.0" - } - }, - "@waku/sdk": { - "version": "0.0.35", - "resolved": "https://registry.npmjs.org/@waku/sdk/-/sdk-0.0.35.tgz", - "integrity": "sha512-bnXl5b8BDCOKSrJ7V6PiJshc3bsBWaGweWy20IsGlXalCJj7257wK31lZxEP62lTFKHS8tPfG+EvDwrPK4oxKA==", - "requires": { - "@chainsafe/libp2p-noise": "16.1.3", - "@libp2p/bootstrap": "11.0.42", - "@libp2p/identify": "3.0.36", - "@libp2p/mplex": "11.0.42", - "@libp2p/ping": "2.0.35", - "@libp2p/websockets": "9.2.16", - "@noble/hashes": "^1.3.3", - "@types/lodash.debounce": "^4.0.9", - "@waku/core": "0.0.39", - "@waku/discovery": "0.0.12", - "@waku/interfaces": "0.0.34", - "@waku/proto": "^0.0.14", - "@waku/sds": "^0.0.7", - "@waku/utils": "0.0.27", - "libp2p": "2.8.11", - "lodash.debounce": "^4.0.8" - } - }, - "@waku/sds": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@waku/sds/-/sds-0.0.7.tgz", - "integrity": "sha512-wPZASJ1iH9K5gSgHMvmahRdnD/yNrNj/35R4H0SZGhUaViOyUGMooJH0YewZmVc1Dvy2L9mDitHurJWqLoWbcg==", - "requires": { - "@libp2p/interface": "2.10.4", - "@noble/hashes": "^1.7.1", - "@waku/proto": "^0.0.14", - "@waku/utils": "^0.0.27", - "chai": "^5.1.2", - "lodash": "^4.17.21" - }, - "dependencies": { - "@libp2p/interface": { - "version": "2.10.4", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.10.4.tgz", - "integrity": "sha512-FX3uujZgjH9bb7mDSNR54j3JzJnF/ngnQH20GQ1wPk5irIeHDvmzRlUj3bJ3hHQmdB2MxLZNT6e39O1es10LFA==", - "requires": { - "@multiformats/multiaddr": "^12.4.4", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.2", - "main-event": "^1.0.1", - "multiformats": "^13.3.6", - "progress-events": "^1.0.1", - "uint8arraylist": "^2.4.8" - } - } - } - }, - "@waku/utils": { - "version": "0.0.27", - "resolved": "https://registry.npmjs.org/@waku/utils/-/utils-0.0.27.tgz", - "integrity": "sha512-kIS/EN9Xoc5ik2c4MweqcvV3NEcl+CDmg09jpVUVG7fB2/yxVRakBlROytGn+vALR4pcHom1tW2dW1vtofCfFw==", - "requires": { - "@noble/hashes": "^1.3.2", - "@waku/interfaces": "0.0.34", - "chai": "^4.3.10", - "debug": "^4.3.4", - "uint8arrays": "^5.0.1" - }, - "dependencies": { - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" - }, - "chai": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", - "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.1.0" - } - }, - "check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "requires": { - "get-func-name": "^2.0.2" - } - }, - "deep-eql": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", - "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", - "requires": { - "type-detect": "^4.0.0" - } - }, - "loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", - "requires": { - "get-func-name": "^2.0.1" - } - }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==" - } - } - }, - "@wdio/config": { - "version": "9.25.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.25.0.tgz", - "integrity": "sha512-EWa7l1rrbSNthCRDpdBw7ESAa1/jAjSsWCGkaVAO0HMOGlQjzvYI6gNi4KUeymnurDZ2IPr0jr+f9We6AWi6QA==", - "dev": true, - "requires": { - "@wdio/logger": "9.18.0", - "@wdio/types": "9.25.0", - "@wdio/utils": "9.25.0", - "deepmerge-ts": "^7.0.3", - "glob": "^10.2.2", - "import-meta-resolve": "^4.0.0", - "jiti": "^2.6.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "dev": true, - "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - } - }, - "minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.2" - } - }, - "path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "requires": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - } - } - } - }, - "@wdio/logger": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.18.0.tgz", - "integrity": "sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==", - "dev": true, - "requires": { - "chalk": "^5.1.2", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "safe-regex2": "^5.0.0", - "strip-ansi": "^7.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true - }, - "chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true - }, - "strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "requires": { - "ansi-regex": "^6.2.2" - } - } - } - }, - "@wdio/protocols": { - "version": "9.25.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.25.0.tgz", - "integrity": "sha512-PErbZqdpFmE69bRuku3OR34Ro2xuZNNLXYFOcJnjXJVzf5+ApDyGHYrMlvhtrrSy9/55LUybk851ppjS+3RoDA==", - "dev": true - }, - "@wdio/repl": { - "version": "9.16.2", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-9.16.2.tgz", - "integrity": "sha512-FLTF0VL6+o5BSTCO7yLSXocm3kUnu31zYwzdsz4n9s5YWt83sCtzGZlZpt7TaTzb3jVUfxuHNQDTb8UMkCu0lQ==", - "dev": true, - "requires": { - "@types/node": "^20.1.0" - }, - "dependencies": { - "@types/node": { - "version": "20.19.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", - "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", - "dev": true, - "requires": { - "undici-types": "~6.21.0" - } - } - } - }, - "@wdio/types": { - "version": "9.25.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.25.0.tgz", - "integrity": "sha512-ovSEcUBLz6gVDIsBZYKQXz8EGU37jS8sqbmlOe5+jB4XbsTBCyTLjQK/rO7LWQAKJcs0vBq+Pd+VrlsFtA7tTQ==", - "dev": true, - "requires": { - "@types/node": "^20.1.0" - }, - "dependencies": { - "@types/node": { - "version": "20.19.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", - "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", - "dev": true, - "requires": { - "undici-types": "~6.21.0" - } - } - } - }, - "@wdio/utils": { - "version": "9.25.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.25.0.tgz", - "integrity": "sha512-w/ej8gZkc2tZr8L91ATyA1AWrbPDYDOvblQ7r+zt1uPRobuA4H98GME7Zm7i3FIP695BvV4G35Gcs5NssZW1pw==", - "dev": true, - "requires": { - "@puppeteer/browsers": "^2.2.0", - "@wdio/logger": "9.18.0", - "@wdio/types": "9.25.0", - "decamelize": "^6.0.0", - "deepmerge-ts": "^7.0.3", - "edgedriver": "^6.1.2", - "geckodriver": "^6.1.0", - "get-port": "^7.0.0", - "import-meta-resolve": "^4.0.0", - "locate-app": "^2.2.24", - "mitt": "^3.0.1", - "safaridriver": "^1.0.0", - "split2": "^4.2.0", - "wait-port": "^1.1.0" - } - }, - "@zip.js/zip.js": { - "version": "2.8.23", - "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.23.tgz", - "integrity": "sha512-RB+RLnxPJFPrGvQ9rgO+4JOcsob6lD32OcF0QE0yg24oeW9q8KnTTNlugcDaIveEcCbclobJcZP+fLQ++sH0bw==", - "dev": true - }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "abort-error": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/abort-error/-/abort-error-1.0.1.tgz", - "integrity": "sha512-fxqCblJiIPdSXIUrxI0PL+eJG49QdP9SQ70qtB65MVAoMr2rASlOyAbJFOylfB467F/f+5BCLJJq58RYi7mGfg==" - }, - "abstract-leveldown": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.7.2.tgz", - "integrity": "sha512-+OVvxH2rHVEhWLdbudP6p0+dNMXu8JA1CbhP19T8paTYAcX7oJ4OVjT+ZUVpv7mITxXHqDMej+GdqXBmXkw09w==", - "dev": true, - "requires": { - "xtend": "~4.0.0" - } - }, - "acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, - "peer": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true - }, - "ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "any-signal": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/any-signal/-/any-signal-4.1.1.tgz", - "integrity": "sha512-iADenERppdC+A2YKbOXXB2WUeABLaM6qnpZ70kZbPZ1cZMMJ7eF+3CaYm+/PhBizgkzlvssC7QuHS30oOiQYWA==" - }, - "archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", - "dev": true, - "requires": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - } - }, - "archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", - "dev": true, - "requires": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "dev": true, - "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - } - }, - "minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.2" - } - }, - "path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "requires": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - } - } - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "dev": true - }, - "array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - } - }, - "array-includes": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", - "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", - "get-intrinsic": "^1.3.0", - "is-string": "^1.1.1", - "math-intrinsics": "^1.1.0" - } - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "array.prototype.findlastindex": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", - "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-shim-unscopables": "^1.1.0" - } - }, - "array.prototype.flat": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", - "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - } - }, - "array.prototype.flatmap": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", - "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - } - }, - "arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "dev": true, - "requires": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - } - }, - "assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==" - }, - "ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "dev": true, - "requires": { - "tslib": "^2.0.1" - } - }, - "ast-v8-to-istanbul": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz", - "integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.31", - "estree-walker": "^3.0.3", - "js-tokens": "^9.0.1" - }, - "dependencies": { - "js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true - } - } - }, - "async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true - }, - "async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "dev": true - }, - "available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "requires": { - "possible-typed-array-names": "^1.0.0" - } - }, - "axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true - }, - "b4a": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", - "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", - "dev": true, - "requires": {} - }, - "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 - }, - "bare-events": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", - "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", - "dev": true, - "requires": {} - }, - "bare-fs": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.5.tgz", - "integrity": "sha512-XvwYM6VZqKoqDll8BmSww5luA5eflDzY0uEFfBJtFKe4PAAtxBjU3YIxzIBzhyaEQBy1VXEQBto4cpN5RZJw+w==", - "dev": true, - "requires": { - "bare-events": "^2.5.4", - "bare-path": "^3.0.0", - "bare-stream": "^2.6.4", - "bare-url": "^2.2.2", - "fast-fifo": "^1.3.2" - } - }, - "bare-os": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.0.tgz", - "integrity": "sha512-Dc9/SlwfxkXIGYhvMQNUtKaXCaGkZYGcd1vuNUUADVqzu4/vQfvnMkYYOUnt2VwQ2AqKr/8qAVFRtwETljgeFg==", - "dev": true - }, - "bare-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", - "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", - "dev": true, - "requires": { - "bare-os": "^3.0.1" - } - }, - "bare-stream": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.8.1.tgz", - "integrity": "sha512-bSeR8RfvbRwDpD7HWZvn8M3uYNDrk7m9DQjYOFkENZlXW8Ju/MPaqUPQq5LqJ3kyjEm07siTaAQ7wBKCU59oHg==", - "dev": true, - "requires": { - "streamx": "^2.21.0", - "teex": "^1.0.1" - } - }, - "bare-url": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", - "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", - "dev": true, - "requires": { - "bare-path": "^3.0.0" - } - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "baseline-browser-mapping": { - "version": "2.10.8", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz", - "integrity": "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==", - "dev": true - }, - "basic-ftp": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz", - "integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==", - "dev": true - }, - "bl": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.6.tgz", - "integrity": "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==", - "requires": { - "@types/readable-stream": "^4.0.0", - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^4.2.0" - } - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, - "bowser": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", - "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==" - }, - "brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "requires": { - "fill-range": "^7.1.1" - } - }, - "broker-factory": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.10.tgz", - "integrity": "sha512-BzqK5GYFhvVFvO13uzPN0SCiOsOQuhMUbsGvTXDJMA2/N4GvIlFdxEuueE+60Zk841bBU5G3+fl2cqYEo0wgGg==", - "requires": { - "@babel/runtime": "^7.28.4", - "fast-unique-numbers": "^9.0.24", - "tslib": "^2.8.1", - "worker-factory": "^7.0.46" - } - }, - "browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "peer": true, - "requires": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - } - }, - "buffer": { + "node_modules/zip-stream/node_modules/buffer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "requires": { + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, - "buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", - "dev": true - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "builtin-modules": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-5.0.0.tgz", - "integrity": "sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==", - "dev": true - }, - "call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, - "requires": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - } - }, - "call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - } - }, - "call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "requires": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001780", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", - "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", - "dev": true - }, - "catering": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", - "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==" - }, - "chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", - "requires": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "check-error": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", - "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==" - }, - "cheerio": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz", - "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==", - "dev": true, - "requires": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "encoding-sniffer": "^0.2.1", - "htmlparser2": "^10.0.0", - "parse5": "^7.3.0", - "parse5-htmlparser2-tree-adapter": "^7.1.0", - "parse5-parser-stream": "^7.1.2", - "undici": "^7.12.0", - "whatwg-mimetype": "^4.0.0" - } - }, - "cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "dev": true, - "requires": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" - } - }, - "chokidar": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", - "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", - "dev": true, - "requires": { - "readdirp": "^4.0.1" - } - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, - "cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", - "dev": true - }, - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, - "clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "dev": true - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "dev": true - }, - "commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==" - }, - "commist": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/commist/-/commist-3.2.0.tgz", - "integrity": "sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==" - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true - }, - "compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", - "dev": true, - "requires": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", - "dev": true - }, - "crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", - "dev": true, - "requires": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - } - }, - "crelt": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", - "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", - "dev": true, - "requires": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - } - }, - "css-shorthand-properties": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.2.tgz", - "integrity": "sha512-C2AugXIpRGQTxaCW0N7n5jD/p5irUmCrwl03TrnMFBHDbdq44CFWR2zO7rK9xPN4Eo3pUxC4vQzQgbIpzrD1PQ==", - "dev": true - }, - "css-value": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz", - "integrity": "sha512-FUV3xaJ63buRLgHrLQVlVgQnQdR4yqdLGaDu7g8CQcWjInDfM9plBTPI9FRfpahju1UBSaMckeb2/46ApS/V1Q==", - "dev": true - }, - "css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", - "dev": true - }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true - }, - "data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "dev": true - }, - "data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - } - }, - "data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - } - }, - "data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - } - }, - "datastore-core": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/datastore-core/-/datastore-core-10.0.4.tgz", - "integrity": "sha512-IctgCO0GA7GHG7aRm3JRruibCsfvN4EXNnNIlLCZMKIv0TPkdAL5UFV3/xTYFYrrZ1jRNrXZNZRvfcVf/R+rAw==", - "requires": { - "@libp2p/logger": "^5.1.18", - "interface-datastore": "^8.0.0", - "interface-store": "^6.0.0", - "it-drain": "^3.0.9", - "it-filter": "^3.1.3", - "it-map": "^3.1.3", - "it-merge": "^3.0.11", - "it-pipe": "^3.0.1", - "it-sort": "^3.0.8", - "it-take": "^3.0.8" - } - }, - "debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "requires": { - "ms": "^2.1.3" - } - }, - "decamelize": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.1.tgz", - "integrity": "sha512-G7Cqgaelq68XHJNGlZ7lrNQyhZGsFqpwtGFexqUv4IQdjKoSYF7ipZ9UuTJZUSQXFj/XaoBLuEVIVqr8EJngEQ==", - "dev": true - }, - "decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "requires": { - "mimic-response": "^3.1.0" - } - }, - "deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==" - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true - }, - "deepmerge-ts": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", - "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", - "dev": true - }, - "deferred-leveldown": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz", - "integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==", - "requires": { - "abstract-leveldown": "~6.2.1", - "inherits": "^2.0.3" - }, - "dependencies": { - "abstract-leveldown": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", - "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", - "requires": { - "buffer": "^5.5.0", - "immediate": "^3.2.3", - "level-concat-iterator": "~2.0.0", - "level-supports": "~1.0.0", - "xtend": "~4.0.0" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - } - } - }, - "define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - } - }, - "define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "requires": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "dev": true, - "requires": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - } - }, - "delay": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/delay/-/delay-6.0.0.tgz", - "integrity": "sha512-2NJozoOHQ4NuZuVIr5CWd0iiLVIRSDepakaovIN+9eIDHEhdCAEvSy2cuf1DCrPPQLvHmbqTHODlhHg8UCy4zw==" - }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "dev": true - }, - "diff-match-patch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", - "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "dns-over-http-resolver": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/dns-over-http-resolver/-/dns-over-http-resolver-3.0.16.tgz", - "integrity": "sha512-Qnq8HhNRuMnA61pf1lVPlStCAv1BVrraCx0umPESWgYKf995tUMF5oNhW59PKdnf7E8d5yqwHlEoFywXjsNMCw==", - "requires": { - "quick-lru": "^7.0.0", - "weald": "^1.0.2" - } - }, - "dns-packet": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", - "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", - "requires": { - "@leichtgewicht/ip-codec": "^2.0.1" - } - }, - "dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "requires": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - } - }, - "domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true - }, - "domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "requires": { - "domelementtype": "^2.3.0" - } - }, - "domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "dev": true, - "requires": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - } - }, - "dotenv": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", - "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", - "dev": true - }, - "dotenv-cli": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-11.0.0.tgz", - "integrity": "sha512-r5pA8idbk7GFWuHEU7trSTflWcdBpQEK+Aw17UrSHjS6CReuhrrPcyC3zcQBPQvhArRHnBo/h6eLH1fkCvNlww==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.6", - "dotenv": "^17.1.0", - "dotenv-expand": "^12.0.0", - "minimist": "^1.2.6" - } - }, - "dotenv-expand": { - "version": "12.0.3", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", - "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", - "dev": true, - "requires": { - "dotenv": "^16.4.5" - }, - "dependencies": { - "dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "dev": true - } - } - }, - "double-ended-queue": { - "version": "2.1.0-0", - "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", - "integrity": "sha512-+BNfZ+deCo8hMNpDqDnvT+c0XpJ5cUa6mqYq89bho2Ifze4URTqRkcwR399hWoTrTkbZ/XJYDgP6rc7pRgffEQ==" - }, - "dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "requires": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - } - }, - "eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "edge-paths": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", - "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", - "dev": true, - "requires": { - "@types/which": "^2.0.1", - "which": "^2.0.2" - } - }, - "edgedriver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-6.3.0.tgz", - "integrity": "sha512-ggEQL+oEyIcM4nP2QC3AtCQ04o4kDNefRM3hja0odvlPSnsaxiruMxEZ93v3gDCKWYW6BXUr51PPradb+3nffw==", - "dev": true, - "requires": { - "@wdio/logger": "^9.18.0", - "@zip.js/zip.js": "^2.8.11", - "decamelize": "^6.0.1", - "edge-paths": "^3.0.5", - "fast-xml-parser": "^5.3.3", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.6", - "which": "^6.0.0" - }, - "dependencies": { - "isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", - "dev": true - }, - "which": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", - "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", - "dev": true, - "requires": { - "isexe": "^4.0.0" - } - } - } - }, - "electron-to-chromium": { - "version": "1.5.313", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", - "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "encoding-down": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", - "integrity": "sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==", - "requires": { - "abstract-leveldown": "^6.2.1", - "inherits": "^2.0.3", - "level-codec": "^9.0.0", - "level-errors": "^2.0.0" - }, - "dependencies": { - "abstract-leveldown": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.3.0.tgz", - "integrity": "sha512-TU5nlYgta8YrBMNpc9FwQzRbiXsj49gsALsXadbGHt9CROPzX5fB0rWDR5mtdpOOKa5XqRFpbj1QroPAoPzVjQ==", - "requires": { - "buffer": "^5.5.0", - "immediate": "^3.2.3", - "level-concat-iterator": "~2.0.0", - "level-supports": "~1.0.0", - "xtend": "~4.0.0" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - } - } - }, - "encoding-sniffer": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", - "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", - "dev": true, - "requires": { - "iconv-lite": "^0.6.3", - "whatwg-encoding": "^3.1.1" - } - }, - "end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "requires": { - "once": "^1.4.0" - } - }, - "end-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/end-stream/-/end-stream-0.1.0.tgz", - "integrity": "sha512-Brl10T8kYnc75IepKizW6Y9liyW8ikz1B7n/xoHrJxoVSSjoqPn30sb7XVFfQERK4QfUMYRGs9dhWwtt2eu6uA==", - "requires": { - "write-stream": "~0.4.3" - } - }, - "entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" - }, - "errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "requires": { - "prr": "~1.0.1" - } - }, - "es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", - "dev": true, - "requires": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.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", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" - } - }, - "es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true - }, - "es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true - }, - "es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true - }, - "es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "requires": { - "es-errors": "^1.3.0" - } - }, - "es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - } - }, - "es-shim-unscopables": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", - "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", - "dev": true, - "requires": { - "hasown": "^2.0.2" - } - }, - "es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "dev": true, - "requires": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - } - }, - "esbuild": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", - "dev": true, - "peer": true, - "requires": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" - } - }, - "esbuild-plugin-inline-worker": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/esbuild-plugin-inline-worker/-/esbuild-plugin-inline-worker-0.1.1.tgz", - "integrity": "sha512-VmFqsQKxUlbM51C1y5bRiMeyc1x2yTdMXhKB6S//++g9aCBg8TfGsbKxl5ZDkCGquqLY+RmEk93TBNd0i35dPA==", - "dev": true, - "requires": { - "esbuild": "latest", - "find-cache-dir": "^3.3.1" - } - }, - "esbuild-svelte": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.9.4.tgz", - "integrity": "sha512-v/a0GjkKN06nal2QLluxjk2GXsei3fdtjIuHRa6pVnri5rQBZ6pj4a2WwjLfRojgRsLwDHl4xSeZ1BeUHsqQrw==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.19" - } - }, - "escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "source-map": "~0.6.1" - } - }, - "eslint": { - "version": "9.39.3", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", - "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", - "dev": true, - "peer": true, - "requires": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.3", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true - }, - "minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "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", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "dev": true, - "requires": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-module-utils": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", - "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", - "dev": true, - "requires": { - "debug": "^3.2.7" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-plugin-import": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", - "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", - "dev": true, - "requires": { - "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.9", - "array.prototype.findlastindex": "^1.2.6", - "array.prototype.flat": "^1.3.3", - "array.prototype.flatmap": "^1.3.3", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.1", - "hasown": "^2.0.2", - "is-core-module": "^2.16.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "object.groupby": "^1.0.3", - "object.values": "^1.2.1", - "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.9", - "tsconfig-paths": "^3.15.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "eslint-plugin-svelte": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.15.0.tgz", - "integrity": "sha512-QKB7zqfuB8aChOfBTComgDptMf2yxiJx7FE04nneCmtQzgTHvY8UJkuh8J2Rz7KB9FFV9aTHX6r7rdYGvG8T9Q==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.6.1", - "@jridgewell/sourcemap-codec": "^1.5.0", - "esutils": "^2.0.3", - "globals": "^16.0.0", - "known-css-properties": "^0.37.0", - "postcss": "^8.4.49", - "postcss-load-config": "^3.1.4", - "postcss-safe-parser": "^7.0.0", - "semver": "^7.6.3", - "svelte-eslint-parser": "^1.4.0" - }, - "dependencies": { - "globals": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", - "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", - "dev": true - }, - "lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true - }, - "postcss-load-config": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", - "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", - "dev": true, - "requires": { - "lilconfig": "^2.0.5", - "yaml": "^1.10.2" - } - }, - "yaml": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", - "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", - "dev": true - } - } - }, - "eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true - }, - "esm-env": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "dev": true - }, - "espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "requires": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - } - }, - "esrap": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz", - "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==", - "dev": true, - "requires": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "requires": { - "@types/estree": "^1.0.0" - } - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "event-iterator": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/event-iterator/-/event-iterator-2.0.0.tgz", - "integrity": "sha512-KGft0ldl31BZVV//jj+IAIGCxkvvUkkON+ScH6zfoX+l+omX6001ggyRSpI0Io2Hlro0ThXotswCtfzS8UkIiQ==" - }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" - }, - "eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" - }, - "events-universal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", - "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", - "dev": true, - "requires": { - "bare-events": "^2.7.0" - } - }, - "expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" - }, - "expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true - }, - "extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dev": true - }, - "fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fast-unique-numbers": { - "version": "9.0.24", - "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.24.tgz", - "integrity": "sha512-Dv0BYn4waOWse94j16rsZ5w/0zoaCa74O3q6IZjMqaXbtT92Q+Sb6pPk+phGzD8Xh+nueQmSRI3tSCaHKidzKw==", - "requires": { - "@babel/runtime": "^7.28.4", - "tslib": "^2.8.1" - } - }, - "fast-xml-builder": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.3.tgz", - "integrity": "sha512-1o60KoFw2+LWKQu3IdcfcFlGTW4dpqEWmjhYec6H82AYZU2TVBXep6tMl8Z1Y+wM+ZrzCwe3BZ9Vyd9N2rIvmg==", - "requires": { - "path-expression-matcher": "^1.1.3" - } - }, - "fast-xml-parser": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", - "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", - "requires": { - "fast-xml-builder": "^1.0.0", - "strnum": "^2.1.2" - } - }, - "fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "requires": { - "websocket-driver": ">=0.5.1" - } - }, - "fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "requires": { - "pend": "~1.2.0" - } - }, - "fetch-cookie": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-2.2.0.tgz", - "integrity": "sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==", - "requires": { - "set-cookie-parser": "^2.4.8", - "tough-cookie": "^4.0.0" - } - }, - "fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==" - }, - "file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "requires": { - "flat-cache": "^4.0.0" - } - }, - "fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "firebase": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/firebase/-/firebase-12.4.0.tgz", - "integrity": "sha512-/chNgDQ6ppPPGOQO4jctxOa/5JeQxuhaxA7Y90K0I+n/wPfoO8mRveedhVUdo7ExLcWUivnnow/ouSLYSI5Icw==", - "requires": { - "@firebase/ai": "2.4.0", - "@firebase/analytics": "0.10.19", - "@firebase/analytics-compat": "0.2.25", - "@firebase/app": "0.14.4", - "@firebase/app-check": "0.11.0", - "@firebase/app-check-compat": "0.4.0", - "@firebase/app-compat": "0.5.4", - "@firebase/app-types": "0.9.3", - "@firebase/auth": "1.11.0", - "@firebase/auth-compat": "0.6.0", - "@firebase/data-connect": "0.3.11", - "@firebase/database": "1.1.0", - "@firebase/database-compat": "2.1.0", - "@firebase/firestore": "4.9.2", - "@firebase/firestore-compat": "0.4.2", - "@firebase/functions": "0.13.1", - "@firebase/functions-compat": "0.4.1", - "@firebase/installations": "0.6.19", - "@firebase/installations-compat": "0.2.19", - "@firebase/messaging": "0.12.23", - "@firebase/messaging-compat": "0.2.23", - "@firebase/performance": "0.7.9", - "@firebase/performance-compat": "0.2.22", - "@firebase/remote-config": "0.7.0", - "@firebase/remote-config-compat": "0.2.20", - "@firebase/storage": "0.14.0", - "@firebase/storage-compat": "0.4.0", - "@firebase/util": "1.13.0" - } - }, - "flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "requires": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - } - }, - "flatted": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", - "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", - "dev": true - }, - "for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, - "requires": { - "is-callable": "^1.2.7" - } - }, - "foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - } - }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "dependencies": { - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - } - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true - }, - "function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - } - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true - }, - "geckodriver": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-6.1.0.tgz", - "integrity": "sha512-ZRXLa4ZaYTTgUO4Eefw+RsQCleugU2QLb1ME7qTYxxuRj51yAhfnXaItXNs5/vUzfIaDHuZ+YnSF005hfp07nQ==", - "dev": true, - "requires": { - "@wdio/logger": "^9.18.0", - "@zip.js/zip.js": "^2.8.11", - "decamelize": "^6.0.1", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.6", - "modern-tar": "^0.7.2" - } - }, - "generator-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", - "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - }, - "get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==" - }, - "get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "requires": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - } - }, - "get-iterator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/get-iterator/-/get-iterator-2.0.1.tgz", - "integrity": "sha512-7HuY/hebu4gryTDT7O/XY/fvY9wRByEGdK6QOa4of8npTcv0+NS6frFKABcf6S9EBAsveTuKTsZQQBFMMNILIg==" - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-port": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", - "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==", - "dev": true - }, - "get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "requires": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - } - }, - "get-tsconfig": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", - "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", - "dev": true, - "requires": { - "resolve-pkg-maps": "^1.0.0" - } - }, - "get-uri": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", - "dev": true, - "requires": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - } - }, - "github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" - }, - "glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "dev": true, - "requires": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true - }, - "globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "requires": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - } - }, - "globby": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", - "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", - "dev": true, - "requires": { - "@types/glob": "^7.1.1", - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.0.3", - "glob": "^7.1.3", - "ignore": "^5.1.1", - "merge2": "^1.2.3", - "slash": "^3.0.0" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "requires": { - "es-define-property": "^1.0.0" - } - }, - "has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "dev": true, - "requires": { - "dunder-proto": "^1.0.0" - } - }, - "has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true - }, - "has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.3" - } - }, - "hashlru": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz", - "integrity": "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==" - }, - "hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "requires": { - "function-bind": "^1.1.2" - } - }, - "help-me": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", - "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" - }, - "hi-base32": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/hi-base32/-/hi-base32-0.5.1.tgz", - "integrity": "sha512-EmBBpvdYh/4XxsnUybsPag6VikPYnN30td+vQk+GI3qpahVEG9+gTkG0aXVxTjBqQ5T6ijbWIu77O+C5WFWsnA==" - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "htmlfy": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/htmlfy/-/htmlfy-0.8.1.tgz", - "integrity": "sha512-xWROBw9+MEGwxpotll0h672KCaLrKKiCYzsyN8ZgL9cQbVumFnyvsk2JqiB9ELAV1GLj1GG/jxZUjV9OZZi/yQ==", - "dev": true - }, - "htmlparser2": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", - "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", - "dev": true, - "requires": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.1", - "entities": "^6.0.0" - }, - "dependencies": { - "entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true - } - } - }, - "http-parser-js": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", - "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==" - }, - "http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "requires": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - } - }, - "https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "requires": { - "agent-base": "^7.1.2", - "debug": "4" - } - }, - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - }, - "idb": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.3.tgz", - "integrity": "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==" - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true - }, - "immediate": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", - "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==" - }, - "import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-meta-resolve": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", - "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "interface-datastore": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-8.3.2.tgz", - "integrity": "sha512-R3NLts7pRbJKc3qFdQf+u40hK8XWc0w4Qkx3OFEstC80VoaDUABY/dXA2EJPhtNC+bsrf41Ehvqb6+pnIclyRA==", - "requires": { - "interface-store": "^6.0.0", - "uint8arrays": "^5.1.0" - } - }, - "interface-store": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-6.0.3.tgz", - "integrity": "sha512-+WvfEZnFUhRwFxgz+QCQi7UC6o9AM0EHM9bpIe2Nhqb100NHCsTvNAn4eJgvgV2/tmLo1MP9nGxQKEcZTAueLA==" - }, - "internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - } - }, - "ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==" - }, - "is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - } - }, - "is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "dev": true, - "requires": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - } - }, - "is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "dev": true, - "requires": { - "has-bigints": "^1.0.2" - } - }, - "is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - } - }, - "is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" - }, - "is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true - }, - "is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "requires": { - "hasown": "^2.0.2" - } - }, - "is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - } - }, - "is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - } - }, - "is-electron": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", - "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==" - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, - "requires": { - "call-bound": "^1.0.3" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "is-generator-function": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", - "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", - "dev": true, - "requires": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - } - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-loopback-addr": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-loopback-addr/-/is-loopback-addr-2.0.2.tgz", - "integrity": "sha512-26POf2KRCno/KTNL5Q0b/9TYnL00xEsSaLfiFRmjM7m7Lw7ZMmFybzzuX4CcsLAluZGd+niLUiMRxEooVE3aqg==" - }, - "is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true - }, - "is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true - }, - "is-network-error": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", - "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==" - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - } - }, - "is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==" - }, - "is-plain-object": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", - "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", - "dev": true - }, - "is-reference": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "dev": true, - "requires": { - "@types/estree": "^1.0.6" - } - }, - "is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - } - }, - "is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true - }, - "is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "dev": true, - "requires": { - "call-bound": "^1.0.3" - } - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - } - }, - "is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - } - }, - "is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, - "requires": { - "which-typed-array": "^1.1.16" - } - }, - "is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true - }, - "is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "dev": true, - "requires": { - "call-bound": "^1.0.3" - } - }, - "is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - } - }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "requires": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - } - }, - "istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "requires": { - "semver": "^7.5.3" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - } - }, - "istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "it-all": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/it-all/-/it-all-3.0.9.tgz", - "integrity": "sha512-fz1oJJ36ciGnu2LntAlE6SA97bFZpW7Rnt0uEc1yazzR2nKokZLr8lIRtgnpex4NsmaBcvHF+Z9krljWFy/mmg==" - }, - "it-byte-stream": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/it-byte-stream/-/it-byte-stream-2.0.3.tgz", - "integrity": "sha512-h7FFcn4DWiWsJw1dCJhuPdiY8cGi1z8g4aLAfFspTaJbwQxvEMlEBFG/f8lIVGwM8YK26ClM4/9lxLVhF33b8g==", - "requires": { - "abort-error": "^1.0.1", - "it-queueless-pushable": "^2.0.0", - "it-stream-types": "^2.0.2", - "race-signal": "^1.1.3", - "uint8arraylist": "^2.4.8" - } - }, - "it-drain": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/it-drain/-/it-drain-3.0.10.tgz", - "integrity": "sha512-0w/bXzudlyKIyD1+rl0xUKTI7k4cshcS43LTlBiGFxI8K1eyLydNPxGcsVLsFVtKh1/ieS8AnVWt6KwmozxyEA==" - }, - "it-filter": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/it-filter/-/it-filter-3.1.4.tgz", - "integrity": "sha512-80kWEKgiFEa4fEYD3mwf2uygo1dTQ5Y5midKtL89iXyjinruA/sNXl6iFkTcdNedydjvIsFhWLiqRPQP4fAwWQ==", - "requires": { - "it-peekable": "^3.0.0" - } - }, - "it-foreach": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/it-foreach/-/it-foreach-2.1.4.tgz", - "integrity": "sha512-gFntBbNLpVK9uDmaHusugICD8/Pp+OCqbF5q1Z8K+B8WaG20YgMePWbMxI1I25+JmNWWr3hk0ecKyiI9pOLgeA==", - "requires": { - "it-peekable": "^3.0.0" - } - }, - "it-length-prefixed": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/it-length-prefixed/-/it-length-prefixed-10.0.1.tgz", - "integrity": "sha512-BhyluvGps26u9a7eQIpOI1YN7mFgi8lFwmiPi07whewbBARKAG9LE09Odc8s1Wtbt2MB6rNUrl7j9vvfXTJwdQ==", - "requires": { - "it-reader": "^6.0.1", - "it-stream-types": "^2.0.1", - "uint8-varint": "^2.0.1", - "uint8arraylist": "^2.0.0", - "uint8arrays": "^5.0.1" - } - }, - "it-length-prefixed-stream": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/it-length-prefixed-stream/-/it-length-prefixed-stream-2.0.3.tgz", - "integrity": "sha512-Ns3jNFy2mcFnV59llCYitJnFHapg8wIcOsWkEaAwOkG9v4HBCk24nze/zGDQjiJdDTyFXTT5GOY3M/uaksot3w==", - "requires": { - "abort-error": "^1.0.1", - "it-byte-stream": "^2.0.0", - "it-stream-types": "^2.0.2", - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8" - } - }, - "it-map": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/it-map/-/it-map-3.1.4.tgz", - "integrity": "sha512-QB9PYQdE9fUfpVFYfSxBIyvKynUCgblb143c+ktTK6ZuKSKkp7iH58uYFzagqcJ5HcqIfn1xbfaralHWam+3fg==", - "requires": { - "it-peekable": "^3.0.0" - } - }, - "it-merge": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/it-merge/-/it-merge-3.0.12.tgz", - "integrity": "sha512-nnnFSUxKlkZVZD7c0jYw6rDxCcAQYcMsFj27thf7KkDhpj0EA0g9KHPxbFzHuDoc6US2EPS/MtplkNj8sbCx4Q==", - "requires": { - "it-queueless-pushable": "^2.0.0" - } - }, - "it-pair": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/it-pair/-/it-pair-2.0.6.tgz", - "integrity": "sha512-5M0t5RAcYEQYNG5BV7d7cqbdwbCAp5yLdzvkxsZmkuZsLbTdZzah6MQySYfaAQjNDCq6PUnDt0hqBZ4NwMfW6g==", - "requires": { - "it-stream-types": "^2.0.1", - "p-defer": "^4.0.0" - } - }, - "it-parallel": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/it-parallel/-/it-parallel-3.0.13.tgz", - "integrity": "sha512-85PPJ/O8q97Vj9wmDTSBBXEkattwfQGruXitIzrh0RLPso6RHfiVqkuTqBNufYYtB1x6PSkh0cwvjmMIkFEPHA==", - "requires": { - "p-defer": "^4.0.1" - } - }, - "it-peekable": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/it-peekable/-/it-peekable-3.0.8.tgz", - "integrity": "sha512-7IDBQKSp/dtBxXV3Fj0v3qM1jftJ9y9XrWLRIuU1X6RdKqWiN60syNwP0fiDxZD97b8SYM58dD3uklIk1TTQAw==" - }, - "it-pipe": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/it-pipe/-/it-pipe-3.0.1.tgz", - "integrity": "sha512-sIoNrQl1qSRg2seYSBH/3QxWhJFn9PKYvOf/bHdtCBF0bnghey44VyASsWzn5dAx0DCDDABq1hZIuzKmtBZmKA==", - "requires": { - "it-merge": "^3.0.0", - "it-pushable": "^3.1.2", - "it-stream-types": "^2.0.1" - } - }, - "it-protobuf-stream": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/it-protobuf-stream/-/it-protobuf-stream-2.0.3.tgz", - "integrity": "sha512-Dus9qyylOSnC7l75/3qs6j3Fe9MCM2K5luXi9o175DYijFRne5FPucdOGIYdwaDBDQ4Oy34dNCuFobOpcusvEQ==", - "requires": { - "abort-error": "^1.0.1", - "it-length-prefixed-stream": "^2.0.0", - "it-stream-types": "^2.0.2", - "uint8arraylist": "^2.4.8" - } - }, - "it-pushable": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/it-pushable/-/it-pushable-3.2.3.tgz", - "integrity": "sha512-gzYnXYK8Y5t5b/BnJUr7glfQLO4U5vyb05gPx/TyTw+4Bv1zM9gFk4YsOrnulWefMewlphCjKkakFvj1y99Tcg==", - "requires": { - "p-defer": "^4.0.0" - } - }, - "it-queue": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/it-queue/-/it-queue-1.1.0.tgz", - "integrity": "sha512-aK9unJRIaJc9qiv53LByhF7/I2AuD7Ro4oLfLieVLL9QXNvRx++ANMpv8yCp2UO0KAtBuf70GOxSYb6ElFVRpQ==", - "requires": { - "abort-error": "^1.0.1", - "it-pushable": "^3.2.3", - "main-event": "^1.0.0", - "race-event": "^1.3.0", - "race-signal": "^1.1.3" - } - }, - "it-queueless-pushable": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/it-queueless-pushable/-/it-queueless-pushable-2.0.2.tgz", - "integrity": "sha512-2BqIt7XvDdgEgudLAdJkdseAwbVSBc0yAd8yPVHrll4eBuJPWIj9+8C3OIxzEKwhswLtd3bi+yLrzgw9gCyxMA==", - "requires": { - "abort-error": "^1.0.1", - "p-defer": "^4.0.1", - "race-signal": "^1.1.3" - } - }, - "it-reader": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/it-reader/-/it-reader-6.0.4.tgz", - "integrity": "sha512-XCWifEcNFFjjBHtor4Sfaj8rcpt+FkY0L6WdhD578SCDhV4VUm7fCkF3dv5a+fTcfQqvN9BsxBTvWbYO6iCjTg==", - "requires": { - "it-stream-types": "^2.0.1", - "uint8arraylist": "^2.0.0" - } - }, - "it-sort": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/it-sort/-/it-sort-3.0.9.tgz", - "integrity": "sha512-jsM6alGaPiQbcAJdzMsuMh00uJcI+kD9TBoScB8TR75zUFOmHvhSsPi+Dmh2zfVkcoca+14EbfeIZZXTUGH63w==", - "requires": { - "it-all": "^3.0.0" - } - }, - "it-stream-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/it-stream-types/-/it-stream-types-2.0.2.tgz", - "integrity": "sha512-Rz/DEZ6Byn/r9+/SBCuJhpPATDF9D+dz5pbgSUyBsCDtza6wtNATrz/jz1gDyNanC3XdLboriHnOC925bZRBww==" - }, - "it-take": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/it-take/-/it-take-3.0.9.tgz", - "integrity": "sha512-XMeUbnjOcgrhFXPUqa7H0VIjYSV/BvyxxjCp76QHVAFDJw2LmR1SHxUFiqyGeobgzJr7P2ZwSRRJQGn4D2BVlA==" - }, - "it-ws": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/it-ws/-/it-ws-6.1.5.tgz", - "integrity": "sha512-uWjMtpy5HqhSd/LlrlP3fhYrr7rUfJFFMABv0F5d6n13Q+0glhZthwUKpEAVhDrXY95Tb1RB5lLqqef+QbVNaw==", - "requires": { - "@types/ws": "^8.2.2", - "event-iterator": "^2.0.0", - "it-stream-types": "^2.0.1", - "uint8arrays": "^5.0.0", - "ws": "^8.4.0" - } - }, - "jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "requires": { - "@isaacs/cliui": "^8.0.2", - "@pkgjs/parseargs": "^0.11.0" - } - }, - "jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, - "peer": true - }, - "js-sdsl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", - "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==" - }, - "js-sha3": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.9.3.tgz", - "integrity": "sha512-BcJPCQeLg6WjEx3FE591wVAevlli8lxsxm9/FzV4HXkV49TmBH38Yvrpce6fjbADGMKFrBMGTqrVz3qPIZ88Gg==" - }, - "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 - }, - "js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true - }, - "json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "dev": true, - "requires": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "requires": { - "json-buffer": "3.0.1" - } - }, - "known-css-properties": { - "version": "0.37.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", - "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", - "dev": true - }, - "lazystream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", - "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", - "dev": true, - "requires": { - "readable-stream": "^2.0.5" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "level": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/level/-/level-6.0.1.tgz", - "integrity": "sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw==", - "requires": { - "level-js": "^5.0.0", - "level-packager": "^5.1.0", - "leveldown": "^5.4.0" - }, - "dependencies": { - "abstract-leveldown": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", - "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", - "requires": { - "buffer": "^5.5.0", - "immediate": "^3.2.3", - "level-concat-iterator": "~2.0.0", - "level-supports": "~1.0.0", - "xtend": "~4.0.0" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "leveldown": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.6.0.tgz", - "integrity": "sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==", - "requires": { - "abstract-leveldown": "~6.2.1", - "napi-macros": "~2.0.0", - "node-gyp-build": "~4.1.0" - } - }, - "node-gyp-build": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz", - "integrity": "sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==" - } - } - }, - "level-codec": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.2.tgz", - "integrity": "sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==", - "requires": { - "buffer": "^5.6.0" - }, - "dependencies": { - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - } - } - }, - "level-concat-iterator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", - "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==" - }, - "level-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", - "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", - "requires": { - "errno": "~0.1.1" - } - }, - "level-iterator-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz", - "integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==", - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.4.0", - "xtend": "^4.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "level-js": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/level-js/-/level-js-5.0.2.tgz", - "integrity": "sha512-SnBIDo2pdO5VXh02ZmtAyPP6/+6YTJg2ibLtl9C34pWvmtMEmRTWpra+qO/hifkUtBTOtfx6S9vLDjBsBK4gRg==", - "requires": { - "abstract-leveldown": "~6.2.3", - "buffer": "^5.5.0", - "inherits": "^2.0.3", - "ltgt": "^2.1.2" - }, - "dependencies": { - "abstract-leveldown": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", - "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", - "requires": { - "buffer": "^5.5.0", - "immediate": "^3.2.3", - "level-concat-iterator": "~2.0.0", - "level-supports": "~1.0.0", - "xtend": "~4.0.0" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - } - } - }, - "level-packager": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.1.1.tgz", - "integrity": "sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==", - "requires": { - "encoding-down": "^6.3.0", - "levelup": "^4.3.2" - } - }, - "level-supports": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", - "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", - "requires": { - "xtend": "^4.0.2" - } - }, - "level-write-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/level-write-stream/-/level-write-stream-1.0.0.tgz", - "integrity": "sha512-bBNKOEOMl8msO+uIM9YX/gUO6ckokZ/4pCwTm/lwvs46x6Xs8Zy0sn3Vh37eDqse4mhy4fOMIb/JsSM2nyQFtw==", - "requires": { - "end-stream": "~0.1.0" - } - }, - "leveldown": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-6.1.1.tgz", - "integrity": "sha512-88c+E+Eizn4CkQOBHwqlCJaTNEjGpaEIikn1S+cINc5E9HEvJ77bqY4JY/HxT5u0caWqsc3P3DcFIKBI1vHt+A==", - "requires": { - "abstract-leveldown": "^7.2.0", - "napi-macros": "~2.0.0", - "node-gyp-build": "^4.3.0" - }, - "dependencies": { - "abstract-leveldown": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-7.2.0.tgz", - "integrity": "sha512-DnhQwcFEaYsvYDnACLZhMmCWd3rkOeEvglpa4q5i/5Jlm3UIsWaxVzuXvDLFCSCWRO3yy2/+V/G7FusFgejnfQ==", - "requires": { - "buffer": "^6.0.3", - "catering": "^2.0.0", - "is-buffer": "^2.0.5", - "level-concat-iterator": "^3.0.0", - "level-supports": "^2.0.1", - "queue-microtask": "^1.2.3" - } - }, - "level-concat-iterator": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-3.1.0.tgz", - "integrity": "sha512-BWRCMHBxbIqPxJ8vHOvKUsaO0v1sLYZtjN3K2iZJsRBYtp+ONsY6Jfi6hy9K3+zolgQRryhIn2NRZjZnWJ9NmQ==", - "requires": { - "catering": "^2.1.0" - } - }, - "level-supports": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-2.1.0.tgz", - "integrity": "sha512-E486g1NCjW5cF78KGPrMDRBYzPuueMZ6VBXHT6gC7A8UYWGiM14fGgp+s/L1oFfDWSPV/+SFkYCmZ0SiESkRKA==" - } - } - }, - "levelup": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.4.0.tgz", - "integrity": "sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==", - "requires": { - "deferred-leveldown": "~5.3.0", - "level-errors": "~2.0.0", - "level-iterator-stream": "~4.0.0", - "level-supports": "~1.0.0", - "xtend": "~4.0.0" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "libp2p": { - "version": "2.8.11", - "resolved": "https://registry.npmjs.org/libp2p/-/libp2p-2.8.11.tgz", - "integrity": "sha512-EjkyN0CI6uP+e4OOkEcZvhbZtlwFl4Y0rkkMvDbXmcfILX4E4n/jKE4Ppoc1qhNufxToxVWCMDS2ipniQgiYaw==", - "peer": true, - "requires": { - "@chainsafe/is-ip": "^2.1.0", - "@chainsafe/netmask": "^2.0.0", - "@libp2p/crypto": "^5.1.6", - "@libp2p/interface": "^2.10.4", - "@libp2p/interface-internal": "^2.3.17", - "@libp2p/logger": "^5.1.20", - "@libp2p/multistream-select": "^6.0.27", - "@libp2p/peer-collections": "^6.0.33", - "@libp2p/peer-id": "^5.1.7", - "@libp2p/peer-store": "^11.2.5", - "@libp2p/utils": "^6.7.0", - "@multiformats/dns": "^1.0.6", - "@multiformats/multiaddr": "^12.4.4", - "@multiformats/multiaddr-matcher": "^1.7.2", - "any-signal": "^4.1.1", - "datastore-core": "^10.0.2", - "interface-datastore": "^8.3.1", - "it-byte-stream": "^2.0.2", - "it-merge": "^3.0.11", - "it-parallel": "^3.0.11", - "main-event": "^1.0.1", - "multiformats": "^13.3.6", - "p-defer": "^4.0.1", - "p-retry": "^6.2.1", - "progress-events": "^1.0.1", - "race-event": "^1.3.0", - "race-signal": "^1.1.3", - "uint8arrays": "^5.1.0" - } - }, - "lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dev": true, - "requires": { - "immediate": "~3.0.5" - }, - "dependencies": { - "immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "dev": true - } - } - }, - "lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true - }, - "linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", - "requires": { - "uc.micro": "^2.0.0" - } - }, - "locate-app": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.5.0.tgz", - "integrity": "sha512-xIqbzPMBYArJRmPGUZD9CzV9wOqmVtQnaAn3wrj3s6WYW0bQvPI7x+sPYUGmDTYMHefVK//zc6HEYZ1qnxIK+Q==", - "dev": true, - "requires": { - "@promptbook/utils": "0.69.5", - "type-fest": "4.26.0", - "userhome": "1.0.1" - } - }, - "locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "dev": true - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==" - }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", - "dev": true - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.zip": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz", - "integrity": "sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg==", - "dev": true - }, - "loglevel": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", - "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", - "dev": true - }, - "loglevel-plugin-prefix": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/loglevel-plugin-prefix/-/loglevel-plugin-prefix-0.8.4.tgz", - "integrity": "sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==", - "dev": true - }, - "long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" - }, - "loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==" - }, - "lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" - }, - "ltgt": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", - "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==" - }, - "magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "requires": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "magicast": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", - "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", - "dev": true, - "requires": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", - "source-map-js": "^1.2.1" - } - }, - "main-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/main-event/-/main-event-1.0.1.tgz", - "integrity": "sha512-NWtdGrAca/69fm6DIVd8T9rtfDII4Q8NQbIbsKQq2VzS9eqOGYs8uaNQjcuaCq/d9H/o625aOTJX2Qoxzqw0Pw==" - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "markdown-it": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", - "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", - "requires": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.0", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - } - }, - "math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true - }, - "mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" - }, - "memdown": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/memdown/-/memdown-1.4.1.tgz", - "integrity": "sha512-iVrGHZB8i4OQfM155xx8akvG9FIj+ht14DX5CQkCTG4EHzZ3d3sgckIf/Lm9ivZalEsFuEVnWv2B2WZvbrro2w==", - "dev": true, - "requires": { - "abstract-leveldown": "~2.7.1", - "functional-red-black-tree": "^1.0.1", - "immediate": "^3.2.3", - "inherits": "~2.0.1", - "ltgt": "~2.2.0", - "safe-buffer": "~5.1.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "requires": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - } - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" - }, - "minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "requires": { - "brace-expansion": "^5.0.2" - }, - "dependencies": { - "balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==" - }, - "brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "requires": { - "balanced-match": "^4.0.2" - } - } - } - }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" - }, - "minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true - }, - "mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true - }, - "mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" - }, - "modern-tar": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.5.tgz", - "integrity": "sha512-YTefgdpKKFgoTDbEUqXqgUJct2OG6/4hs4XWLsxcHkDLj/x/V8WmKIRppPnXP5feQ7d1vuYWSp3qKkxfwaFaxA==", - "dev": true - }, - "moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "dev": true - }, - "mortice": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/mortice/-/mortice-3.3.1.tgz", - "integrity": "sha512-t3oESfijIPGsmsdLEKjF+grHfrbnKSXflJtgb1wY14cjxZpS6GnhHRXTxxzCAoCCnq1YYfpEPwY3gjiCPhOufQ==", - "requires": { - "abort-error": "^1.0.0", - "it-queue": "^1.1.0", - "main-event": "^1.0.0" - } - }, - "mqtt": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.14.1.tgz", - "integrity": "sha512-NxkPxE70Uq3Ph7goefQa7ggSsVzHrayCD0OyxlJgITN/EbzlZN+JEPmaAZdxP1LsIT5FamDyILoQTF72W7Nnbw==", - "requires": { - "@types/readable-stream": "^4.0.21", - "@types/ws": "^8.18.1", - "commist": "^3.2.0", - "concat-stream": "^2.0.0", - "debug": "^4.4.1", - "help-me": "^5.0.0", - "lru-cache": "^10.4.3", - "minimist": "^1.2.8", - "mqtt-packet": "^9.0.2", - "number-allocator": "^1.0.14", - "readable-stream": "^4.7.0", - "rfdc": "^1.4.1", - "socks": "^2.8.6", - "split2": "^4.2.0", - "worker-timers": "^8.0.23", - "ws": "^8.18.3" - } - }, - "mqtt-packet": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-9.0.2.tgz", - "integrity": "sha512-MvIY0B8/qjq7bKxdN1eD+nrljoeaai+qjLJgfRn3TiMuz0pamsIWY2bFODPZMSNmabsLANXsLl4EMoWvlaTZWA==", - "requires": { - "bl": "^6.0.8", - "debug": "^4.3.4", - "process-nextick-args": "^2.0.1" - } - }, - "mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true - }, - "mrmime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", - "dev": true - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "multiformats": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", - "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==" - }, - "nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true - }, - "napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==" - }, - "napi-macros": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", - "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==" - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==" - }, - "node-abi": { - "version": "3.88.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.88.0.tgz", - "integrity": "sha512-At6b4UqIEVudaqPsXjmUO1r/N5BUr4yhDGs5PkBE8/oG5+TfLPhFechiskFsnT6Ql0VfUXbalUUCbfXxtj7K+w==", - "requires": { - "semver": "^7.3.5" - } - }, - "node-datachannel": { - "version": "0.32.1", - "resolved": "https://registry.npmjs.org/node-datachannel/-/node-datachannel-0.32.1.tgz", - "integrity": "sha512-r4UdtA0lCsz6XrG84pJ6lntAyw/MHpmBOhEkg5UQcmWTEpANqCPkMos6rj/QZDdq3GBUsdI/wst5acwWUiibCA==", - "requires": { - "prebuild-install": "^7.1.3" - } - }, - "node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==" - }, - "node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "requires": { - "boolbase": "^1.0.0" - } - }, - "number-allocator": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", - "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", - "requires": { - "debug": "^4.3.1", - "js-sdsl": "4.3.0" - } - }, - "object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - } - }, - "object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - } - }, - "object.groupby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" - } - }, - "object.values": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", - "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - } - }, - "obsidian": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.12.3.tgz", - "integrity": "sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw==", - "dev": true, - "requires": { - "@types/codemirror": "5.60.8", - "moment": "2.29.4" - } - }, - "obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true - }, - "octagonal-wheels": { - "version": "0.1.45", - "resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.45.tgz", - "integrity": "sha512-gXoCrwoUIXhmu57YN4BxAtBe+JaYNJNaXaZuVjqjopwYKpH5p2mn1om6KjA22rgGPiIJFXkse2U28FFXoT3/0Q==", - "requires": { - "idb": "^8.0.3" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "requires": { - "wrappy": "1" - } - }, - "optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "requires": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - } - }, - "own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - } - }, - "oxc-parser": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.8.0.tgz", - "integrity": "sha512-ObPeMkbDX7igb7NyyAC8CbVC3fY+YmlMsxsRQ2oyFBkpQtI5tjoyqSDKbS9A9EcJvt2q89C4UoC+HjVBdLYYJg==", - "dev": true, - "requires": { - "@oxc-parser/binding-darwin-arm64": "0.8.0", - "@oxc-parser/binding-darwin-x64": "0.8.0", - "@oxc-parser/binding-linux-arm64-gnu": "0.8.0", - "@oxc-parser/binding-linux-arm64-musl": "0.8.0", - "@oxc-parser/binding-linux-x64-gnu": "0.8.0", - "@oxc-parser/binding-linux-x64-musl": "0.8.0", - "@oxc-parser/binding-win32-arm64-msvc": "0.8.0", - "@oxc-parser/binding-win32-x64-msvc": "0.8.0" - } - }, - "p-defer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-4.0.1.tgz", - "integrity": "sha512-Mr5KC5efvAK5VUptYEIopP1bakB85k2IWXaRC0rsh1uwn1L6M0LVml8OIQ4Gudg4oyZakf7FmeRLkMMtZW1i5A==" - }, - "p-event": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", - "integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==", - "requires": { - "p-timeout": "^6.1.2" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "p-queue": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.0.0.tgz", - "integrity": "sha512-KO1RyxstL9g1mK76530TExamZC/S2Glm080Nx8PE5sTd7nlduDQsAfEl4uXX+qZjLiwvDauvzXavufy3+rJ9zQ==", - "requires": { - "eventemitter3": "^5.0.1", - "p-timeout": "^7.0.0" - }, - "dependencies": { - "p-timeout": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", - "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==" - } - } - }, - "p-retry": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", - "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", - "requires": { - "@types/retry": "0.12.2", - "is-network-error": "^1.0.0", - "retry": "^0.13.1" - } - }, - "p-timeout": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", - "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==" - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", - "dev": true, - "requires": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - } - }, - "pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "dev": true, - "requires": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - } - }, - "package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true - }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dev": true, - "requires": { - "entities": "^6.0.0" - }, - "dependencies": { - "entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true - } - } - }, - "parse5-htmlparser2-tree-adapter": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", - "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", - "dev": true, - "requires": { - "domhandler": "^5.0.3", - "parse5": "^7.0.0" - } - }, - "parse5-parser-stream": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", - "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", - "dev": true, - "requires": { - "parse5": "^7.0.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-expression-matcher": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz", - "integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "dev": true, - "requires": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "dependencies": { - "lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", - "dev": true - } - } - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true - }, - "pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==" - }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true - }, - "picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pixelmatch": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-7.1.0.tgz", - "integrity": "sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==", - "dev": true, - "requires": { - "pngjs": "^7.0.0" - } - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - } - } - }, - "playwright": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", - "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", - "dev": true, - "requires": { - "fsevents": "2.3.2", - "playwright-core": "1.58.2" - }, - "dependencies": { - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - } - } - }, - "playwright-core": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", - "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", - "dev": true - }, - "pngjs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", - "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", - "dev": true - }, - "possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true - }, - "postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "peer": true, - "requires": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - } - }, - "postcss-load-config": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", - "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", - "dev": true, - "peer": true, - "requires": { - "lilconfig": "^3.1.1" - } - }, - "postcss-safe-parser": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", - "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", - "dev": true, - "requires": {} - }, - "postcss-scss": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", - "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", - "dev": true, - "requires": {} - }, - "postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - } - }, - "pouchdb-abstract-mapreduce": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-abstract-mapreduce/-/pouchdb-abstract-mapreduce-9.0.0.tgz", - "integrity": "sha512-SnTtqwAEiAa3uxKbc1J7LfiBViwEkKe2xkK92zxyTXPqWBvMnh4UU3GXxx7GrXTM4L9llsQ3lSjpbH4CNqG1Mw==", - "dev": true, - "requires": { - "pouchdb-binary-utils": "9.0.0", - "pouchdb-collate": "9.0.0", - "pouchdb-errors": "9.0.0", - "pouchdb-fetch": "9.0.0", - "pouchdb-mapreduce-utils": "9.0.0", - "pouchdb-md5": "9.0.0", - "pouchdb-utils": "9.0.0" - } - }, - "pouchdb-adapter-http": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-adapter-http/-/pouchdb-adapter-http-9.0.0.tgz", - "integrity": "sha512-2eL008XeRZkdyp3hMHHOhdIPqK9H6Mn4SLlQvit4zCbqnOFfAswzPjUmHULGMbDUCrQBTu6y82FnV6NHXv9kgw==", - "dev": true, - "requires": { - "pouchdb-binary-utils": "9.0.0", - "pouchdb-errors": "9.0.0", - "pouchdb-fetch": "9.0.0", - "pouchdb-utils": "9.0.0" - } - }, - "pouchdb-adapter-idb": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-adapter-idb/-/pouchdb-adapter-idb-9.0.0.tgz", - "integrity": "sha512-2oLlgwMyOQwdKuzrEmOv8T7jFVgX7JgT4Cr81zX3eiiRClp7xXGgjv41ZRdVCAbM530sIN8BudafaQRVFKRVmA==", - "dev": true, - "requires": { - "pouchdb-adapter-utils": "9.0.0", - "pouchdb-binary-utils": "9.0.0", - "pouchdb-errors": "9.0.0", - "pouchdb-json": "9.0.0", - "pouchdb-merge": "9.0.0", - "pouchdb-utils": "9.0.0" - } - }, - "pouchdb-adapter-indexeddb": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-adapter-indexeddb/-/pouchdb-adapter-indexeddb-9.0.0.tgz", - "integrity": "sha512-/mcCbnVR0VKwtVZWKf8lVSdADLD0yApjFudu4d+0jeLWAeBSGZBRKYlogz2PGs4uTA7GVc2TXjVCNGUdkCM9ZQ==", - "dev": true, - "requires": { - "pouchdb-adapter-utils": "9.0.0", - "pouchdb-binary-utils": "9.0.0", - "pouchdb-errors": "9.0.0", - "pouchdb-md5": "9.0.0", - "pouchdb-merge": "9.0.0", - "pouchdb-utils": "9.0.0" - } - }, - "pouchdb-adapter-leveldb": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-adapter-leveldb/-/pouchdb-adapter-leveldb-9.0.0.tgz", - "integrity": "sha512-kF5OAN8io3j9HWP7SY0ycJrhpXxklGpO0A6On0TZXZzji2wwjLNMBxyaPGVbVni95+/t1u7Xdo3r0cAjfm+mww==", - "requires": { - "level": "6.0.1", - "level-write-stream": "1.0.0", - "leveldown": "6.1.1", - "pouchdb-adapter-leveldb-core": "9.0.0", - "pouchdb-merge": "9.0.0", - "through2": "3.0.2" - } - }, - "pouchdb-adapter-leveldb-core": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-adapter-leveldb-core/-/pouchdb-adapter-leveldb-core-9.0.0.tgz", - "integrity": "sha512-b3ZGPtVXyivGL5SK3AIDG7PrNsZdoDpGFkmTytDTtctkVhxOg71gnXXP+CrupENPqSNG/eGbKW4w+bbMpxy6aA==", - "requires": { - "double-ended-queue": "2.1.0-0", - "levelup": "4.4.0", - "pouchdb-adapter-utils": "9.0.0", - "pouchdb-binary-utils": "9.0.0", - "pouchdb-core": "9.0.0", - "pouchdb-errors": "9.0.0", - "pouchdb-json": "9.0.0", - "pouchdb-md5": "9.0.0", - "pouchdb-merge": "9.0.0", - "pouchdb-utils": "9.0.0", - "sublevel-pouchdb": "9.0.0", - "through2": "3.0.2" - } - }, - "pouchdb-adapter-memory": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-adapter-memory/-/pouchdb-adapter-memory-9.0.0.tgz", - "integrity": "sha512-XbCwJ5f5U9dGdkiDikzYjTebdPHuA6Ghylx1Pq0lDe4y6l8R9xhjDSUy56pJ8G2F4Z+8QdB5FBY9EQoFlFSXWQ==", - "dev": true, - "requires": { - "memdown": "1.4.1", - "pouchdb-adapter-leveldb-core": "9.0.0" - } - }, - "pouchdb-adapter-utils": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-adapter-utils/-/pouchdb-adapter-utils-9.0.0.tgz", - "integrity": "sha512-hmbm4ey0HL0vtoY1tRTPIt2FfYjvMh3DWoGGSxXDTS73qTFQ+Fhhi5I0AnN9PcD2omfKQAVXiYks4kkMvlAHqA==", - "requires": { - "pouchdb-binary-utils": "9.0.0", - "pouchdb-errors": "9.0.0", - "pouchdb-md5": "9.0.0", - "pouchdb-merge": "9.0.0", - "pouchdb-utils": "9.0.0" - } - }, - "pouchdb-binary-utils": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-binary-utils/-/pouchdb-binary-utils-9.0.0.tgz", - "integrity": "sha512-2OMtgDZi82vqs+zNDE0YiYjOaWkYCUcZJZKK3WkRr+XYRu+2B7umJrnygJFhUwoGedBbHSrlQBLhdNV3F1AX1A==" - }, - "pouchdb-changes-filter": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-changes-filter/-/pouchdb-changes-filter-9.0.0.tgz", - "integrity": "sha512-ig0fo0WLgIjAniFJ19Uw1Y+oxiypqC+Skhd8BCETRVXOhLBzueRwEQR4thffyo0UayYVqldJfSR5wHSDvEVk/A==", - "requires": { - "pouchdb-errors": "9.0.0", - "pouchdb-selector-core": "9.0.0", - "pouchdb-utils": "9.0.0" - } - }, - "pouchdb-checkpointer": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-checkpointer/-/pouchdb-checkpointer-9.0.0.tgz", - "integrity": "sha512-yu1OlWw78oTHKOkg1GoxxF2qB7YUsjK3rUDJOChMs/sVlZwOTZ4mGdWFPBr3udxSGvR77E+g89kpdmAWhPpHvA==", - "dev": true, - "requires": { - "pouchdb-collate": "9.0.0", - "pouchdb-utils": "9.0.0" - } - }, - "pouchdb-collate": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-collate/-/pouchdb-collate-9.0.0.tgz", - "integrity": "sha512-TrnEDNZEmIIl+W3xKUO8h+geqVLQ90oZe5ujPkl8myUzpREULWXWQBnV5EzPXVEKDBpJlb8T3I6oy/zdWGQpdA==" - }, - "pouchdb-core": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-core/-/pouchdb-core-9.0.0.tgz", - "integrity": "sha512-98SJgs8bqXhr4gMGuOTR8yVeLlMYy797zlOtdlvlXIxIicvocyA8ColhVVhdBXPNOGxT2HwReIMywdIVAgibpg==", - "requires": { - "pouchdb-changes-filter": "9.0.0", - "pouchdb-errors": "9.0.0", - "pouchdb-fetch": "9.0.0", - "pouchdb-merge": "9.0.0", - "pouchdb-utils": "9.0.0", - "uuid": "8.3.2" - } - }, - "pouchdb-errors": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-errors/-/pouchdb-errors-9.0.0.tgz", - "integrity": "sha512-961PSMLhW0UqqdJ566g+CdLZ5pkBJRd6l4WWpCDdD0USvE4xYfYGzv43w7nZZBw1k3Xdy092yqPge7yX/tfnyw==" - }, - "pouchdb-fetch": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-fetch/-/pouchdb-fetch-9.0.0.tgz", - "integrity": "sha512-TbE3cUcAJQrwb9kr44tDP0X+NAbcqgjsTvcL30L4xzBNJeCPTIRjukYX80s154SHJUXBxcWRiPsMmNqpXsjfCA==", - "requires": { - "fetch-cookie": "2.2.0", - "node-fetch": "2.6.9" - } - }, - "pouchdb-find": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-find/-/pouchdb-find-9.0.0.tgz", - "integrity": "sha512-vvVhq4eEOmSkwSRwf2NBYtdhURB7ryJ7sUI4WDN00GuLUj2g8jAXBJuZIryVgdYt/5S5cfn70iRL6Eow+LFhpA==", - "dev": true, - "requires": { - "pouchdb-abstract-mapreduce": "9.0.0", - "pouchdb-collate": "9.0.0", - "pouchdb-errors": "9.0.0", - "pouchdb-fetch": "9.0.0", - "pouchdb-md5": "9.0.0", - "pouchdb-selector-core": "9.0.0", - "pouchdb-utils": "9.0.0" - } - }, - "pouchdb-generate-replication-id": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-generate-replication-id/-/pouchdb-generate-replication-id-9.0.0.tgz", - "integrity": "sha512-wetxjU0W/qNYtfHIoKwBO73ddUr0/eqzYOkoKHSFXCgOzYmTglDeqXiVY9LPysRXTgaHUJPKC5LoknZZw7e+Dw==", - "dev": true, - "requires": { - "pouchdb-collate": "9.0.0", - "pouchdb-md5": "9.0.0" - } - }, - "pouchdb-json": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-json/-/pouchdb-json-9.0.0.tgz", - "integrity": "sha512-aI41mYVyI195GXuT1Ys7mLIB/Mvrz11ihoTP6km6hYqVgSuaUxuZcFUozlyTJiZXr7H5kdhNgclhlVnjir4JAA==", - "requires": { - "vuvuzela": "1.0.3" - } - }, - "pouchdb-mapreduce": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-mapreduce/-/pouchdb-mapreduce-9.0.0.tgz", - "integrity": "sha512-ZD8PleQ9atzQAzT2LZWsvooUVEfsen5QGv/SDfci20IleCaFW2A2q7OERrqY0YWKDCCNRsWhPWPmsFvZC9K8DQ==", - "dev": true, - "requires": { - "pouchdb-abstract-mapreduce": "9.0.0", - "pouchdb-mapreduce-utils": "9.0.0", - "pouchdb-utils": "9.0.0" - } - }, - "pouchdb-mapreduce-utils": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-mapreduce-utils/-/pouchdb-mapreduce-utils-9.0.0.tgz", - "integrity": "sha512-Bjh8W6QXqp1j7MKmHhYYp5cYlcQsm5drD8Jd/F+ZlfNt18uiD2SQXWzGM5797+tiW/LszFGb8ttw0uHWjxufCQ==", - "dev": true, - "requires": { - "pouchdb-utils": "9.0.0" - } - }, - "pouchdb-md5": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-md5/-/pouchdb-md5-9.0.0.tgz", - "integrity": "sha512-58xUYBvW3/s+aH0j4uOhhN8yCk0LQ254cxBzI/gbKA9PrfwHpe4zrr0L/ia5ml3A30oH1f8aTnuVMwWDkFcuww==", - "requires": { - "pouchdb-binary-utils": "9.0.0", - "spark-md5": "3.0.2" - } - }, - "pouchdb-merge": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-merge/-/pouchdb-merge-9.0.0.tgz", - "integrity": "sha512-Xh+TgOZCkGoZpI589btKf/cTiuQ5CsnPl9YpdW4h0cAPusniN6XNsR62F+/HbL9wirI6XTEPHUrk7MsQbk3S3A==", - "requires": { - "pouchdb-utils": "9.0.0" - } - }, - "pouchdb-replication": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-replication/-/pouchdb-replication-9.0.0.tgz", - "integrity": "sha512-EZ68KJ3ZUWuPe35NxP6WnRw8J6Zudf0j/tZ/6mOSrCcp3EbtBNt8Ke2FaAThUgiFahVnHD5Y8nd53EGs2DLygg==", - "dev": true, - "requires": { - "pouchdb-checkpointer": "9.0.0", - "pouchdb-errors": "9.0.0", - "pouchdb-generate-replication-id": "9.0.0", - "pouchdb-utils": "9.0.0" - } - }, - "pouchdb-selector-core": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-selector-core/-/pouchdb-selector-core-9.0.0.tgz", - "integrity": "sha512-ZYHYsdoedwm8j5tYofz+3+uUSK8i+7tRCBb01T0OuqDQb17+w5mzjHF8Ppi160xdPUPaWCo1Un+nLWGJzkmA3g==", - "requires": { - "pouchdb-collate": "9.0.0", - "pouchdb-utils": "9.0.0" - } - }, - "pouchdb-utils": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-utils/-/pouchdb-utils-9.0.0.tgz", - "integrity": "sha512-xWZE5c+nAslgmLC8JBZbky8AYgdz7pKtv7KTSi6CD2tuQD0WyNKib0YnhZndeE84dksTeZlqlg56RQHsHoB2LQ==", - "requires": { - "pouchdb-errors": "9.0.0", - "pouchdb-md5": "9.0.0", - "uuid": "8.3.2" - } - }, - "pouchdb-wrappers": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pouchdb-wrappers/-/pouchdb-wrappers-5.0.0.tgz", - "integrity": "sha512-fXqsVn+rmlPtxaAIGaQP5TkiaT39OMwvMk+ScLLtHrmfXD2KBO6fe/qBl38N/rpTn0h/A058dPN4fLAHt550zA==", - "dev": true - }, - "prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "requires": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "dependencies": { - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==" - }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - } - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "progress-events": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/progress-events/-/progress-events-1.0.1.tgz", - "integrity": "sha512-MOzLIwhpt64KIVN64h1MwdKWiyKFNc/S6BoYKPIVUHFg0/eIEyBulhWCgn678v/4c0ri3FdGuzXymNCv02MUIw==" - }, - "protobufjs": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", - "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - } - }, - "protons-runtime": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-5.6.0.tgz", - "integrity": "sha512-/Kde+sB9DsMFrddJT/UZWe6XqvL7SL5dbag/DBCElFKhkwDj7XKt53S+mzLyaDP5OqS0wXjV5SA572uWDaT0Hg==", - "requires": { - "uint8-varint": "^2.0.2", - "uint8arraylist": "^2.4.3", - "uint8arrays": "^5.0.1" - } - }, - "proxy-agent": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", - "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", - "dev": true, - "requires": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.6", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.1.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.5" - }, - "dependencies": { - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true - } - } - }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==" - }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" - }, - "pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==" - }, - "qrcode-generator": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.5.2.tgz", - "integrity": "sha512-pItrW0Z9HnDBnFmgiNrY1uxRdri32Uh9EjNYLPVC2zZ3ZRIIEqBoDgm4DkvDwNNDHTK7FNkmr8zAa77BYc9xNw==" - }, - "query-selector-shadow-dom": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz", - "integrity": "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==", - "dev": true - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" - }, - "quick-lru": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-7.3.0.tgz", - "integrity": "sha512-k9lSsjl36EJdK7I06v7APZCbyGT2vMTsYSRX1Q2nbYmnkBqgUhRkAuzH08Ciotteu/PLJmIF2+tti7o3C/ts2g==" - }, - "race-event": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/race-event/-/race-event-1.6.1.tgz", - "integrity": "sha512-vi7WH5g5KoTFpu2mme/HqZiWH14XSOtg5rfp6raBskBHl7wnmy3F/biAIyY5MsK+BHWhoPhxtZ1Y2R7OHHaWyQ==", - "requires": { - "abort-error": "^1.0.1" - } - }, - "race-signal": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/race-signal/-/race-signal-1.1.3.tgz", - "integrity": "sha512-Mt2NznMgepLfORijhQMncE26IhkmjEphig+/1fKC0OtaKwys/gpvpmswSjoN01SS+VO951mj0L4VIDXdXsjnfA==" - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" - } - } - }, - "readable-stream": { + "node_modules/zip-stream/node_modules/readable-stream": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "requires": { + "dev": true, + "license": "MIT", + "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" - } - }, - "readdir-glob": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", - "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", - "dev": true, - "requires": { - "minimatch": "^5.1.0" }, - "dependencies": { - "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "readdirp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", - "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", - "dev": true - }, - "reflect.getprototypeof": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - } - }, - "regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - }, - "resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "requires": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true - }, - "resq": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/resq/-/resq-1.11.0.tgz", - "integrity": "sha512-G10EBz+zAAy3zUd/CDoBbXRL6ia9kOo3xRHrMDsHljI0GDkhYlyjwoCx5+3eCC4swi1uCoZQhskuJkj7Gp57Bw==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1" - }, - "dependencies": { - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", - "dev": true - } - } - }, - "ret": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", - "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", - "dev": true - }, - "retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" - }, - "reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true - }, - "rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" - }, - "rgb2hex": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.5.tgz", - "integrity": "sha512-22MOP1Rh7sAo1BZpDG6R5RFYzR2lYEgwq7HEmyW2qcsOqR2lQKmn+O//xV3YG/0rrhMC6KVX2hU+ZXuaw9a5bw==", - "dev": true - }, - "rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", - "dev": true, - "requires": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "@types/estree": "1.0.8", - "fsevents": "~2.3.2" - } - }, - "rollup-plugin-copy": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.5.0.tgz", - "integrity": "sha512-wI8D5dvYovRMx/YYKtUNt3Yxaw4ORC9xo6Gt9t22kveWz1enG9QrhVlagzwrxSC455xD1dHMKhIJkbsQ7d48BA==", - "dev": true, - "requires": { - "@types/fs-extra": "^8.0.1", - "colorette": "^1.1.0", - "fs-extra": "^8.1.0", - "globby": "10.0.1", - "is-plain-object": "^3.0.0" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dev": true, - "requires": { - "mri": "^1.1.0" - } - }, - "safaridriver": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safaridriver/-/safaridriver-1.0.1.tgz", - "integrity": "sha512-jkg4434cYgtrIF2AeY/X0Wmd2W73cK5qIEFE3hDrrQenJH/2SDJIXGvPAigfvQTcE9+H31zkiNHbUqcihEiMRA==", - "dev": true - }, - "safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safe-push-apply": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - } - }, - "safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - } - }, - "safe-regex2": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.0.tgz", - "integrity": "sha512-pNHAuBW7TrcleFHsxBr5QMi/Iyp0ENjUKz7GCcX1UO7cMh+NmVK6HxQckNL1tJp1XAJVjG6B8OKIPqodqj9rtw==", - "dev": true, - "requires": { - "ret": "~0.5.0" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==" - }, - "serialize-error": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-12.0.0.tgz", - "integrity": "sha512-ZYkZLAvKTKQXWuh5XpBw7CdbSzagarX39WyZ2H07CDLC5/KfsRGlIXV8d4+tfqX1M7916mRqR1QfNHSij+c9Pw==", - "dev": true, - "requires": { - "type-fest": "^4.31.0" - }, - "dependencies": { - "type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true - } - } - }, - "set-cookie-parser": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", - "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==" - }, - "set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "requires": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - } - }, - "set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "requires": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - } - }, - "set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "dev": true, - "requires": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - } - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - } - }, - "side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - } - }, - "side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - } - }, - "side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - } - }, - "siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true - }, - "signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true - }, - "simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" - }, - "simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "requires": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "sirv": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", - "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", - "dev": true, - "requires": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" - }, - "socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "requires": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - } - }, - "socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "requires": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "spacetrim": { - "version": "0.11.59", - "resolved": "https://registry.npmjs.org/spacetrim/-/spacetrim-0.11.59.tgz", - "integrity": "sha512-lLYsktklSRKprreOm7NXReW8YiX2VBjbgmXYEziOoGf/qsJqAEACaDvoTtUOycwjpaSh+bT8eu0KrJn7UNxiCg==", - "dev": true - }, - "spark-md5": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.2.tgz", - "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==" - }, - "split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true - }, - "std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "dev": true - }, - "stop-iteration-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - } - }, - "streamx": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", - "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", - "dev": true, - "requires": { - "events-universal": "^1.0.0", - "fast-fifo": "^1.3.2", - "text-decoder": "^1.1.0" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "string-width-cjs": { - "version": "npm:string-width@4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - } - }, - "string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - } - }, - "string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "requires": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-ansi-cjs": { - "version": "npm:strip-ansi@6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "strnum": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz", - "integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==" - }, - "style-mod": { - "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 - }, - "sublevel-pouchdb": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/sublevel-pouchdb/-/sublevel-pouchdb-9.0.0.tgz", - "integrity": "sha512-pX4r8+F7wuts0C81kUJ341h4bl2aRe7qV572FE8X1FMz9VkKlmi2nPD1vfeiOJXz5Y09I4MHjGULAbqvTfQZEQ==", - "requires": { - "level-codec": "9.0.2", - "ltgt": "2.2.1", - "readable-stream": "1.1.14" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "svelte": { - "version": "5.41.1", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.41.1.tgz", - "integrity": "sha512-0a/huwc8e2es+7KFi70esqsReRfRbrT8h1cJSY/+z1lF0yKM6TT+//HYu28Yxstr50H7ifaqZRDGd0KuKDxP7w==", - "dev": true, - "peer": true, - "requires": { - "@jridgewell/remapping": "^2.3.4", - "@jridgewell/sourcemap-codec": "^1.5.0", - "@sveltejs/acorn-typescript": "^1.0.5", - "@types/estree": "^1.0.5", - "acorn": "^8.12.1", - "aria-query": "^5.3.1", - "axobject-query": "^4.1.0", - "clsx": "^2.1.1", - "esm-env": "^1.2.1", - "esrap": "^2.1.0", - "is-reference": "^3.0.3", - "locate-character": "^3.0.0", - "magic-string": "^0.30.11", - "zimmerframe": "^1.1.2" - } - }, - "svelte-check": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.3.tgz", - "integrity": "sha512-4HtdEv2hOoLCEsSXI+RDELk9okP/4sImWa7X02OjMFFOWeSdFF3NFy3vqpw0z+eH9C88J9vxZfUXz/Uv2A1ANw==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.25", - "chokidar": "^4.0.1", - "fdir": "^6.2.0", - "picocolors": "^1.0.0", - "sade": "^1.7.4" - }, - "dependencies": { - "fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "requires": {} - } - } - }, - "svelte-eslint-parser": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.4.0.tgz", - "integrity": "sha512-fjPzOfipR5S7gQ/JvI9r2H8y9gMGXO3JtmrylHLLyahEMquXI0lrebcjT+9/hNgDej0H7abTyox5HpHmW1PSWA==", - "dev": true, - "requires": { - "eslint-scope": "^8.2.0", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.0.0", - "postcss": "^8.4.49", - "postcss-scss": "^4.0.9", - "postcss-selector-parser": "^7.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true - } - } - }, - "svelte-preprocess": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-6.0.3.tgz", - "integrity": "sha512-PLG2k05qHdhmRG7zR/dyo5qKvakhm8IJ+hD2eFRQmMLHp7X3eJnjeupUtvuRpbNiF31RjVw45W+abDwHEmP5OA==", - "dev": true, - "requires": {} - }, - "tar-fs": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz", - "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", - "dev": true, - "requires": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - }, - "tar-stream": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", - "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==", - "dev": true, - "requires": { - "b4a": "^1.6.4", - "bare-fs": "^4.5.5", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, - "teex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", - "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", - "dev": true, - "requires": { - "streamx": "^2.12.5" - } - }, - "terser": { - "version": "5.39.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", - "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", - "dev": true, - "requires": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - } - } - }, - "test-exclude": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz", - "integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^13.0.6", - "minimatch": "^10.2.2" - } - }, - "text-decoder": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", - "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", - "dev": true, - "requires": { - "b4a": "^1.6.4" - } - }, - "through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "requires": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true - }, - "tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", - "dev": true - }, - "tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "requires": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "dependencies": { - "fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "requires": {} - }, - "picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "peer": true - } - } - }, - "tinyrainbow": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", - "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", - "dev": true - }, - "to-regex-range": { - "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, - "requires": { - "is-number": "^7.0.0" - } - }, - "totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "dev": true - }, - "tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "transform-pouch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/transform-pouch/-/transform-pouch-2.0.0.tgz", - "integrity": "sha512-nDZovo0U5o0UdMNL93fMQgGjrwH9h4F/a7qqRTnF6cVA+FfgyXiJPTrSuD+LmWSO7r2deZt0P0oeCD8hkgxl5g==", - "dev": true, - "requires": { - "pouchdb-wrappers": "^5.0.0" - } - }, - "trystero": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/trystero/-/trystero-0.22.0.tgz", - "integrity": "sha512-VscO7kaTFWNLmuxu1Au1kIxX6FzkVeXcL3+mhb9MaCSz8fm4T5MFWkdfDOujMtNK4iztupQ5AGEqGniP/I8Gvw==", - "requires": { - "@noble/secp256k1": "^3.0.0", - "@supabase/supabase-js": "^2.75.0", - "@waku/sdk": "^0.0.35", - "firebase": "^12.4.0", - "mqtt": "^5.14.1" - }, - "dependencies": { - "@noble/secp256k1": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-3.0.0.tgz", - "integrity": "sha512-NJBaR352KyIvj3t6sgT/+7xrNyF9Xk9QlLSIqUGVUYlsnDTAUqY8LOmwpcgEx4AMJXRITQ5XEVHD+mMaPfr3mg==" - } - } - }, - "ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", - "dev": true, - "requires": {} - }, - "tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", - "dev": true, - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" - }, - "tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", - "dev": true, - "peer": true, - "requires": { - "esbuild": "~0.27.0", - "fsevents": "~2.3.3", - "get-tsconfig": "^4.7.5" - }, - "dependencies": { - "@esbuild/aix-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", - "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", - "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", - "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", - "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", - "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", - "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", - "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", - "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", - "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", - "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", - "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", - "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", - "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", - "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", - "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", - "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", - "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", - "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", - "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", - "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", - "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", - "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", - "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", - "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", - "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", - "dev": true, - "optional": true - }, - "esbuild": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", - "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", - "dev": true, - "requires": { - "@esbuild/aix-ppc64": "0.27.4", - "@esbuild/android-arm": "0.27.4", - "@esbuild/android-arm64": "0.27.4", - "@esbuild/android-x64": "0.27.4", - "@esbuild/darwin-arm64": "0.27.4", - "@esbuild/darwin-x64": "0.27.4", - "@esbuild/freebsd-arm64": "0.27.4", - "@esbuild/freebsd-x64": "0.27.4", - "@esbuild/linux-arm": "0.27.4", - "@esbuild/linux-arm64": "0.27.4", - "@esbuild/linux-ia32": "0.27.4", - "@esbuild/linux-loong64": "0.27.4", - "@esbuild/linux-mips64el": "0.27.4", - "@esbuild/linux-ppc64": "0.27.4", - "@esbuild/linux-riscv64": "0.27.4", - "@esbuild/linux-s390x": "0.27.4", - "@esbuild/linux-x64": "0.27.4", - "@esbuild/netbsd-arm64": "0.27.4", - "@esbuild/netbsd-x64": "0.27.4", - "@esbuild/openbsd-arm64": "0.27.4", - "@esbuild/openbsd-x64": "0.27.4", - "@esbuild/openharmony-arm64": "0.27.4", - "@esbuild/sunos-x64": "0.27.4", - "@esbuild/win32-arm64": "0.27.4", - "@esbuild/win32-ia32": "0.27.4", - "@esbuild/win32-x64": "0.27.4" - } - } - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==" - }, - "type-fest": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.0.tgz", - "integrity": "sha512-OduNjVJsFbifKb57UqZ2EMP1i4u64Xwow3NYXUtBbD4vIwJdQd4+xl8YDou1dlm4DVrtwT/7Ky8z8WyCULVfxw==", - "dev": true - }, - "typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - } - }, - "typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - } - }, - "typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - } - }, - "typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "dev": true, - "requires": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" - }, - "typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "peer": true - }, - "uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" - }, - "uint8-varint": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-2.0.4.tgz", - "integrity": "sha512-FwpTa7ZGA/f/EssWAb5/YV6pHgVF1fViKdW8cWaEarjB8t7NyofSWBdOTyFPaGuUG4gx3v1O3PQ8etsiOs3lcw==", - "requires": { - "uint8arraylist": "^2.0.0", - "uint8arrays": "^5.0.0" - } - }, - "uint8arraylist": { - "version": "2.4.8", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-2.4.8.tgz", - "integrity": "sha512-vc1PlGOzglLF0eae1M8mLRTBivsvrGsdmJ5RbK3e+QRvRLOZfZhQROTwH/OfyF3+ZVUg9/8hE8bmKP2CvP9quQ==", - "requires": { - "uint8arrays": "^5.0.1" - } - }, - "uint8arrays": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", - "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", - "requires": { - "multiformats": "^13.0.0" - } - }, - "unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - } - }, - "undici": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz", - "integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==", - "dev": true - }, - "undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true - }, - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" - }, - "update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "requires": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "urlpattern-polyfill": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.1.0.tgz", - "integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==", - "dev": true - }, - "userhome": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/userhome/-/userhome-1.0.1.tgz", - "integrity": "sha512-5cnLm4gseXjAclKowC4IjByaGsjtAoV6PrOQOljplNB54ReUYJP8HdAFq2muHinSDAh09PPX/uXDPfdxRHvuSA==", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - }, - "vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", - "dev": true, - "peer": true, - "requires": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "fsevents": "~2.3.3", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "dependencies": { - "@esbuild/aix-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", - "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", - "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", - "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", - "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", - "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", - "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", - "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", - "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", - "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", - "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", - "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", - "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", - "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", - "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", - "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", - "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", - "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", - "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", - "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", - "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", - "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", - "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", - "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", - "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", - "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", - "dev": true, - "optional": true - }, - "esbuild": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", - "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", - "dev": true, - "requires": { - "@esbuild/aix-ppc64": "0.27.4", - "@esbuild/android-arm": "0.27.4", - "@esbuild/android-arm64": "0.27.4", - "@esbuild/android-x64": "0.27.4", - "@esbuild/darwin-arm64": "0.27.4", - "@esbuild/darwin-x64": "0.27.4", - "@esbuild/freebsd-arm64": "0.27.4", - "@esbuild/freebsd-x64": "0.27.4", - "@esbuild/linux-arm": "0.27.4", - "@esbuild/linux-arm64": "0.27.4", - "@esbuild/linux-ia32": "0.27.4", - "@esbuild/linux-loong64": "0.27.4", - "@esbuild/linux-mips64el": "0.27.4", - "@esbuild/linux-ppc64": "0.27.4", - "@esbuild/linux-riscv64": "0.27.4", - "@esbuild/linux-s390x": "0.27.4", - "@esbuild/linux-x64": "0.27.4", - "@esbuild/netbsd-arm64": "0.27.4", - "@esbuild/netbsd-x64": "0.27.4", - "@esbuild/openbsd-arm64": "0.27.4", - "@esbuild/openbsd-x64": "0.27.4", - "@esbuild/openharmony-arm64": "0.27.4", - "@esbuild/sunos-x64": "0.27.4", - "@esbuild/win32-arm64": "0.27.4", - "@esbuild/win32-ia32": "0.27.4", - "@esbuild/win32-x64": "0.27.4" - } - }, - "fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "requires": {} - }, - "picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "peer": true - } - } - }, - "vite-plugin-istanbul": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vite-plugin-istanbul/-/vite-plugin-istanbul-8.0.0.tgz", - "integrity": "sha512-r6L7cg2iwPqNnY/rWFyemWeDTIKRZjekEWS90e2FsTjDYH4UdTS6hvW1nEX1B++PKPCnqCaj5BJTDn5Cy5jYoQ==", - "dev": true, - "requires": { - "@babel/generator": "^7.29.1", - "@istanbuljs/load-nyc-config": "^1.1.0", - "@types/babel__generator": "7.27.0", - "espree": "^11.2.0", - "istanbul-lib-instrument": "^6.0.3", - "picocolors": "^1.1.1", - "source-map": "^0.7.6", - "test-exclude": "^8.0.0" - }, - "dependencies": { - "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 - }, - "espree": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", - "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", - "dev": true, - "requires": { - "acorn": "^8.16.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.1" - } - }, - "source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", - "dev": true - } - } - }, - "vitefu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", - "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", - "dev": true, - "requires": {} - }, - "vitest": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.16.tgz", - "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", - "dev": true, - "peer": true, - "requires": { - "@vitest/expect": "4.0.16", - "@vitest/mocker": "4.0.16", - "@vitest/pretty-format": "4.0.16", - "@vitest/runner": "4.0.16", - "@vitest/snapshot": "4.0.16", - "@vitest/spy": "4.0.16", - "@vitest/utils": "4.0.16", - "es-module-lexer": "^1.7.0", - "expect-type": "^1.2.2", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^3.10.0", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.0.3", - "vite": "^6.0.0 || ^7.0.0", - "why-is-node-running": "^2.3.0" - }, - "dependencies": { - "picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true - } - } - }, - "vuvuzela": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/vuvuzela/-/vuvuzela-1.0.3.tgz", - "integrity": "sha512-Tm7jR1xTzBbPW+6y1tknKiEhz04Wf/1iZkcTJjSFcpNko43+dFW6+OOeQe9taJIug3NdfUAjFKgUSyQrIKaDvQ==" - }, - "w3c-keyname": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", - "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", - "dev": true - }, - "wait-port": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-1.1.0.tgz", - "integrity": "sha512-3e04qkoN3LxTMLakdqeWth8nih8usyg+sf1Bgdf9wwUkp05iuK1eSY/QpLvscT/+F/gA89+LpUmmgBtesbqI2Q==", - "dev": true, - "requires": { - "chalk": "^4.1.2", - "commander": "^9.3.0", - "debug": "^4.3.4" - }, - "dependencies": { - "commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "dev": true - } - } - }, - "weald": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/weald/-/weald-1.1.1.tgz", - "integrity": "sha512-PaEQShzMCz8J/AD2N3dJMc1hTZWkJeLKS2NMeiVkV5KDHwgZe7qXLEzyodsT/SODxWDdXJJqocuwf3kHzcXhSQ==", - "requires": { - "ms": "^3.0.0-canary.1", - "supports-color": "^10.0.0" - }, - "dependencies": { - "ms": { - "version": "3.0.0-canary.202508261828", - "resolved": "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.202508261828.tgz", - "integrity": "sha512-NotsCoUCIUkojWCzQff4ttdCfIPoA1UGZsyQbi7KmqkNRfKCrvga8JJi2PknHymHOuor0cJSn/ylj52Cbt2IrQ==" - }, - "supports-color": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", - "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==" - } - } - }, - "web-vitals": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", - "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==" - }, - "webdriver": { - "version": "9.25.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.25.0.tgz", - "integrity": "sha512-XnABKdrp83zX3xVltmX0OcFzn8zOzWGtZQxIUKY0+INB0g9Nnnfu7G75W0G+0y4nyb3zH8mavGzDBiXctdEd3Q==", - "dev": true, - "requires": { - "@types/node": "^20.1.0", - "@types/ws": "^8.5.3", - "@wdio/config": "9.25.0", - "@wdio/logger": "9.18.0", - "@wdio/protocols": "9.25.0", - "@wdio/types": "9.25.0", - "@wdio/utils": "9.25.0", - "deepmerge-ts": "^7.0.3", - "https-proxy-agent": "^7.0.6", - "undici": "^6.21.3", - "ws": "^8.8.0" - }, - "dependencies": { - "@types/node": { - "version": "20.19.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", - "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", - "dev": true, - "requires": { - "undici-types": "~6.21.0" - } - }, - "undici": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", - "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==", - "dev": true - } - } - }, - "webdriverio": { - "version": "9.25.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.25.0.tgz", - "integrity": "sha512-ualC/LtWGjL5rwGAbUUzURKqKoHJG2/qecEppcS9k4n1IX3MlbzGXuL/qpXiRbs/h4981HpRbZAKBxRYqwUe3g==", - "dev": true, - "requires": { - "@types/node": "^20.11.30", - "@types/sinonjs__fake-timers": "^8.1.5", - "@wdio/config": "9.25.0", - "@wdio/logger": "9.18.0", - "@wdio/protocols": "9.25.0", - "@wdio/repl": "9.16.2", - "@wdio/types": "9.25.0", - "@wdio/utils": "9.25.0", - "archiver": "^7.0.1", - "aria-query": "^5.3.0", - "cheerio": "^1.0.0-rc.12", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "grapheme-splitter": "^1.0.4", - "htmlfy": "^0.8.1", - "is-plain-obj": "^4.1.0", - "jszip": "^3.10.1", - "lodash.clonedeep": "^4.5.0", - "lodash.zip": "^4.2.0", - "query-selector-shadow-dom": "^1.0.1", - "resq": "^1.11.0", - "rgb2hex": "0.2.5", - "serialize-error": "^12.0.0", - "urlpattern-polyfill": "^10.0.0", - "webdriver": "9.25.0" - }, - "dependencies": { - "@types/node": { - "version": "20.19.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", - "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", - "dev": true, - "requires": { - "undici-types": "~6.21.0" - } - } - } - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "requires": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - } - }, - "websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" - }, - "whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "dev": true, - "requires": { - "iconv-lite": "0.6.3" - } - }, - "whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "dev": true - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "wherearewe": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wherearewe/-/wherearewe-2.0.1.tgz", - "integrity": "sha512-XUguZbDxCA2wBn2LoFtcEhXL6AXo+hVjGonwhSTTTU9SzbWG8Xu3onNIpzf9j/mYUcJQ0f+m37SzG77G851uFw==", - "requires": { - "is-electron": "^2.2.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "dev": true, - "requires": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - } - }, - "which-builtin-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - } - }, - "which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, - "requires": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - } - }, - "which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - } - }, - "why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "requires": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - } - }, - "worker-factory": { - "version": "7.0.46", - "resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.46.tgz", - "integrity": "sha512-Sr1hq2FMgNa04UVhYQacsw+i58BtMimzDb4+CqYphZ97OfefRpURu0UZ+JxMr/H36VVJBfuVkxTK7MytsanC3w==", - "requires": { - "@babel/runtime": "^7.28.4", - "fast-unique-numbers": "^9.0.24", - "tslib": "^2.8.1" - } - }, - "worker-timers": { - "version": "8.0.25", - "resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-8.0.25.tgz", - "integrity": "sha512-X7Z5dmM6PlrEnaadtFQOyXHGD/IysPA3HZzaC2koqsU1VI+RvyGmjiiLiUBQixK8PH5R7ilkOzZupWskNRaXmA==", - "requires": { - "@babel/runtime": "^7.28.4", - "tslib": "^2.8.1", - "worker-timers-broker": "^8.0.11", - "worker-timers-worker": "^9.0.11" - } - }, - "worker-timers-broker": { - "version": "8.0.11", - "resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-8.0.11.tgz", - "integrity": "sha512-uwhxKru8BI9m2tsogxr2fB6POZ8LB2xH+Pu3R0mvQnAZLPgLD6K3IX4LNKPTEgTJ/j5VsuQPB+gLI1NBNKkPlg==", - "requires": { - "@babel/runtime": "^7.28.4", - "broker-factory": "^3.1.10", - "fast-unique-numbers": "^9.0.24", - "tslib": "^2.8.1", - "worker-timers-worker": "^9.0.11" - } - }, - "worker-timers-worker": { - "version": "9.0.11", - "resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-9.0.11.tgz", - "integrity": "sha512-pArb5xtgHWImYpXhjg1OFv7JFG0ubmccb73TFoXHXjG830fFj+16N57q9YeBnZX52dn+itRrMoJZ9HaZBVzDaA==", - "requires": { - "@babel/runtime": "^7.28.4", - "tslib": "^2.8.1", - "worker-factory": "^7.0.46" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrap-ansi-cjs": { - "version": "npm:wrap-ansi@7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "write-stream": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/write-stream/-/write-stream-0.4.3.tgz", - "integrity": "sha512-IJrvkhbAnj89W/GAVdVgbnPiVw5Ntg/B4tc/MUCIEwj/g6JIww1DWJyB/yBMT3yw2/TkT6IUZ0+IYef3flEw8A==", - "requires": { - "readable-stream": "~0.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-0.0.4.tgz", - "integrity": "sha512-azrivNydKRYt7zwLV5wWUK7YzKTWs3q87xSmY6DlHapPrCvaT6ZrukvM5erV+yCSSPmZT8zkSdttOHQpWWm9zw==" - } - } - }, - "ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "requires": {} - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, - "xxhash-wasm-102": { - "version": "npm:xxhash-wasm@1.0.2", - "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.0.2.tgz", - "integrity": "sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A==" - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", - "dev": true, - "peer": true - }, - "yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" - }, - "yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "requires": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - }, - "dependencies": { - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true - } - } - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - }, - "zimmerframe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", - "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", - "dev": true - }, - "zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", - "dev": true, - "requires": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } } } diff --git a/package.json b/package.json index 3d20723..11820a9 100644 --- a/package.json +++ b/package.json @@ -80,9 +80,9 @@ "@types/transform-pouch": "^1.0.6", "@typescript-eslint/eslint-plugin": "8.56.1", "@typescript-eslint/parser": "8.56.1", - "@vitest/browser": "^4.0.16", - "@vitest/browser-playwright": "^4.0.16", - "@vitest/coverage-v8": "^4.0.16", + "@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", @@ -121,8 +121,8 @@ "typescript": "5.9.3", "vite": "^7.3.1", "vite-plugin-istanbul": "^8.0.0", - "vitest": "^4.0.16", - "webdriverio": "^9.24.0", + "vitest": "^4.1.1", + "webdriverio": "^9.27.0", "yaml": "^2.8.2" }, "dependencies": { From a0af6201a5ae1d7c2435c5b79b1ad1b4ffaef2b0 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 26 Mar 2026 13:13:27 +0100 Subject: [PATCH 110/339] - No longer `Peer-to-Peer Sync is not enabled. We cannot open a new connection.` error occurs when we have not enabled P2P sync and are not expected to use it (#830). --- src/lib | 2 +- updates.md | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/lib b/src/lib index 202038d..3d6d960 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 202038d19eeb19df76b6804bcd0ea784896ce257 +Subproject commit 3d6d9603bf96477895a674398f22585662479723 diff --git a/updates.md b/updates.md index 34929eb..68296a1 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 -- + +26th March, 2026 + +### Fixed + +- No longer `Peer-to-Peer Sync is not enabled. We cannot open a new connection.` error occurs when we have not enabled P2P sync and are not expected to use it (#830). + +### CLI + +Fixed incomplete localStorage support in the CLI (#831). Thank you so much @rewse ! + ## 0.25.54 18th March, 2026 From 2de9899a994f0890948cb8be5845df2ab8207f00 Mon Sep 17 00:00:00 2001 From: ChinhLee <76194645+chinhkrb113@users.noreply.github.com> Date: Fri, 27 Mar 2026 22:26:00 +0700 Subject: [PATCH 111/339] docs: undocumented test environment variables The P2P test suite relies on several specific environment variables (e.g., `P2P_TEST_ROOM_ID`, `P2P_TEST_PASSPHRASE`, `P2P_TEST_RELAY`) loaded from `.env` or `.test.env`. Because these are not documented anywhere in the repository, new contributors will be unable to configure their local environment to run the P2P tests successfully. Affected files: vitest.config.p2p.ts Signed-off-by: ChinhLee <76194645+chinhkrb113@users.noreply.github.com> --- vitest.config.p2p.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/vitest.config.p2p.ts b/vitest.config.p2p.ts index f66ed09..7772061 100644 --- a/vitest.config.p2p.ts +++ b/vitest.config.p2p.ts @@ -5,6 +5,23 @@ import path from "path"; import dotenv from "dotenv"; import { grantClipboardPermissions, writeHandoffFile, readHandoffFile } from "./test/lib/commands"; +// P2P test environment variables +// Configure these in .env or .test.env, or inject via shell before running tests. +// Shell-injected values take precedence over dotenv files. +// +// Required: +// P2P_TEST_ROOM_ID - Shared room identifier for peers to discover each other +// P2P_TEST_PASSPHRASE - Encryption passphrase shared between test peers +// +// Optional: +// P2P_TEST_HOST_PEER_NAME - Name used to identify the host peer (default varies) +// P2P_TEST_RELAY - WebRTC relay/TURN server URL (falls back to built-in signalling) +// P2P_TEST_APP_ID - Application ID scoping the P2P session +// P2P_TEST_HANDOFF_FILE - File path used to pass state between up/down test phases +// +// 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; // Merge: dotenv files < process.env (so shell-injected vars like P2P_TEST_* take precedence) From 12f04f6cf7728c271f55ce9d8e9706033633268f Mon Sep 17 00:00:00 2001 From: kdavh Date: Sat, 28 Mar 2026 12:47:28 -0400 Subject: [PATCH 112/339] Update README.md, fix webpeer link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a839112..9980ca7 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Additionally, it supports peer-to-peer synchronisation using WebRTC now (experim - WebRTC is a peer-to-peer synchronisation method, so **at least one device must be online to synchronise**. - Instead of keeping your device online as a stable peer, you can use two pseudo-peers: - [livesync-serverpeer](https://github.com/vrtmrz/livesync-serverpeer): A pseudo-client running on the server for receiving and sending data between devices. - - [webpeer](https://github.com/vrtmrz/livesync-commonlib/tree/main/apps/webpeer): A pseudo-client for receiving and sending data between devices. + - [webpeer](https://github.com/vrtmrz/obsidian-livesync/tree/main/src/apps/webpeer): A pseudo-client for receiving and sending data between devices. - A pre-built instance is available at [fancy-syncing.vrtmrz.net/webpeer](https://fancy-syncing.vrtmrz.net/webpeer/) (hosted on the vrtmrz blog site). This is also peer-to-peer. Feel free to use it. - For more information, refer to the [English explanatory article](https://fancy-syncing.vrtmrz.net/blog/0034-p2p-sync-en.html) or the [Japanese explanatory article](https://fancy-syncing.vrtmrz.net/blog/0034-p2p-sync). From b1efbf74c70218d6148dc3f986cd167583dc80a9 Mon Sep 17 00:00:00 2001 From: chinhkrb113 <76194645+chinhkrb113@users.noreply.github.com> Date: Mon, 30 Mar 2026 02:18:25 +0700 Subject: [PATCH 113/339] docs: clarify P2P_TEST_RELAY as Nostr relay --- vitest.config.p2p.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vitest.config.p2p.ts b/vitest.config.p2p.ts index 7772061..a968e9f 100644 --- a/vitest.config.p2p.ts +++ b/vitest.config.p2p.ts @@ -15,7 +15,7 @@ import { grantClipboardPermissions, writeHandoffFile, readHandoffFile } from "./ // // Optional: // P2P_TEST_HOST_PEER_NAME - Name used to identify the host peer (default varies) -// P2P_TEST_RELAY - WebRTC relay/TURN server URL (falls back to built-in signalling) +// P2P_TEST_RELAY - Nostr relay server URL used for peer signalling/discovery // P2P_TEST_APP_ID - Application ID scoping the P2P session // P2P_TEST_HANDOFF_FILE - File path used to pass state between up/down test phases // From 4c8e13ccb946ee2422f64c8d7f75157c4a815446 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 30 Mar 2026 09:14:49 +0100 Subject: [PATCH 114/339] bump --- manifest.json | 2 +- package-lock.json | 85 ++++++++++++++++++++++++++--------------------- package.json | 2 +- updates.md | 5 +-- 4 files changed, 53 insertions(+), 41 deletions(-) diff --git a/manifest.json b/manifest.json index a375038..2fd3ddf 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.54", + "version": "0.25.55", "minAppVersion": "0.9.12", "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 f5c267a..15dcc07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.54", + "version": "0.25.55", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.54", + "version": "0.25.55", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", @@ -1939,9 +1939,9 @@ "license": "MIT" }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -2033,9 +2033,9 @@ "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -3147,24 +3147,24 @@ } }, "node_modules/@libp2p/crypto": { - "version": "5.1.13", - "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-5.1.13.tgz", - "integrity": "sha512-8NN9cQP3jDn+p9+QE9ByiEoZ2lemDFf/unTgiKmS3JF93ph240EUVdbCyyEgOMfykzb0okTM4gzvwfx9osJebQ==", + "version": "5.1.14", + "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-5.1.14.tgz", + "integrity": "sha512-0L2SEhDfvKWFhlc8GXgm268MoakrS4qbewD5LoZpoiUesXpB9e1vjed9dWEN1VsSjOmrOPyhBoSxZ2mnLTrOVA==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/interface": "^3.1.0", + "@libp2p/interface": "^3.1.1", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1", "multiformats": "^13.4.0", - "protons-runtime": "^5.6.0", + "protons-runtime": "^6.0.1", "uint8arraylist": "^2.4.8", "uint8arrays": "^5.1.0" } }, "node_modules/@libp2p/crypto/node_modules/@libp2p/interface": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-3.1.0.tgz", - "integrity": "sha512-RE7/XyvC47fQBe1cHxhMvepYKa5bFCUyFrrpj8PuM0E7JtzxU7F+Du5j4VXbg2yLDcToe0+j8mB7jvwE2AThYw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-3.1.1.tgz", + "integrity": "sha512-pQuReZeZUSqk27UXwXXdAVlxrgs08GrcPsd92Qv27IFBPICG8da3FmHg1bclUpMW/6GE6o4qDCVqR4cBMRVKyA==", "license": "Apache-2.0 OR MIT", "dependencies": { "@multiformats/dns": "^1.0.6", @@ -3214,6 +3214,17 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@libp2p/crypto/node_modules/protons-runtime": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-6.0.1.tgz", + "integrity": "sha512-ONL+jDj143WA1m+WKLuuqBIaDKxm32dx6HfJdyujrRcni/6KkhXzVnyg22nH/Wwqmbwnd1BKUVkD1hMEWZFeww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8-varint": "^2.0.4", + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.1.0" + } + }, "node_modules/@libp2p/identify": { "version": "3.0.36", "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-3.0.36.tgz", @@ -3464,9 +3475,9 @@ } }, "node_modules/@multiformats/dns/node_modules/@libp2p/interface": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-3.1.0.tgz", - "integrity": "sha512-RE7/XyvC47fQBe1cHxhMvepYKa5bFCUyFrrpj8PuM0E7JtzxU7F+Du5j4VXbg2yLDcToe0+j8mB7jvwE2AThYw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-3.1.1.tgz", + "integrity": "sha512-pQuReZeZUSqk27UXwXXdAVlxrgs08GrcPsd92Qv27IFBPICG8da3FmHg1bclUpMW/6GE6o4qDCVqR4cBMRVKyA==", "license": "Apache-2.0 OR MIT", "dependencies": { "@multiformats/dns": "^1.0.6", @@ -6639,9 +6650,9 @@ "license": "MIT" }, "node_modules/@wdio/config/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -7015,9 +7026,9 @@ "license": "MIT" }, "node_modules/archiver-utils/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -9292,9 +9303,9 @@ "license": "MIT" }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -9483,9 +9494,9 @@ "license": "MIT" }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -10399,9 +10410,9 @@ "license": "MIT" }, "node_modules/globby/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -14372,9 +14383,9 @@ "license": "MIT" }, "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 11820a9..7ebc4f0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.54", + "version": "0.25.55", "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 68296a1..ff9bf69 100644 --- a/updates.md +++ b/updates.md @@ -3,7 +3,7 @@ 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.55 26th March, 2026 @@ -13,7 +13,8 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid ### CLI -Fixed incomplete localStorage support in the CLI (#831). Thank you so much @rewse ! +- Fixed incomplete localStorage support in the CLI (#831). Thank you so much @rewse ! +- Fixed the issue where the CLI could not be connected to the remote which had been locked once (#833), also thanks to @rewse ! ## 0.25.54 From 837a828cec70235a906b849cb88f19df8b47f24a Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 30 Mar 2026 09:20:01 +0100 Subject: [PATCH 115/339] Fix: fix update note... --- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- updates.md | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/manifest.json b/manifest.json index 2fd3ddf..7c0aee9 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.55", + "version": "0.25.56", "minAppVersion": "0.9.12", "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 15dcc07..b1934f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.55", + "version": "0.25.56", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.55", + "version": "0.25.56", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 7ebc4f0..dc74e70 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.55", + "version": "0.25.56", "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 ff9bf69..a0b2ab1 100644 --- a/updates.md +++ b/updates.md @@ -3,9 +3,9 @@ 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.55 +## ~~0.25.55~~ 0.25.56 -26th March, 2026 +30th March, 2026 ### Fixed From cda27fb7f8f9db20c4edf350597add0626cff9cb Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Tue, 31 Mar 2026 07:17:51 +0000 Subject: [PATCH 116/339] - Update trystero to v0.23.0 - Add dockerfile for CLI - Change relay image for testing on arm64 --- .dockerignore | 31 + package-lock.json | 3653 +++-------------- package.json | 4 +- src/apps/cli/.gitignore | 9 +- src/apps/cli/Dockerfile | 111 + src/apps/cli/README.md | 778 ++-- src/apps/cli/docker-entrypoint.sh | 25 + src/apps/cli/entrypoint.ts | 9 +- src/apps/cli/package.json | 71 +- src/apps/cli/runtime-package.json | 24 + .../cli/test/test-e2e-two-vaults-common.sh | 0 .../cli/test/test-e2e-two-vaults-matrix.sh | 0 .../test-e2e-two-vaults-with-docker-linux.sh | 0 src/apps/cli/test/test-helpers-docker.sh | 150 + src/apps/cli/test/test-helpers.sh | 18 +- src/apps/cli/test/test-mirror-linux.sh | 0 .../test-p2p-three-nodes-conflict-linux.sh | 0 src/apps/cli/test/test-setup-put-cat-linux.sh | 0 .../cli/test/test-sync-locked-remote-linux.sh | 0 .../test-sync-two-local-databases-linux.sh | 0 src/apps/cli/util/p2p-start.sh | 30 +- src/apps/cli/vite.config.ts | 5 +- src/lib | 2 +- test/shell/p2p-start.sh | 27 +- 24 files changed, 1459 insertions(+), 3488 deletions(-) create mode 100644 .dockerignore create mode 100644 src/apps/cli/Dockerfile create mode 100644 src/apps/cli/docker-entrypoint.sh create mode 100644 src/apps/cli/runtime-package.json mode change 100755 => 100644 src/apps/cli/test/test-e2e-two-vaults-common.sh mode change 100755 => 100644 src/apps/cli/test/test-e2e-two-vaults-matrix.sh mode change 100755 => 100644 src/apps/cli/test/test-e2e-two-vaults-with-docker-linux.sh create mode 100644 src/apps/cli/test/test-helpers-docker.sh mode change 100755 => 100644 src/apps/cli/test/test-mirror-linux.sh mode change 100755 => 100644 src/apps/cli/test/test-p2p-three-nodes-conflict-linux.sh mode change 100755 => 100644 src/apps/cli/test/test-setup-put-cat-linux.sh mode change 100755 => 100644 src/apps/cli/test/test-sync-locked-remote-linux.sh mode change 100755 => 100644 src/apps/cli/test/test-sync-two-local-databases-linux.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..76fcffa --- /dev/null +++ b/.dockerignore @@ -0,0 +1,31 @@ +# Git history +.git/ +.gitignore + +# Dependencies — re-installed inside Docker +node_modules/ +src/apps/cli/node_modules/ + +# Pre-built CLI output — rebuilt inside Docker +src/apps/cli/dist/ + +# Obsidian plugin build outputs +main.js +main_org.js +pouchdb-browser.js +production/ + +# Test coverage and reports +coverage/ + +# Local environment / secrets +.env +*.env +.test.env + +# local config files +*.local + +# OS artefacts +.DS_Store +Thumbs.db diff --git a/package-lock.json b/package-lock.json index b1934f2..c552fe9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,17 +15,17 @@ "@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", "commander": "^14.0.3", "diff-match-patch": "^1.0.5", "fflate": "^0.8.2", "idb": "^8.0.3", "markdown-it": "^14.1.1", "minimatch": "^10.2.2", - "node-datachannel": "^0.32.1", "octagonal-wheels": "^0.1.45", "pouchdb-adapter-leveldb": "^9.0.0", "qrcode-generator": "^1.4.4", - "trystero": "^0.22.0", + "werift": "^0.22.9", "xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2" }, "devDependencies": { @@ -1263,58 +1263,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@chainsafe/as-chacha20poly1305": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@chainsafe/as-chacha20poly1305/-/as-chacha20poly1305-0.1.0.tgz", - "integrity": "sha512-BpNcL8/lji/GM3+vZ/bgRWqJ1q5kwvTFmGPk7pxm/QQZDbaMI98waOHjEymTjq2JmdD/INdNBFOVSyJofXg7ew==", - "license": "Apache-2.0" - }, - "node_modules/@chainsafe/as-sha256": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-1.2.0.tgz", - "integrity": "sha512-H2BNHQ5C3RS+H0ZvOdovK6GjFAyq5T6LClad8ivwj9Oaiy28uvdsGVS7gNJKuZmg0FGHAI+n7F0Qju6U0QkKDA==", - "license": "Apache-2.0" - }, - "node_modules/@chainsafe/is-ip": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@chainsafe/is-ip/-/is-ip-2.1.0.tgz", - "integrity": "sha512-KIjt+6IfysQ4GCv66xihEitBjvhU/bixbbbFxdJ1sqCp4uJ0wuZiYBPhksZoy4lfaF0k9cwNzY5upEW/VWdw3w==", - "license": "MIT" - }, - "node_modules/@chainsafe/libp2p-noise": { - "version": "16.1.3", - "resolved": "https://registry.npmjs.org/@chainsafe/libp2p-noise/-/libp2p-noise-16.1.3.tgz", - "integrity": "sha512-YLonKdIUFk/0keKRfzlmdrsObi8r0EaZC14Vjh3qdLy4+W7NaQAs1sSMt8aDP07oE78pa51NyejmQLKOnt7tOw==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@chainsafe/as-chacha20poly1305": "^0.1.0", - "@chainsafe/as-sha256": "^1.0.0", - "@libp2p/crypto": "^5.0.0", - "@libp2p/interface": "^2.9.0", - "@libp2p/peer-id": "^5.0.0", - "@noble/ciphers": "^1.1.3", - "@noble/curves": "^1.1.0", - "@noble/hashes": "^1.3.1", - "it-length-prefixed": "^10.0.1", - "it-length-prefixed-stream": "^2.0.1", - "it-pair": "^2.0.6", - "it-pipe": "^3.0.1", - "it-stream-types": "^2.0.1", - "protons-runtime": "^5.5.0", - "uint8arraylist": "^2.4.3", - "uint8arrays": "^5.0.0", - "wherearewe": "^2.0.1" - } - }, - "node_modules/@chainsafe/netmask": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@chainsafe/netmask/-/netmask-2.0.0.tgz", - "integrity": "sha512-I3Z+6SWUoaljh3TBzCnCxjlUyN8tA+NAk5L6m9IxvCf1BENQTePzPMis97CoN/iMW1St3WN+AWCCRp+TTBRiDg==", - "license": "MIT", - "dependencies": { - "@chainsafe/is-ip": "^2.0.1" - } - }, "node_modules/@chialab/esbuild-plugin-meta-url": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@chialab/esbuild-plugin-meta-url/-/esbuild-plugin-meta-url-0.19.1.tgz", @@ -1411,19 +1359,6 @@ "w3c-keyname": "^2.2.4" } }, - "node_modules/@dnsquery/dns-packet": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@dnsquery/dns-packet/-/dns-packet-6.1.1.tgz", - "integrity": "sha512-WXTuFvL3G+74SchFAtz3FgIYVOe196ycvGsMgvSH/8Goptb1qpIQtIuM4SOK9G9lhMWYpHxnXyy544ZhluFOew==", - "license": "MIT", - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.4", - "utf8-codec": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", @@ -2106,720 +2041,26 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@ethersproject/bytes": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.8.0.tgz", - "integrity": "sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], + "node_modules/@fidm/asn1": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@fidm/asn1/-/asn1-1.0.4.tgz", + "integrity": "sha512-esd1jyNvRb2HVaQGq2Gg8Z0kbQPXzV9Tq5Z14KNIov6KfFD6PTaRIO8UpcsYiTNzOqJpmyzWgVTrUwFV3UF4TQ==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@fidm/x509": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@fidm/x509/-/x509-1.2.1.tgz", + "integrity": "sha512-nwc2iesjyc9hkuzcrMCBXQRn653XuAUKorfWM8PZyJawiy1QzLj4vahwzaI25+pfpwOLvMzbJ0uKpWLDNmo16w==", "license": "MIT", "dependencies": { - "@ethersproject/logger": "^5.8.0" - } - }, - "node_modules/@ethersproject/logger": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.8.0.tgz", - "integrity": "sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT" - }, - "node_modules/@ethersproject/rlp": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.8.0.tgz", - "integrity": "sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/logger": "^5.8.0" - } - }, - "node_modules/@firebase/ai": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-2.10.0.tgz", - "integrity": "sha512-1lI6HomyoO/8RSJb6ItyHLpHnB2z27m5F4aX/Vpi1nhwWoxdNjkq+6UQOykHyCE0KairojOE5qQ20i1tnF0nNA==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app-check-interop-types": "0.3.3", - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" + "@fidm/asn1": "^1.0.4", + "tweetnacl": "^1.0.1" }, "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app": "0.x", - "@firebase/app-types": "0.x" - } - }, - "node_modules/@firebase/analytics": { - "version": "0.10.21", - "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.21.tgz", - "integrity": "sha512-j2y2q65BlgLGB5Pwjhv/Jopw2X/TBTzvAtI5z/DSp56U4wBj7LfhBfzbdCtFPges+Wz0g55GdoawXibOH5jGng==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/installations": "0.6.21", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/analytics-compat": { - "version": "0.2.27", - "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.27.tgz", - "integrity": "sha512-ZObpYpAxL6JfgH7GnvlDD0sbzGZ0o4nijV8skatV9ZX49hJtCYbFqaEcPYptT94rgX1KUoKEderC7/fa7hybtw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/analytics": "0.10.21", - "@firebase/analytics-types": "0.8.3", - "@firebase/component": "0.7.2", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/analytics-types": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.3.tgz", - "integrity": "sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/app": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.10.tgz", - "integrity": "sha512-PlPhdtjgWUra+LImQTnXOUqUa/jcufZhizdR93ZjlQSS3ahCtDTG6pJw7j0OwFal18DQjICXfeVNsUUrcNisfA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", - "idb": "7.1.1", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@firebase/app-check": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.11.2.tgz", - "integrity": "sha512-jcXQVMHAQ5AEKzVD5C7s5fmAYeFOuN6lAJeNTgZK2B9aLnofWaJt8u1A8Idm8gpsBBYSaY3cVyeH5SWMOVPBLQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/app-check-compat": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.4.2.tgz", - "integrity": "sha512-M91NhxqbSkI0ChkJWy69blC+rPr6HEgaeRllddSaU1pQ/7IiegeCQM9pPDIgvWnwnBSzKhUHpe6ro/jhJ+cvzw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app-check": "0.11.2", - "@firebase/app-check-types": "0.5.3", - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/app-check-interop-types": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz", - "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/app-check-types": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.3.tgz", - "integrity": "sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/app-compat": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.10.tgz", - "integrity": "sha512-tFmBuZL0/v1h6eyKRgWI58ucft6dEJmAi9nhPUXoAW4ZbPSTlnsh31AuEwUoRTz+wwRk9gmgss9GZV05ZM9Kug==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@firebase/app": "0.14.10", - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@firebase/app-types": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", - "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", - "license": "Apache-2.0", - "peer": true - }, - "node_modules/@firebase/app/node_modules/idb": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", - "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", - "license": "ISC" - }, - "node_modules/@firebase/auth": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.12.2.tgz", - "integrity": "sha512-CZJL8V10Vzibs+pDTXdQF+hot1IigIoqF4a4lA/qr5Deo1srcefiyIfgg28B67Lk7IxZhwfJMuI+1bu2xBmV0A==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app": "0.x", - "@react-native-async-storage/async-storage": "^2.2.0" - }, - "peerDependenciesMeta": { - "@react-native-async-storage/async-storage": { - "optional": true - } - } - }, - "node_modules/@firebase/auth-compat": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.6.4.tgz", - "integrity": "sha512-2pj8m/hnqXvMLfC0Mk+fORVTM5DQPkS6l8JpMgtoAWGVgCmYnoWdFMaNWtKbmCxBEyvMA3FlnCJyzrUSMWTfuA==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/auth": "1.12.2", - "@firebase/auth-types": "0.13.0", - "@firebase/component": "0.7.2", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/auth-interop-types": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz", - "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/auth-types": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.0.tgz", - "integrity": "sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==", - "license": "Apache-2.0", - "peerDependencies": { - "@firebase/app-types": "0.x", - "@firebase/util": "1.x" - } - }, - "node_modules/@firebase/component": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.2.tgz", - "integrity": "sha512-iyVDGc6Vjx7Rm0cAdccLH/NG6fADsgJak/XW9IA2lPf8AjIlsemOpFGKczYyPHxm4rnKdR8z6sK4+KEC7NwmEg==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@firebase/data-connect": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.5.0.tgz", - "integrity": "sha512-G3GYHpWNJJ95502RQLApzw0jaG3pScHl+J/2MdxIuB51xtHnkRL6KvIAP3fFF1drUewWJHOnDA1U+q4Evf3KSw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/auth-interop-types": "0.2.4", - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/database": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.2.tgz", - "integrity": "sha512-lP96CMjMPy/+d1d9qaaHjHHdzdwvEOuyyLq9ehX89e2XMKwS1jHNzYBO+42bdSumuj5ukPbmnFtViZu8YOMT+w==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app-check-interop-types": "0.3.3", - "@firebase/auth-interop-types": "0.2.4", - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", - "faye-websocket": "0.11.4", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@firebase/database-compat": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.2.tgz", - "integrity": "sha512-j4A6IhVZbgxAzT6gJJC2PfOxYCK9SrDrUO7nTM4EscTYtKkAkzsbKoCnDdjFapQfnsncvPWjqVTr/0PffUwg3g==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/database": "1.1.2", - "@firebase/database-types": "1.0.18", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@firebase/database-types": { - "version": "1.0.18", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.18.tgz", - "integrity": "sha512-yOY8IC2go9lfbVDMiy2ATun4EB2AFwocPaQADwMN/RHRUAZSM4rlAV7PGbWPSG/YhkJ2A9xQAiAENgSua9G5Fg==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app-types": "0.9.3", - "@firebase/util": "1.15.0" - } - }, - "node_modules/@firebase/firestore": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.13.0.tgz", - "integrity": "sha512-7i4cVNJXTMim7/P7UsNim0DwyLPk4QQ3y1oSNzv4l0ykJOKYCiFMOuEeUxUYvrReXDJxWHrT/4XMeVQm+13rRw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", - "@firebase/webchannel-wrapper": "1.0.5", - "@grpc/grpc-js": "~1.9.0", - "@grpc/proto-loader": "^0.7.8", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/firestore-compat": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.7.tgz", - "integrity": "sha512-Et4XxtGnjp0Q9tmaEMETnY5GHJ8gQ9+RN6sSTT4ETWKmym2d6gIjarw0rCQcx+7BrWVYLEIOAXSXysl0b3xnUA==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/firestore": "4.13.0", - "@firebase/firestore-types": "3.0.3", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/firestore-types": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz", - "integrity": "sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==", - "license": "Apache-2.0", - "peerDependencies": { - "@firebase/app-types": "0.x", - "@firebase/util": "1.x" - } - }, - "node_modules/@firebase/functions": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.13.3.tgz", - "integrity": "sha512-csO7ckK3SSs+NUZW1nms9EK7ckHe/1QOjiP8uAkCYa7ND18s44vjE9g3KxEeIUpyEPqZaX1EhJuFyZjHigAcYw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app-check-interop-types": "0.3.3", - "@firebase/auth-interop-types": "0.2.4", - "@firebase/component": "0.7.2", - "@firebase/messaging-interop-types": "0.2.3", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/functions-compat": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.4.3.tgz", - "integrity": "sha512-BxkEwWgx1of0tKaao/r2VR6WBLk/RAiyztatiONPrPE8gkitFkOnOCxf8i9cUyA5hX5RGt5H30uNn25Q6QNEmQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/functions": "0.13.3", - "@firebase/functions-types": "0.6.3", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/functions-types": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz", - "integrity": "sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/installations": { - "version": "0.6.21", - "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.21.tgz", - "integrity": "sha512-xGFGTeICJZ5vhrmmDukeczIcFULFXybojML2+QSDFoKj5A7zbGN7KzFGSKNhDkIxpjzsYG9IleJyUebuAcmqWA==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/util": "1.15.0", - "idb": "7.1.1", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/installations-compat": { - "version": "0.2.21", - "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.21.tgz", - "integrity": "sha512-zahIUkaVKbR8zmTeBHkdfaVl6JGWlhVoSjF7CVH33nFqD3SlPEpEEegn2GNT5iAfsVdtlCyJJ9GW4YKjq+RJKQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/installations": "0.6.21", - "@firebase/installations-types": "0.5.3", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/installations-types": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.3.tgz", - "integrity": "sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==", - "license": "Apache-2.0", - "peerDependencies": { - "@firebase/app-types": "0.x" - } - }, - "node_modules/@firebase/installations/node_modules/idb": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", - "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", - "license": "ISC" - }, - "node_modules/@firebase/logger": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.0.tgz", - "integrity": "sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@firebase/messaging": { - "version": "0.12.25", - "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.25.tgz", - "integrity": "sha512-7RhDwoDHlOK1/ou0/LeubxmjcngsTjDdrY/ssg2vwAVpUuVAhQzQvuCAOYxcX5wNC1zCgQ54AP1vdngBwbCmOQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/installations": "0.6.21", - "@firebase/messaging-interop-types": "0.2.3", - "@firebase/util": "1.15.0", - "idb": "7.1.1", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/messaging-compat": { - "version": "0.2.25", - "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.25.tgz", - "integrity": "sha512-eoOQqGLtRlseTdiemTN44LlHZpltK5gnhq8XVUuLgtIOG+odtDzrz2UoTpcJWSzaJQVxNLb/x9f39tHdDM4N4w==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/messaging": "0.12.25", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/messaging-interop-types": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz", - "integrity": "sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/messaging/node_modules/idb": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", - "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", - "license": "ISC" - }, - "node_modules/@firebase/performance": { - "version": "0.7.11", - "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.11.tgz", - "integrity": "sha512-V3uAhrz7IYJuji+OgT3qYTGKxpek/TViXti9OSsUJ4AexZ3jQjYH5Yrn7JvBxk8MGiSLsC872hh+BxQiPZsm7g==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/installations": "0.6.21", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0", - "web-vitals": "^4.2.4" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/performance-compat": { - "version": "0.2.24", - "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.24.tgz", - "integrity": "sha512-YRlejH8wLt7ThWao+HXoKUHUrZKGYq+otxkPS+8nuE5PeN1cBXX7NAJl9ueuUkBwMIrnKdnDqL/voHXxDAAt3g==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/performance": "0.7.11", - "@firebase/performance-types": "0.2.3", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/performance-types": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz", - "integrity": "sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/remote-config": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.8.2.tgz", - "integrity": "sha512-5EXqOThV4upjK9D38d/qOSVwOqRhemlaOFk9vCkMNNALeIlwr+4pLjtLNo4qoY8etQmU/1q4aIATE9N8PFqg0g==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/installations": "0.6.21", - "@firebase/logger": "0.5.0", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/remote-config-compat": { - "version": "0.2.23", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.23.tgz", - "integrity": "sha512-4+KqRRHEUUmKT6tFmnpWATOsaFfmSuBs1jXH8JzVtMLEYqq/WS9IDM92OdefFDSrAA2xGd0WN004z8mKeIIscw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/logger": "0.5.0", - "@firebase/remote-config": "0.8.2", - "@firebase/remote-config-types": "0.5.0", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/remote-config-types": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.5.0.tgz", - "integrity": "sha512-vI3bqLoF14L/GchtgayMiFpZJF+Ao3uR8WCde0XpYNkSokDpAKca2DxvcfeZv7lZUqkUwQPL2wD83d3vQ4vvrg==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/storage": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.14.2.tgz", - "integrity": "sha512-o/culaTeJ8GRpKXRJov21rux/n9dRaSOWLebyatFP2sqEdCxQPjVA1H9Z2fzYwQxMIU0JVmC7SPPmU11v7L6vQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/storage-compat": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.4.2.tgz", - "integrity": "sha512-R+aB38wxCH5zjIO/xu9KznI7fgiPuZAG98uVm1NcidHyyupGgIDLKigGmRGBZMnxibe/m2oxNKoZpfEbUX2aQQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.2", - "@firebase/storage": "0.14.2", - "@firebase/storage-types": "0.8.3", - "@firebase/util": "1.15.0", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/storage-types": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.3.tgz", - "integrity": "sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==", - "license": "Apache-2.0", - "peerDependencies": { - "@firebase/app-types": "0.x", - "@firebase/util": "1.x" - } - }, - "node_modules/@firebase/util": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.15.0.tgz", - "integrity": "sha512-AmWf3cHAOMbrCPG4xdPKQaj5iHnyYfyLKZxwz+Xf55bqKbpAmcYifB4jQinT2W9XhDRHISOoPyBOariJpCG6FA==", - "hasInstallScript": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@firebase/webchannel-wrapper": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.5.tgz", - "integrity": "sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw==", - "license": "Apache-2.0" - }, - "node_modules/@grpc/grpc-js": { - "version": "1.9.15", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz", - "integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/proto-loader": "^0.7.8", - "@types/node": ">=12.12.47" - }, - "engines": { - "node": "^8.13.0 || >=10.10.0" - } - }, - "node_modules/@grpc/proto-loader": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", - "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", - "license": "Apache-2.0", - "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.2.5", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" + "node": ">= 8" } }, "node_modules/@humanfs/core": { @@ -3132,327 +2373,6 @@ "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", "license": "MIT" }, - "node_modules/@libp2p/bootstrap": { - "version": "11.0.42", - "resolved": "https://registry.npmjs.org/@libp2p/bootstrap/-/bootstrap-11.0.42.tgz", - "integrity": "sha512-xe5LMZrXR2cnFcR69ax/cHqNCc8zwSvqDWOf82u/ifAsUae7M0eeoEVi15lfrPewWst4YPkTqYDkZSPWC65I3Q==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/interface": "^2.10.4", - "@libp2p/interface-internal": "^2.3.17", - "@libp2p/peer-id": "^5.1.7", - "@multiformats/mafmt": "^12.1.6", - "@multiformats/multiaddr": "^12.4.4", - "main-event": "^1.0.1" - } - }, - "node_modules/@libp2p/crypto": { - "version": "5.1.14", - "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-5.1.14.tgz", - "integrity": "sha512-0L2SEhDfvKWFhlc8GXgm268MoakrS4qbewD5LoZpoiUesXpB9e1vjed9dWEN1VsSjOmrOPyhBoSxZ2mnLTrOVA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/interface": "^3.1.1", - "@noble/curves": "^2.0.1", - "@noble/hashes": "^2.0.1", - "multiformats": "^13.4.0", - "protons-runtime": "^6.0.1", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/@libp2p/crypto/node_modules/@libp2p/interface": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-3.1.1.tgz", - "integrity": "sha512-pQuReZeZUSqk27UXwXXdAVlxrgs08GrcPsd92Qv27IFBPICG8da3FmHg1bclUpMW/6GE6o4qDCVqR4cBMRVKyA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/dns": "^1.0.6", - "@multiformats/multiaddr": "^13.0.1", - "main-event": "^1.0.1", - "multiformats": "^13.4.0", - "progress-events": "^1.0.1", - "uint8arraylist": "^2.4.8" - } - }, - "node_modules/@libp2p/crypto/node_modules/@multiformats/multiaddr": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-13.0.1.tgz", - "integrity": "sha512-XToN915cnfr6Lr9EdGWakGJbPT0ghpg/850HvdC+zFX8XvpLZElwa8synCiwa8TuvKNnny6m8j8NVBNCxhIO3g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "multiformats": "^13.0.0", - "uint8-varint": "^2.0.1", - "uint8arrays": "^5.0.0" - } - }, - "node_modules/@libp2p/crypto/node_modules/@noble/curves": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz", - "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "2.0.1" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@libp2p/crypto/node_modules/@noble/hashes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", - "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@libp2p/crypto/node_modules/protons-runtime": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-6.0.1.tgz", - "integrity": "sha512-ONL+jDj143WA1m+WKLuuqBIaDKxm32dx6HfJdyujrRcni/6KkhXzVnyg22nH/Wwqmbwnd1BKUVkD1hMEWZFeww==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/@libp2p/identify": { - "version": "3.0.36", - "resolved": "https://registry.npmjs.org/@libp2p/identify/-/identify-3.0.36.tgz", - "integrity": "sha512-swpgKzZ8SihHeguIEf3LxYlEcD7C9cSB7DE1XGTuCxNXkXksv2ieQTSiJ2xURMRw0nwe0wEGS5vzmshJh3kzrA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/crypto": "^5.1.6", - "@libp2p/interface": "^2.10.4", - "@libp2p/interface-internal": "^2.3.17", - "@libp2p/peer-id": "^5.1.7", - "@libp2p/peer-record": "^8.0.33", - "@libp2p/utils": "^6.7.0", - "@multiformats/multiaddr": "^12.4.4", - "@multiformats/multiaddr-matcher": "^1.7.2", - "it-drain": "^3.0.9", - "it-parallel": "^3.0.11", - "it-protobuf-stream": "^2.0.2", - "main-event": "^1.0.1", - "protons-runtime": "^5.5.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/@libp2p/interface": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.11.0.tgz", - "integrity": "sha512-0MUFKoXWHTQW3oWIgSHApmYMUKWO/Y02+7Hpyp+n3z+geD4Xo2Rku2gYWmxcq+Pyjkz6Q9YjDWz3Yb2SoV2E8Q==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/dns": "^1.0.6", - "@multiformats/multiaddr": "^12.4.4", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.2", - "main-event": "^1.0.1", - "multiformats": "^13.3.6", - "progress-events": "^1.0.1", - "uint8arraylist": "^2.4.8" - } - }, - "node_modules/@libp2p/interface-internal": { - "version": "2.3.19", - "resolved": "https://registry.npmjs.org/@libp2p/interface-internal/-/interface-internal-2.3.19.tgz", - "integrity": "sha512-v335EB0i5CaNF+0SqT01CTBp0VyjJizpy46KprcshFFjX16UQ8+/QzoTZqmot9WiAmAzwR0b87oKmlAE9cpxzQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/interface": "^2.11.0", - "@libp2p/peer-collections": "^6.0.35", - "@multiformats/multiaddr": "^12.4.4", - "progress-events": "^1.0.1" - } - }, - "node_modules/@libp2p/logger": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-5.2.0.tgz", - "integrity": "sha512-OEFS529CnIKfbWEHmuCNESw9q0D0hL8cQ8klQfjIVPur15RcgAEgc1buQ7Y6l0B6tCYg120bp55+e9tGvn8c0g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/interface": "^2.11.0", - "@multiformats/multiaddr": "^12.4.4", - "interface-datastore": "^8.3.1", - "multiformats": "^13.3.6", - "weald": "^1.0.4" - } - }, - "node_modules/@libp2p/mplex": { - "version": "11.0.42", - "resolved": "https://registry.npmjs.org/@libp2p/mplex/-/mplex-11.0.42.tgz", - "integrity": "sha512-06HXNQS02GOEx1796Nsk640tIHwnHtJvd4TWnslUEeVi7SxLA8LqCttT7qJ2P3Y1tmm3E2a74kxtHqaJDqypig==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/interface": "^2.10.4", - "@libp2p/utils": "^6.7.0", - "it-pipe": "^3.0.1", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.2", - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/@libp2p/multistream-select": { - "version": "6.0.29", - "resolved": "https://registry.npmjs.org/@libp2p/multistream-select/-/multistream-select-6.0.29.tgz", - "integrity": "sha512-SWQbPcABOIpznEY7+vAp0Y3HNrE2PlaVY4EywN0lUZ7zvTv9VnAb7av3/gMvfaLI+YrOvhCr1mZ9qbSB93k4kA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/interface": "^2.11.0", - "it-length-prefixed": "^10.0.1", - "it-length-prefixed-stream": "^2.0.2", - "it-stream-types": "^2.0.2", - "p-defer": "^4.0.1", - "race-signal": "^1.1.3", - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/@libp2p/peer-collections": { - "version": "6.0.35", - "resolved": "https://registry.npmjs.org/@libp2p/peer-collections/-/peer-collections-6.0.35.tgz", - "integrity": "sha512-QiloK3T7DXW7R2cpL38dBnALCHf5pMzs/TyFzlEK33WezA2YFVoj7CtOJKqbn29bmV9uspWOxMgfmLUXf8ALvA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/interface": "^2.11.0", - "@libp2p/peer-id": "^5.1.9", - "@libp2p/utils": "^6.7.2", - "multiformats": "^13.3.6" - } - }, - "node_modules/@libp2p/peer-id": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-5.1.9.tgz", - "integrity": "sha512-cVDp7lX187Epmi/zr0Qq2RsEMmueswP9eIxYSFoMcHL/qcvRFhsxOfUGB8361E26s2WJvC9sXZ0oJS9XVueJhQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/crypto": "^5.1.8", - "@libp2p/interface": "^2.11.0", - "multiformats": "^13.3.6", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/@libp2p/peer-record": { - "version": "8.0.35", - "resolved": "https://registry.npmjs.org/@libp2p/peer-record/-/peer-record-8.0.35.tgz", - "integrity": "sha512-0818zvjKbucq5XBnusG8oSWxJ992rVry/2qlfcn/nyK/uDrZ12tjDYHNMCoOWTNeFvFUVkMg9pRkvXvTNp6Yiw==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/crypto": "^5.1.8", - "@libp2p/interface": "^2.11.0", - "@libp2p/peer-id": "^5.1.9", - "@libp2p/utils": "^6.7.2", - "@multiformats/multiaddr": "^12.4.4", - "multiformats": "^13.3.6", - "protons-runtime": "^5.5.0", - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/@libp2p/peer-store": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/@libp2p/peer-store/-/peer-store-11.2.7.tgz", - "integrity": "sha512-dwTM+0i7mAgAnZvMHghgGcFoWPGaTbKx2nBueMd2Yg38mCs9WeambmR6gQdjwvYpybvNgFDAA+XesCKCotuczg==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/crypto": "^5.1.8", - "@libp2p/interface": "^2.11.0", - "@libp2p/peer-collections": "^6.0.35", - "@libp2p/peer-id": "^5.1.9", - "@libp2p/peer-record": "^8.0.35", - "@multiformats/multiaddr": "^12.4.4", - "interface-datastore": "^8.3.1", - "it-all": "^3.0.8", - "main-event": "^1.0.1", - "mortice": "^3.2.1", - "multiformats": "^13.3.6", - "protons-runtime": "^5.5.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/@libp2p/ping": { - "version": "2.0.35", - "resolved": "https://registry.npmjs.org/@libp2p/ping/-/ping-2.0.35.tgz", - "integrity": "sha512-f9z5drqjaRRTu1dK/3gshtth/xdE2YwZ6qhBUpqLX4x5s3k/X8ds4aRx2lzvznQMmOMaT8e1VEjhbfFlcJmUOA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/crypto": "^5.1.6", - "@libp2p/interface": "^2.10.4", - "@libp2p/interface-internal": "^2.3.17", - "@multiformats/multiaddr": "^12.4.4", - "it-byte-stream": "^2.0.2", - "main-event": "^1.0.1", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/@libp2p/utils": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@libp2p/utils/-/utils-6.7.2.tgz", - "integrity": "sha512-yglVPcYErb4al3MMTdedVLLsdUvr5KaqrrxohxTl/FXMFBvBs0o3w8lo29nfnTUpnNSHFhWZ9at0ZGNnpT/C/w==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@chainsafe/is-ip": "^2.1.0", - "@chainsafe/netmask": "^2.0.0", - "@libp2p/crypto": "^5.1.8", - "@libp2p/interface": "^2.11.0", - "@libp2p/logger": "^5.2.0", - "@multiformats/multiaddr": "^12.4.4", - "@sindresorhus/fnv1a": "^3.1.0", - "any-signal": "^4.1.1", - "delay": "^6.0.0", - "get-iterator": "^2.0.1", - "is-loopback-addr": "^2.0.2", - "is-plain-obj": "^4.1.0", - "it-foreach": "^2.1.3", - "it-pipe": "^3.0.1", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.2", - "main-event": "^1.0.1", - "netmask": "^2.0.2", - "p-defer": "^4.0.1", - "race-event": "^1.3.0", - "race-signal": "^1.1.3", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/@libp2p/websockets": { - "version": "9.2.16", - "resolved": "https://registry.npmjs.org/@libp2p/websockets/-/websockets-9.2.16.tgz", - "integrity": "sha512-jD96ClKeaZvTs+YyGJxOOQSWVe9e9SHmacJ9uE9dqWZjCbPICdCJnOj2pLg258WxVrc+/MRFKSHE/v5f2ZJGCA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/interface": "^2.10.4", - "@libp2p/utils": "^6.7.0", - "@multiformats/multiaddr": "^12.4.4", - "@multiformats/multiaddr-matcher": "^1.7.2", - "@multiformats/multiaddr-to-uri": "^11.0.0", - "@types/ws": "^8.18.1", - "it-ws": "^6.1.5", - "main-event": "^1.0.1", - "p-defer": "^4.0.1", - "p-event": "^6.0.1", - "progress-events": "^1.0.1", - "race-signal": "^1.1.3", - "ws": "^8.18.2" - } - }, "node_modules/@marijn/find-cluster-break": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", @@ -3460,89 +2380,11 @@ "dev": true, "license": "MIT" }, - "node_modules/@multiformats/dns": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.13.tgz", - "integrity": "sha512-yr4bxtA3MbvJ+2461kYIYMsiiZj/FIqKI64hE4SdvWJUdWF9EtZLar38juf20Sf5tguXKFUruluswAO6JsjS2w==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@dnsquery/dns-packet": "^6.1.1", - "@libp2p/interface": "^3.1.0", - "hashlru": "^2.3.0", - "p-queue": "^9.0.0", - "progress-events": "^1.0.0", - "uint8arrays": "^5.0.2" - } - }, - "node_modules/@multiformats/dns/node_modules/@libp2p/interface": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-3.1.1.tgz", - "integrity": "sha512-pQuReZeZUSqk27UXwXXdAVlxrgs08GrcPsd92Qv27IFBPICG8da3FmHg1bclUpMW/6GE6o4qDCVqR4cBMRVKyA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/dns": "^1.0.6", - "@multiformats/multiaddr": "^13.0.1", - "main-event": "^1.0.1", - "multiformats": "^13.4.0", - "progress-events": "^1.0.1", - "uint8arraylist": "^2.4.8" - } - }, - "node_modules/@multiformats/dns/node_modules/@multiformats/multiaddr": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-13.0.1.tgz", - "integrity": "sha512-XToN915cnfr6Lr9EdGWakGJbPT0ghpg/850HvdC+zFX8XvpLZElwa8synCiwa8TuvKNnny6m8j8NVBNCxhIO3g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "multiformats": "^13.0.0", - "uint8-varint": "^2.0.1", - "uint8arrays": "^5.0.0" - } - }, - "node_modules/@multiformats/mafmt": { - "version": "12.1.6", - "resolved": "https://registry.npmjs.org/@multiformats/mafmt/-/mafmt-12.1.6.tgz", - "integrity": "sha512-tlJRfL21X+AKn9b5i5VnaTD6bNttpSpcqwKVmDmSHLwxoz97fAHaepqFOk/l1fIu94nImIXneNbhsJx/RQNIww==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^12.0.0" - } - }, - "node_modules/@multiformats/multiaddr": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.5.1.tgz", - "integrity": "sha512-+DDlr9LIRUS8KncI1TX/FfUn8F2dl6BIxJgshS/yFQCNB5IAF0OGzcwB39g5NLE22s4qqDePv0Qof6HdpJ/4aQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "@chainsafe/netmask": "^2.0.0", - "@multiformats/dns": "^1.0.3", - "abort-error": "^1.0.1", - "multiformats": "^13.0.0", - "uint8-varint": "^2.0.1", - "uint8arrays": "^5.0.0" - } - }, - "node_modules/@multiformats/multiaddr-matcher": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-matcher/-/multiaddr-matcher-1.8.0.tgz", - "integrity": "sha512-tR/HFhDucXjvwCef5lfXT7kikqR2ffUjliuYlg/RKYGPySVKVlvrDufz86cIuHNc+i/fNR16FWWgD/pMJ6RW4w==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "@multiformats/multiaddr": "^12.0.0", - "multiformats": "^13.0.0" - } - }, - "node_modules/@multiformats/multiaddr-to-uri": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr-to-uri/-/multiaddr-to-uri-11.0.2.tgz", - "integrity": "sha512-SiLFD54zeOJ0qMgo9xv1Tl9O5YktDKAVDP4q4hL16mSq4O4sfFNagNADz8eAofxd6TfQUzGQ3TkRRG9IY2uHRg==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^12.3.0" - } + "node_modules/@minhducsun2002/leb128": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@minhducsun2002/leb128/-/leb128-1.0.0.tgz", + "integrity": "sha512-eFrYUPDVHeuwWHluTG1kwNQUEUcFjVKYwPkU8z9DR1JH3AW7JtJsG9cRVGmwz809kKtGfwGJj58juCZxEvnI/g==", + "license": "MIT" }, "node_modules/@napi-rs/magic-string": { "version": "0.3.4", @@ -3790,18 +2632,6 @@ "node": ">= 10" } }, - "node_modules/@noble/ciphers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", - "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@noble/curves": { "version": "1.9.7", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", @@ -4001,6 +2831,154 @@ "node": "^12.18.3 || >=14" } }, + "node_modules/@peculiar/asn1-cms": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.1.tgz", + "integrity": "sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-attr": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.6.1.tgz", + "integrity": "sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.1.tgz", + "integrity": "sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.6.1.tgz", + "integrity": "sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.1", + "@peculiar/asn1-pkcs8": "^2.6.1", + "@peculiar/asn1-rsa": "^2.6.1", + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.1.tgz", + "integrity": "sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.1.tgz", + "integrity": "sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.1", + "@peculiar/asn1-pfx": "^2.6.1", + "@peculiar/asn1-pkcs8": "^2.6.1", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-attr": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.1.tgz", + "integrity": "sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", + "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", + "license": "MIT", + "dependencies": { + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.1.tgz", + "integrity": "sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.1.tgz", + "integrity": "sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/x509": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", + "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -4039,70 +3017,6 @@ "spacetrim": "0.11.59" } }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "license": "BSD-3-Clause" - }, "node_modules/@puppeteer/browsers": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.0.tgz", @@ -4510,18 +3424,36 @@ "dev": true, "license": "MIT" }, - "node_modules/@sindresorhus/fnv1a": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/fnv1a/-/fnv1a-3.1.0.tgz", - "integrity": "sha512-KV321z5m/0nuAg83W1dPLy85HpHDk7Sdi4fJbwvacWsEhAh+rZUW4ZfGcXmUIvjZg4ss2bcwNlRhJ7GBEUG08w==", + "node_modules/@shinyoshiaki/binary-data": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@shinyoshiaki/binary-data/-/binary-data-0.6.1.tgz", + "integrity": "sha512-7HDb/fQAop2bCmvDIzU5+69i+UJaFgIVp99h1VzK1mpg1JwSODOkjbqD7ilTYnqlnadF8C4XjpwpepxDsGY6+w==", "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "dependencies": { + "generate-function": "^2.3.1", + "is-plain-object": "^2.0.3" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=6" } }, + "node_modules/@shinyoshiaki/binary-data/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@shinyoshiaki/jspack": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@shinyoshiaki/jspack/-/jspack-0.0.6.tgz", + "integrity": "sha512-SdsNhLjQh4onBlyPrn4ia1Pdx5bXT88G/LIEpOYAjx2u4xeY/m/HB5yHqlkJB1uQR3Zw4R3hBWLj46STRAN0rg==" + }, "node_modules/@smithy/abort-controller": { "version": "4.2.12", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.12.tgz", @@ -5277,92 +4209,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@supabase/auth-js": { - "version": "2.100.0", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.100.0.tgz", - "integrity": "sha512-pdT3ye3UVRN1Cg0wom6BmyY+XTtp5DiJaYnPi6j8ht5i8Lq8kfqxJMJz9GI9YDKk3w1nhGOPnh6Qz5qpyYm+1w==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/functions-js": { - "version": "2.100.0", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.100.0.tgz", - "integrity": "sha512-keLg79RPwP+uiwHuxFPTFgDRxPV46LM4j/swjyR2GKJgWniTVSsgiBHfbIBDcrQwehLepy09b/9QSHUywtKRWQ==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/phoenix": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.0.tgz", - "integrity": "sha512-RHSx8bHS02xwfHdAbX5Lpbo6PXbgyf7lTaXTlwtFDPwOIw64NnVRwFAXGojHhjtVYI+PEPNSWwkL90f4agN3bw==", - "license": "MIT" - }, - "node_modules/@supabase/postgrest-js": { - "version": "2.100.0", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.100.0.tgz", - "integrity": "sha512-xYNvNbBJaXOGcrZ44wxwp5830uo1okMHGS8h8dm3u4f0xcZ39yzbryUsubTJW41MG2gbL/6U57cA4Pi6YMZ9pA==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/realtime-js": { - "version": "2.100.0", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.100.0.tgz", - "integrity": "sha512-2AZs00zzEF0HuCKY8grz5eCYlwEfVi5HONLZFoNR6aDfxQivl8zdQYNjyFoqN2MZiVhQHD7u6XV/xHwM8mCEHw==", - "license": "MIT", - "dependencies": { - "@supabase/phoenix": "^0.4.0", - "@types/ws": "^8.18.1", - "tslib": "2.8.1", - "ws": "^8.18.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/storage-js": { - "version": "2.100.0", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.100.0.tgz", - "integrity": "sha512-d4EeuK6RNIgYNA2MU9kj8lQrLm5AzZ+WwpWjGkii6SADQNIGTC/uiaTRu02XJ5AmFALQfo8fLl9xuCkO6Xw+iQ==", - "license": "MIT", - "dependencies": { - "iceberg-js": "^0.8.1", - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/supabase-js": { - "version": "2.100.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.100.0.tgz", - "integrity": "sha512-r0tlcukejJXJ1m/2eG/Ya5eYs4W8AC7oZfShpG3+SIo/eIU9uIt76ZeYI1SoUwUmcmzlAbgch+HDZDR/toVQPQ==", - "license": "MIT", - "dependencies": { - "@supabase/auth-js": "2.100.0", - "@supabase/functions-js": "2.100.0", - "@supabase/postgrest-js": "2.100.0", - "@supabase/realtime-js": "2.100.0", - "@supabase/storage-js": "2.100.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/@sveltejs/acorn-typescript": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz", @@ -5420,6 +4266,22 @@ "dev": true, "license": "MIT" }, + "node_modules/@trystero-p2p/core": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@trystero-p2p/core/-/core-0.23.0.tgz", + "integrity": "sha512-ozhtgxKDZH11Gdef0wH8xivwAE/L0/lDFvEcNFWPJWnHZlxWPPyfeonwE287ssGevQNi10vnj6x2ZcOi0n1bQQ==", + "license": "MIT" + }, + "node_modules/@trystero-p2p/nostr": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@trystero-p2p/nostr/-/nostr-0.23.0.tgz", + "integrity": "sha512-KSqUR2c1KVfv4zeErcntuegtyKzFTzNNiitIKGD0LiKA/4H3CeTF81ROk2h+X/PNvP4mv7Gp5eVxFYwfMu4Nrg==", + "license": "MIT", + "dependencies": { + "@noble/secp256k1": "^3.0.0", + "@trystero-p2p/core": "0.23.0" + } + }, "node_modules/@tsconfig/svelte": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.8.tgz", @@ -5538,21 +4400,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/lodash": { - "version": "4.17.24", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", - "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", - "license": "MIT" - }, - "node_modules/@types/lodash.debounce": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz", - "integrity": "sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==", - "license": "MIT", - "dependencies": { - "@types/lodash": "*" - } - }, "node_modules/@types/markdown-it": { "version": "14.1.2", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", @@ -5589,6 +4436,7 @@ "version": "24.12.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -5791,21 +4639,6 @@ "@types/pouchdb-find": "*" } }, - "node_modules/@types/readable-stream": { - "version": "4.0.23", - "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.23.tgz", - "integrity": "sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/retry": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", - "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", - "license": "MIT" - }, "node_modules/@types/sinonjs__fake-timers": { "version": "8.1.5", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", @@ -5844,6 +4677,7 @@ "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -6307,322 +5141,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@waku/core": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.39.tgz", - "integrity": "sha512-Vgb52md4GOzM5z9xfULzjN2tvVHKszFmj5zc2mVDoIgySH4cFBgDTHtVtGEwrFRFWadWYKBtpKBdmG3X+W7SNA==", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@libp2p/ping": "2.0.35", - "@noble/hashes": "^1.3.2", - "@waku/enr": "^0.0.33", - "@waku/interfaces": "0.0.34", - "@waku/proto": "0.0.14", - "@waku/utils": "0.0.27", - "debug": "^4.3.4", - "it-all": "^3.0.4", - "it-length-prefixed": "^9.0.4", - "it-pipe": "^3.0.1", - "uint8arraylist": "^2.4.3", - "uuid": "^9.0.0" - }, - "engines": { - "node": ">=22" - }, - "peerDependencies": { - "@multiformats/multiaddr": "^12.0.0", - "libp2p": "2.8.11" - }, - "peerDependenciesMeta": { - "@multiformats/multiaddr": { - "optional": true - }, - "libp2p": { - "optional": true - } - } - }, - "node_modules/@waku/core/node_modules/it-length-prefixed": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/it-length-prefixed/-/it-length-prefixed-9.1.1.tgz", - "integrity": "sha512-O88nBweT6M9ozsmok68/auKH7ik/slNM4pYbM9lrfy2z5QnpokW5SlrepHZDKtN71llhG2sZvd6uY4SAl+lAQg==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "it-reader": "^6.0.1", - "it-stream-types": "^2.0.1", - "uint8-varint": "^2.0.1", - "uint8arraylist": "^2.0.0", - "uint8arrays": "^5.0.1" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@waku/core/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@waku/discovery": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/@waku/discovery/-/discovery-0.0.12.tgz", - "integrity": "sha512-4ItzLMQA79xveu5I9ymx3Q1A/Aj0fGjdi8TnPnb9xnm2O927w71efnkxmHo3rwATEYonXiB34NpMN2ecAp1enA==", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@waku/core": "0.0.39", - "@waku/enr": "0.0.33", - "@waku/interfaces": "0.0.34", - "@waku/proto": "^0.0.14", - "@waku/utils": "0.0.27", - "debug": "^4.3.4", - "dns-over-http-resolver": "^3.0.8", - "hi-base32": "^0.5.1", - "uint8arrays": "^5.0.1" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@waku/enr": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@waku/enr/-/enr-0.0.33.tgz", - "integrity": "sha512-OVZoCXc9Lto2tUfo+iSqQ61pmRm/QikYSJWc3InKmsL3qtfpMShiChK/X/PafwdRFVA28b46itm++KUqMjGi+A==", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@ethersproject/rlp": "^5.7.0", - "@libp2p/crypto": "5.1.6", - "@libp2p/peer-id": "5.1.7", - "@multiformats/multiaddr": "^12.0.0", - "@noble/secp256k1": "^1.7.1", - "@waku/utils": "0.0.27", - "debug": "^4.3.4", - "js-sha3": "^0.9.2" - }, - "engines": { - "node": ">=22" - }, - "peerDependencies": { - "@multiformats/multiaddr": "^12.0.0" - }, - "peerDependenciesMeta": { - "@multiformats/multiaddr": { - "optional": true - } - } - }, - "node_modules/@waku/enr/node_modules/@libp2p/crypto": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-5.1.6.tgz", - "integrity": "sha512-hCNDInAsjfFTOr1ZlVTVuRKpkGEbR1GC+cDbmn2Vslwd0dHZHqhKv5ye7l6NZaiNUxxqUCVmqvJIWqVLuTPDdg==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/interface": "^2.10.4", - "@noble/curves": "^1.9.1", - "@noble/hashes": "^1.8.0", - "multiformats": "^13.3.6", - "protons-runtime": "^5.5.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/@waku/enr/node_modules/@libp2p/peer-id": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-5.1.7.tgz", - "integrity": "sha512-KBT8Edx/Sqxj0vKe5mPM2PQx06VDmGzx2BZ1M+LiDAM94q9Sag4tyaUugHyTrJKGG8V+7lx1Fz46kfbezuwR9g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/crypto": "^5.1.6", - "@libp2p/interface": "^2.10.4", - "multiformats": "^13.3.6", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/@waku/enr/node_modules/@noble/secp256k1": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.2.tgz", - "integrity": "sha512-/qzwYl5eFLH8OWIecQWM31qld2g1NfjgylK+TNhqtaUKP37Nm+Y+z30Fjhw0Ct8p9yCQEm2N3W/AckdIb3SMcQ==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT" - }, - "node_modules/@waku/interfaces": { - "version": "0.0.34", - "resolved": "https://registry.npmjs.org/@waku/interfaces/-/interfaces-0.0.34.tgz", - "integrity": "sha512-15+SOfr8cKk5J2ukSucy/T6j23jIudRt1hr/N09YaNUvQ19iXofjne5MU/P8otmgP8daedCijCagRB0rwoHKKQ==", - "license": "MIT OR Apache-2.0", - "engines": { - "node": ">=22" - } - }, - "node_modules/@waku/proto": { - "version": "0.0.14", - "resolved": "https://registry.npmjs.org/@waku/proto/-/proto-0.0.14.tgz", - "integrity": "sha512-8zKVHrKzzKQfZBVnpSmJ6G8H1Zd4Gqms1tj3L6K2WCE/NQDR8wJtFwziab3dJ/5rKUTjfPAWFJ57RN97ltzxGA==", - "license": "MIT OR Apache-2.0", - "dependencies": { - "protons-runtime": "^5.4.0" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@waku/sdk": { - "version": "0.0.35", - "resolved": "https://registry.npmjs.org/@waku/sdk/-/sdk-0.0.35.tgz", - "integrity": "sha512-bnXl5b8BDCOKSrJ7V6PiJshc3bsBWaGweWy20IsGlXalCJj7257wK31lZxEP62lTFKHS8tPfG+EvDwrPK4oxKA==", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@chainsafe/libp2p-noise": "16.1.3", - "@libp2p/bootstrap": "11.0.42", - "@libp2p/identify": "3.0.36", - "@libp2p/mplex": "11.0.42", - "@libp2p/ping": "2.0.35", - "@libp2p/websockets": "9.2.16", - "@noble/hashes": "^1.3.3", - "@types/lodash.debounce": "^4.0.9", - "@waku/core": "0.0.39", - "@waku/discovery": "0.0.12", - "@waku/interfaces": "0.0.34", - "@waku/proto": "^0.0.14", - "@waku/sds": "^0.0.7", - "@waku/utils": "0.0.27", - "libp2p": "2.8.11", - "lodash.debounce": "^4.0.8" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@waku/sds": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@waku/sds/-/sds-0.0.7.tgz", - "integrity": "sha512-wPZASJ1iH9K5gSgHMvmahRdnD/yNrNj/35R4H0SZGhUaViOyUGMooJH0YewZmVc1Dvy2L9mDitHurJWqLoWbcg==", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@libp2p/interface": "2.10.4", - "@noble/hashes": "^1.7.1", - "@waku/proto": "^0.0.14", - "@waku/utils": "^0.0.27", - "chai": "^5.1.2", - "lodash": "^4.17.21" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@waku/sds/node_modules/@libp2p/interface": { - "version": "2.10.4", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-2.10.4.tgz", - "integrity": "sha512-FX3uujZgjH9bb7mDSNR54j3JzJnF/ngnQH20GQ1wPk5irIeHDvmzRlUj3bJ3hHQmdB2MxLZNT6e39O1es10LFA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^12.4.4", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.2", - "main-event": "^1.0.1", - "multiformats": "^13.3.6", - "progress-events": "^1.0.1", - "uint8arraylist": "^2.4.8" - } - }, - "node_modules/@waku/utils": { - "version": "0.0.27", - "resolved": "https://registry.npmjs.org/@waku/utils/-/utils-0.0.27.tgz", - "integrity": "sha512-kIS/EN9Xoc5ik2c4MweqcvV3NEcl+CDmg09jpVUVG7fB2/yxVRakBlROytGn+vALR4pcHom1tW2dW1vtofCfFw==", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@noble/hashes": "^1.3.2", - "@waku/interfaces": "0.0.34", - "chai": "^4.3.10", - "debug": "^4.3.4", - "uint8arrays": "^5.0.1" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@waku/utils/node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/@waku/utils/node_modules/chai": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", - "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", - "license": "MIT", - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@waku/utils/node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@waku/utils/node_modules/deep-eql": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", - "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", - "license": "MIT", - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@waku/utils/node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.1" - } - }, - "node_modules/@waku/utils/node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/@wdio/config": { "version": "9.27.0", "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.27.0.tgz", @@ -6860,6 +5378,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, "license": "MIT", "dependencies": { "event-target-shim": "^5.0.0" @@ -6868,12 +5387,6 @@ "node": ">=6.5" } }, - "node_modules/abort-error": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/abort-error/-/abort-error-1.0.1.tgz", - "integrity": "sha512-fxqCblJiIPdSXIUrxI0PL+eJG49QdP9SQ70qtB65MVAoMr2rASlOyAbJFOylfB467F/f+5BCLJJq58RYi7mGfg==", - "license": "Apache-2.0 OR MIT" - }, "node_modules/abstract-leveldown": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", @@ -6915,6 +5428,12 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/aes-js": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz", + "integrity": "sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==", + "license": "MIT" + }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -6959,6 +5478,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -6970,16 +5490,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/any-signal": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/any-signal/-/any-signal-4.2.0.tgz", - "integrity": "sha512-LndMvYuAPf4rC195lk7oSFuHOYFpOszIYrNYv0gHAvz+aEhE9qPZLhmrIz5pXP2BSsPOXvsuHDXEGaiQhIh9wA==", - "license": "Apache-2.0 OR MIT", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, "node_modules/archiver": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", @@ -7342,10 +5852,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asn1js": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.7.tgz", + "integrity": "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==", + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -7583,17 +6108,6 @@ "node": ">=10.0.0" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -7632,18 +6146,6 @@ "node": ">=8" } }, - "node_modules/broker-factory": { - "version": "3.1.14", - "resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.14.tgz", - "integrity": "sha512-L45k5HMbPIrMid0nTOZ/UPXG/c0aRuQKVrSDFIb1zOkvfiyHgYmIjc3cSiN1KwQIvRDOtKE0tfb3I9EZ3CmpQQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.29.2", - "fast-unique-numbers": "^9.0.27", - "tslib": "^2.8.1", - "worker-factory": "^7.0.49" - } - }, "node_modules/browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", @@ -7707,7 +6209,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.0.0" @@ -7717,6 +6218,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, "license": "MIT" }, "node_modules/builtin-modules": { @@ -7832,22 +6334,6 @@ "node": ">=6" } }, - "node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", - "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7865,15 +6351,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/check-error": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", - "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, "node_modules/cheerio": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", @@ -7934,12 +6411,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, "node_modules/cjs-module-lexer": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", @@ -7951,6 +6422,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -7965,6 +6437,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7974,6 +6447,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -7996,6 +6470,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -8008,6 +6483,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/colorette": { @@ -8026,12 +6502,6 @@ "node": ">=20" } }, - "node_modules/commist": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/commist/-/commist-3.2.0.tgz", - "integrity": "sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==", - "license": "MIT" - }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -8105,21 +6575,6 @@ "dev": true, "license": "MIT" }, - "node_modules/concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "engines": [ - "node >= 6.0" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -8344,22 +6799,20 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/datastore-core": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/datastore-core/-/datastore-core-10.0.4.tgz", - "integrity": "sha512-IctgCO0GA7GHG7aRm3JRruibCsfvN4EXNnNIlLCZMKIv0TPkdAL5UFV3/xTYFYrrZ1jRNrXZNZRvfcVf/R+rAw==", - "license": "Apache-2.0 OR MIT", + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "license": "MIT", "dependencies": { - "@libp2p/logger": "^5.1.18", - "interface-datastore": "^8.0.0", - "interface-store": "^6.0.0", - "it-drain": "^3.0.9", - "it-filter": "^3.1.3", - "it-map": "^3.1.3", - "it-merge": "^3.0.11", - "it-pipe": "^3.0.1", - "it-sort": "^3.0.8", - "it-take": "^3.0.8" + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" } }, "node_modules/debug": { @@ -8392,39 +6845,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -8517,18 +6937,6 @@ "node": ">= 14" } }, - "node_modules/delay": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/delay/-/delay-6.0.0.tgz", - "integrity": "sha512-2NJozoOHQ4NuZuVIr5CWd0iiLVIRSDepakaovIN+9eIDHEhdCAEvSy2cuf1DCrPPQLvHmbqTHODlhHg8UCy4zw==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -8561,14 +6969,16 @@ "node": ">=8" } }, - "node_modules/dns-over-http-resolver": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/dns-over-http-resolver/-/dns-over-http-resolver-3.0.16.tgz", - "integrity": "sha512-Qnq8HhNRuMnA61pf1lVPlStCAv1BVrraCx0umPESWgYKf995tUMF5oNhW59PKdnf7E8d5yqwHlEoFywXjsNMCw==", - "license": "Apache-2.0 OR MIT", + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "license": "MIT", "dependencies": { - "quick-lru": "^7.0.0", - "weald": "^1.0.2" + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" } }, "node_modules/doctrine": { @@ -8807,6 +7217,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/encoding-down": { @@ -8843,6 +7254,7 @@ "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, "license": "MIT", "dependencies": { "once": "^1.4.0" @@ -9110,6 +7522,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9649,31 +8062,21 @@ "node": ">=0.10.0" } }, - "node_modules/event-iterator": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/event-iterator/-/event-iterator-2.0.0.tgz", - "integrity": "sha512-KGft0ldl31BZVV//jj+IAIGCxkvvUkkON+ScH6zfoX+l+omX6001ggyRSpI0Io2Hlro0ThXotswCtfzS8UkIiQ==", - "license": "MIT" - }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "license": "MIT" - }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.8.x" @@ -9689,15 +8092,6 @@ "bare-events": "^2.7.0" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -9733,7 +8127,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-fifo": { @@ -9787,19 +8180,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-unique-numbers": { - "version": "9.0.27", - "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.27.tgz", - "integrity": "sha512-nDA9ADeINN8SA2u2wCtU+siWFTTDqQR37XvgPIDDmboWQeExz7X0mImxuaN+kJddliIqy2FpVRmnvRZ+j8i1/A==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.29.2", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=18.2.0" - } - }, "node_modules/fast-xml-builder": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", @@ -9845,18 +8225,6 @@ "reusify": "^1.0.4" } }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "license": "Apache-2.0", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -9944,42 +8312,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/firebase": { - "version": "12.11.0", - "resolved": "https://registry.npmjs.org/firebase/-/firebase-12.11.0.tgz", - "integrity": "sha512-W9f3Y+cgQYgF9gvCGxt0upec8zwAtiQVcHuU8MfzUIgVU/9fRQWtu48Geiv1lsigtBz9QHML++Km9xAKO5GB5Q==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/ai": "2.10.0", - "@firebase/analytics": "0.10.21", - "@firebase/analytics-compat": "0.2.27", - "@firebase/app": "0.14.10", - "@firebase/app-check": "0.11.2", - "@firebase/app-check-compat": "0.4.2", - "@firebase/app-compat": "0.5.10", - "@firebase/app-types": "0.9.3", - "@firebase/auth": "1.12.2", - "@firebase/auth-compat": "0.6.4", - "@firebase/data-connect": "0.5.0", - "@firebase/database": "1.1.2", - "@firebase/database-compat": "2.1.2", - "@firebase/firestore": "4.13.0", - "@firebase/firestore-compat": "0.4.7", - "@firebase/functions": "0.13.3", - "@firebase/functions-compat": "0.4.3", - "@firebase/installations": "0.6.21", - "@firebase/installations-compat": "0.2.21", - "@firebase/messaging": "0.12.25", - "@firebase/messaging-compat": "0.2.25", - "@firebase/performance": "0.7.11", - "@firebase/performance-compat": "0.2.24", - "@firebase/remote-config": "0.8.2", - "@firebase/remote-config-compat": "0.2.23", - "@firebase/storage": "0.14.2", - "@firebase/storage-compat": "0.4.2", - "@firebase/util": "1.15.0" - } - }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -10034,12 +8366,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, "node_modules/fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -10147,6 +8473,15 @@ "node": ">=20.0.0" } }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, "node_modules/generator-function": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", @@ -10171,20 +8506,12 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -10210,12 +8537,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-iterator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/get-iterator/-/get-iterator-2.0.1.tgz", - "integrity": "sha512-7HuY/hebu4gryTDT7O/XY/fvY9wRByEGdK6QOa4of8npTcv0+NS6frFKABcf6S9EBAsveTuKTsZQQBFMMNILIg==", - "license": "MIT" - }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -10315,12 +8636,6 @@ "node": ">= 14" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, "node_modules/glob": { "version": "13.0.6", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", @@ -10563,12 +8878,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hashlru": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz", - "integrity": "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==", - "license": "MIT" - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -10582,18 +8891,6 @@ "node": ">= 0.4" } }, - "node_modules/help-me": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", - "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", - "license": "MIT" - }, - "node_modules/hi-base32": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/hi-base32/-/hi-base32-0.5.1.tgz", - "integrity": "sha512-EmBBpvdYh/4XxsnUybsPag6VikPYnN30td+vQk+GI3qpahVEG9+gTkG0aXVxTjBqQ5T6ijbWIu77O+C5WFWsnA==", - "license": "MIT" - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -10641,12 +8938,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/http-parser-js": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", - "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", - "license": "MIT" - }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -10675,15 +8966,6 @@ "node": ">= 14" } }, - "node_modules/iceberg-js": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", - "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", - "license": "MIT", - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -10795,27 +9077,11 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/interface-datastore": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-8.3.2.tgz", - "integrity": "sha512-R3NLts7pRbJKc3qFdQf+u40hK8XWc0w4Qkx3OFEstC80VoaDUABY/dXA2EJPhtNC+bsrf41Ehvqb6+pnIclyRA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "interface-store": "^6.0.0", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/interface-store": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-6.0.3.tgz", - "integrity": "sha512-+WvfEZnFUhRwFxgz+QCQi7UC6o9AM0EHM9bpIe2Nhqb100NHCsTvNAn4eJgvgV2/tmLo1MP9nGxQKEcZTAueLA==", - "license": "Apache-2.0 OR MIT" + "node_modules/int64-buffer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/int64-buffer/-/int64-buffer-1.1.0.tgz", + "integrity": "sha512-94smTCQOvigN4d/2R/YDjz8YVG0Sufvv2aAh8P5m42gwhCsDAJqnbNOrxJsrADuAFAA69Q/ptGzxvNcNuIJcvw==", + "license": "MIT" }, "node_modules/internal-slot": { "version": "1.1.0", @@ -10832,10 +9098,17 @@ "node": ">= 0.4" } }, + "node_modules/ip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", + "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==", + "dev": true, "license": "MIT", "engines": { "node": ">= 12" @@ -10999,12 +9272,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-electron": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", - "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", - "license": "MIT" - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -11035,6 +9302,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11073,12 +9341,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-loopback-addr": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-loopback-addr/-/is-loopback-addr-2.0.2.tgz", - "integrity": "sha512-26POf2KRCno/KTNL5Q0b/9TYnL00xEsSaLfiFRmjM7m7Lw7ZMmFybzzuX4CcsLAluZGd+niLUiMRxEooVE3aqg==", - "license": "MIT" - }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -11105,18 +9367,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-network-error": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.1.tgz", - "integrity": "sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -11148,6 +9398,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -11166,6 +9417,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, "node_modules/is-reference": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", @@ -11348,6 +9605,15 @@ "dev": true, "license": "ISC" }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -11420,256 +9686,6 @@ "node": ">=8" } }, - "node_modules/it-all": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/it-all/-/it-all-3.0.9.tgz", - "integrity": "sha512-fz1oJJ36ciGnu2LntAlE6SA97bFZpW7Rnt0uEc1yazzR2nKokZLr8lIRtgnpex4NsmaBcvHF+Z9krljWFy/mmg==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/it-byte-stream": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/it-byte-stream/-/it-byte-stream-2.0.4.tgz", - "integrity": "sha512-8pS0OvkBYwQ206pRLgoLDAiHP6c8wYZJ1ig8KDmP5NOrzMxeH2Wv2ktXIjYHwdu7RPOsnxQb0vKo+O784L/m5g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "abort-error": "^1.0.1", - "it-queueless-pushable": "^2.0.0", - "it-stream-types": "^2.0.2", - "race-signal": "^2.0.0", - "uint8arraylist": "^2.4.8" - } - }, - "node_modules/it-byte-stream/node_modules/race-signal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/race-signal/-/race-signal-2.0.0.tgz", - "integrity": "sha512-P31bLhE4ByBX/70QDXMutxnqgwrF1WUXea1O8DXuviAgkdbQ1iQMQotNgzJIBC9yUSn08u/acZrMUhgw7w6GpA==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/it-drain": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/it-drain/-/it-drain-3.0.10.tgz", - "integrity": "sha512-0w/bXzudlyKIyD1+rl0xUKTI7k4cshcS43LTlBiGFxI8K1eyLydNPxGcsVLsFVtKh1/ieS8AnVWt6KwmozxyEA==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/it-filter": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/it-filter/-/it-filter-3.1.4.tgz", - "integrity": "sha512-80kWEKgiFEa4fEYD3mwf2uygo1dTQ5Y5midKtL89iXyjinruA/sNXl6iFkTcdNedydjvIsFhWLiqRPQP4fAwWQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "it-peekable": "^3.0.0" - } - }, - "node_modules/it-foreach": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/it-foreach/-/it-foreach-2.1.5.tgz", - "integrity": "sha512-9tIp+NFVODmGV/49JUKVxW3+8RrPkYrmUaXUM4W6lMC5POM/1gegckNjBmDe5xgBa7+RE9HKBmRTAdY5V+bWSQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "it-peekable": "^3.0.0" - } - }, - "node_modules/it-length-prefixed": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/it-length-prefixed/-/it-length-prefixed-10.0.1.tgz", - "integrity": "sha512-BhyluvGps26u9a7eQIpOI1YN7mFgi8lFwmiPi07whewbBARKAG9LE09Odc8s1Wtbt2MB6rNUrl7j9vvfXTJwdQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "it-reader": "^6.0.1", - "it-stream-types": "^2.0.1", - "uint8-varint": "^2.0.1", - "uint8arraylist": "^2.0.0", - "uint8arrays": "^5.0.1" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/it-length-prefixed-stream": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/it-length-prefixed-stream/-/it-length-prefixed-stream-2.0.4.tgz", - "integrity": "sha512-ugHDOQCkC2Dx2pQaJ+W4OIM6nZFBwlpgdQVVOfdX4c1Os47d6PMsfrkTrzRwZdBCMZb+JISZNP2gjU/DHN/z9A==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "abort-error": "^1.0.1", - "it-byte-stream": "^2.0.0", - "it-stream-types": "^2.0.2", - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8" - } - }, - "node_modules/it-map": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/it-map/-/it-map-3.1.4.tgz", - "integrity": "sha512-QB9PYQdE9fUfpVFYfSxBIyvKynUCgblb143c+ktTK6ZuKSKkp7iH58uYFzagqcJ5HcqIfn1xbfaralHWam+3fg==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "it-peekable": "^3.0.0" - } - }, - "node_modules/it-merge": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/it-merge/-/it-merge-3.0.12.tgz", - "integrity": "sha512-nnnFSUxKlkZVZD7c0jYw6rDxCcAQYcMsFj27thf7KkDhpj0EA0g9KHPxbFzHuDoc6US2EPS/MtplkNj8sbCx4Q==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "it-queueless-pushable": "^2.0.0" - } - }, - "node_modules/it-pair": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/it-pair/-/it-pair-2.0.6.tgz", - "integrity": "sha512-5M0t5RAcYEQYNG5BV7d7cqbdwbCAp5yLdzvkxsZmkuZsLbTdZzah6MQySYfaAQjNDCq6PUnDt0hqBZ4NwMfW6g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "it-stream-types": "^2.0.1", - "p-defer": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/it-parallel": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/it-parallel/-/it-parallel-3.0.13.tgz", - "integrity": "sha512-85PPJ/O8q97Vj9wmDTSBBXEkattwfQGruXitIzrh0RLPso6RHfiVqkuTqBNufYYtB1x6PSkh0cwvjmMIkFEPHA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "p-defer": "^4.0.1" - } - }, - "node_modules/it-peekable": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/it-peekable/-/it-peekable-3.0.8.tgz", - "integrity": "sha512-7IDBQKSp/dtBxXV3Fj0v3qM1jftJ9y9XrWLRIuU1X6RdKqWiN60syNwP0fiDxZD97b8SYM58dD3uklIk1TTQAw==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/it-pipe": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/it-pipe/-/it-pipe-3.0.1.tgz", - "integrity": "sha512-sIoNrQl1qSRg2seYSBH/3QxWhJFn9PKYvOf/bHdtCBF0bnghey44VyASsWzn5dAx0DCDDABq1hZIuzKmtBZmKA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "it-merge": "^3.0.0", - "it-pushable": "^3.1.2", - "it-stream-types": "^2.0.1" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/it-protobuf-stream": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/it-protobuf-stream/-/it-protobuf-stream-2.0.3.tgz", - "integrity": "sha512-Dus9qyylOSnC7l75/3qs6j3Fe9MCM2K5luXi9o175DYijFRne5FPucdOGIYdwaDBDQ4Oy34dNCuFobOpcusvEQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "abort-error": "^1.0.1", - "it-length-prefixed-stream": "^2.0.0", - "it-stream-types": "^2.0.2", - "uint8arraylist": "^2.4.8" - } - }, - "node_modules/it-pushable": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/it-pushable/-/it-pushable-3.2.3.tgz", - "integrity": "sha512-gzYnXYK8Y5t5b/BnJUr7glfQLO4U5vyb05gPx/TyTw+4Bv1zM9gFk4YsOrnulWefMewlphCjKkakFvj1y99Tcg==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "p-defer": "^4.0.0" - } - }, - "node_modules/it-queue": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/it-queue/-/it-queue-1.1.1.tgz", - "integrity": "sha512-yeYCV22WF1QDyb3ylw+g3TGEdkmnoHUH2mc12QoGOQuxW4XP1V7Zd3BfsEF1iq2IFBwIK7wCPUcRLTAQVeZ3SQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "abort-error": "^1.0.1", - "it-pushable": "^3.2.3", - "main-event": "^1.0.0", - "race-event": "^1.3.0", - "race-signal": "^2.0.0" - } - }, - "node_modules/it-queue/node_modules/race-signal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/race-signal/-/race-signal-2.0.0.tgz", - "integrity": "sha512-P31bLhE4ByBX/70QDXMutxnqgwrF1WUXea1O8DXuviAgkdbQ1iQMQotNgzJIBC9yUSn08u/acZrMUhgw7w6GpA==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/it-queueless-pushable": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/it-queueless-pushable/-/it-queueless-pushable-2.0.3.tgz", - "integrity": "sha512-USa5EzTvmQswOcVE7+o6qsj2o2G+6KHCxSogPOs23sGYkDWFidhqVO7dAvv6ve/Z+Q+nvxpEa9rrRo6VEK7w4Q==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "abort-error": "^1.0.1", - "p-defer": "^4.0.1", - "race-signal": "^2.0.0" - } - }, - "node_modules/it-queueless-pushable/node_modules/race-signal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/race-signal/-/race-signal-2.0.0.tgz", - "integrity": "sha512-P31bLhE4ByBX/70QDXMutxnqgwrF1WUXea1O8DXuviAgkdbQ1iQMQotNgzJIBC9yUSn08u/acZrMUhgw7w6GpA==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/it-reader": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/it-reader/-/it-reader-6.0.4.tgz", - "integrity": "sha512-XCWifEcNFFjjBHtor4Sfaj8rcpt+FkY0L6WdhD578SCDhV4VUm7fCkF3dv5a+fTcfQqvN9BsxBTvWbYO6iCjTg==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "it-stream-types": "^2.0.1", - "uint8arraylist": "^2.0.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/it-sort": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/it-sort/-/it-sort-3.0.9.tgz", - "integrity": "sha512-jsM6alGaPiQbcAJdzMsuMh00uJcI+kD9TBoScB8TR75zUFOmHvhSsPi+Dmh2zfVkcoca+14EbfeIZZXTUGH63w==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "it-all": "^3.0.0" - } - }, - "node_modules/it-stream-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/it-stream-types/-/it-stream-types-2.0.2.tgz", - "integrity": "sha512-Rz/DEZ6Byn/r9+/SBCuJhpPATDF9D+dz5pbgSUyBsCDtza6wtNATrz/jz1gDyNanC3XdLboriHnOC925bZRBww==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/it-take": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/it-take/-/it-take-3.0.9.tgz", - "integrity": "sha512-XMeUbnjOcgrhFXPUqa7H0VIjYSV/BvyxxjCp76QHVAFDJw2LmR1SHxUFiqyGeobgzJr7P2ZwSRRJQGn4D2BVlA==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/it-ws": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/it-ws/-/it-ws-6.1.5.tgz", - "integrity": "sha512-uWjMtpy5HqhSd/LlrlP3fhYrr7rUfJFFMABv0F5d6n13Q+0glhZthwUKpEAVhDrXY95Tb1RB5lLqqef+QbVNaw==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@types/ws": "^8.2.2", - "event-iterator": "^2.0.0", - "it-stream-types": "^2.0.1", - "uint8arrays": "^5.0.0", - "ws": "^8.4.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -11697,22 +9713,6 @@ "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/js-sdsl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", - "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/js-sha3": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.9.3.tgz", - "integrity": "sha512-BcJPCQeLg6WjEx3FE591wVAevlli8lxsxm9/FzV4HXkV49TmBH38Yvrpce6fjbADGMKFrBMGTqrVz3qPIZ88Gg==", - "license": "MIT" - }, "node_modules/js-tokens": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", @@ -12152,43 +10152,6 @@ "node": ">= 0.8.0" } }, - "node_modules/libp2p": { - "version": "2.8.11", - "resolved": "https://registry.npmjs.org/libp2p/-/libp2p-2.8.11.tgz", - "integrity": "sha512-EjkyN0CI6uP+e4OOkEcZvhbZtlwFl4Y0rkkMvDbXmcfILX4E4n/jKE4Ppoc1qhNufxToxVWCMDS2ipniQgiYaw==", - "license": "Apache-2.0 OR MIT", - "peer": true, - "dependencies": { - "@chainsafe/is-ip": "^2.1.0", - "@chainsafe/netmask": "^2.0.0", - "@libp2p/crypto": "^5.1.6", - "@libp2p/interface": "^2.10.4", - "@libp2p/interface-internal": "^2.3.17", - "@libp2p/logger": "^5.1.20", - "@libp2p/multistream-select": "^6.0.27", - "@libp2p/peer-collections": "^6.0.33", - "@libp2p/peer-id": "^5.1.7", - "@libp2p/peer-store": "^11.2.5", - "@libp2p/utils": "^6.7.0", - "@multiformats/dns": "^1.0.6", - "@multiformats/multiaddr": "^12.4.4", - "@multiformats/multiaddr-matcher": "^1.7.2", - "any-signal": "^4.1.1", - "datastore-core": "^10.0.2", - "interface-datastore": "^8.3.1", - "it-byte-stream": "^2.0.2", - "it-merge": "^3.0.11", - "it-parallel": "^3.0.11", - "main-event": "^1.0.1", - "multiformats": "^13.3.6", - "p-defer": "^4.0.1", - "p-retry": "^6.2.1", - "progress-events": "^1.0.1", - "race-event": "^1.3.0", - "race-signal": "^1.1.3", - "uint8arrays": "^5.1.0" - } - }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -12279,12 +10242,6 @@ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "license": "MIT" - }, "node_modules/lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -12292,12 +10249,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -12333,18 +10284,6 @@ "dev": true, "license": "MIT" }, - "node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0" - }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "license": "MIT" - }, "node_modules/lru-cache": { "version": "11.2.7", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", @@ -12383,12 +10322,6 @@ "source-map-js": "^1.2.1" } }, - "node_modules/main-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/main-event/-/main-event-1.0.1.tgz", - "integrity": "sha512-NWtdGrAca/69fm6DIVd8T9rtfDII4Q8NQbIbsKQq2VzS9eqOGYs8uaNQjcuaCq/d9H/o625aOTJX2Qoxzqw0Pw==", - "license": "Apache-2.0 OR MIT" - }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -12522,18 +10455,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/minimatch": { "version": "10.2.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", @@ -12553,6 +10474,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -12575,12 +10497,6 @@ "dev": true, "license": "MIT" }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, "node_modules/modern-tar": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.6.tgz", @@ -12601,157 +10517,11 @@ "node": "*" } }, - "node_modules/mortice": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/mortice/-/mortice-3.3.1.tgz", - "integrity": "sha512-t3oESfijIPGsmsdLEKjF+grHfrbnKSXflJtgb1wY14cjxZpS6GnhHRXTxxzCAoCCnq1YYfpEPwY3gjiCPhOufQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "abort-error": "^1.0.0", - "it-queue": "^1.1.0", - "main-event": "^1.0.0" - } - }, - "node_modules/mqtt": { - "version": "5.15.1", - "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.15.1.tgz", - "integrity": "sha512-V1WnkGuJh3ec9QXzy5Iylw8OOBK+Xu1WhxcQ9mMpLThG+/JZIMV1PgLNRgIiqXhZnvnVLsuyxHl5A/3bHHbcAA==", - "license": "MIT", - "dependencies": { - "@types/readable-stream": "^4.0.21", - "@types/ws": "^8.18.1", - "commist": "^3.2.0", - "concat-stream": "^2.0.0", - "debug": "^4.4.1", - "help-me": "^5.0.0", - "lru-cache": "^10.4.3", - "minimist": "^1.2.8", - "mqtt-packet": "^9.0.2", - "number-allocator": "^1.0.14", - "readable-stream": "^4.7.0", - "rfdc": "^1.4.1", - "socks": "^2.8.6", - "split2": "^4.2.0", - "worker-timers": "^8.0.23", - "ws": "^8.18.3" - }, - "bin": { - "mqtt": "build/bin/mqtt.js", - "mqtt_pub": "build/bin/pub.js", - "mqtt_sub": "build/bin/sub.js" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/mqtt-packet": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-9.0.2.tgz", - "integrity": "sha512-MvIY0B8/qjq7bKxdN1eD+nrljoeaai+qjLJgfRn3TiMuz0pamsIWY2bFODPZMSNmabsLANXsLl4EMoWvlaTZWA==", - "license": "MIT", - "dependencies": { - "bl": "^6.0.8", - "debug": "^4.3.4", - "process-nextick-args": "^2.0.1" - } - }, - "node_modules/mqtt-packet/node_modules/bl": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.6.tgz", - "integrity": "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==", - "license": "MIT", - "dependencies": { - "@types/readable-stream": "^4.0.0", - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^4.2.0" - } - }, - "node_modules/mqtt-packet/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/mqtt-packet/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/mqtt/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/mqtt/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/mqtt/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } + "node_modules/mp4box": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/mp4box/-/mp4box-0.5.4.tgz", + "integrity": "sha512-GcCH0fySxBurJtvr0dfhz0IxHZjc1RP+F+I8xw+LIwkU1a+7HJx8NCDiww1I5u4Hz6g4eR1JlGADEGJ9r4lSfA==", + "license": "BSD-3-Clause" }, "node_modules/mri": { "version": "1.2.0", @@ -12779,11 +10549,18 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", - "license": "Apache-2.0 OR MIT" + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } }, "node_modules/nanoid": { "version": "3.3.11", @@ -12804,12 +10581,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" - }, "node_modules/napi-macros": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", @@ -12827,36 +10598,12 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4.0" } }, - "node_modules/node-abi": { - "version": "3.89.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", - "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-datachannel": { - "version": "0.32.1", - "resolved": "https://registry.npmjs.org/node-datachannel/-/node-datachannel-0.32.1.tgz", - "integrity": "sha512-r4UdtA0lCsz6XrG84pJ6lntAyw/MHpmBOhEkg5UQcmWTEpANqCPkMos6rj/QZDdq3GBUsdI/wst5acwWUiibCA==", - "hasInstallScript": true, - "license": "MPL 2.0", - "dependencies": { - "prebuild-install": "^7.1.3" - }, - "engines": { - "node": ">=18.20.0" - } - }, "node_modules/node-fetch": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", @@ -12918,16 +10665,6 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/number-allocator": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", - "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.1", - "js-sdsl": "4.3.0" - } - }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -13064,6 +10801,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -13125,31 +10863,13 @@ "@oxc-parser/binding-win32-x64-msvc": "0.8.0" } }, - "node_modules/p-defer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-4.0.1.tgz", - "integrity": "sha512-Mr5KC5efvAK5VUptYEIopP1bakB85k2IWXaRC0rsh1uwn1L6M0LVml8OIQ4Gudg4oyZakf7FmeRLkMMtZW1i5A==", + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-event": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", - "integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==", - "license": "MIT", - "dependencies": { - "p-timeout": "^6.1.2" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/p-limit": { @@ -13184,63 +10904,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-queue": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.1.0.tgz", - "integrity": "sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==", - "license": "MIT", - "dependencies": { - "eventemitter3": "^5.0.1", - "p-timeout": "^7.0.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-queue/node_modules/p-timeout": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", - "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", - "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", - "license": "MIT", - "dependencies": { - "@types/retry": "0.12.2", - "is-network-error": "^1.0.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", - "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -13451,15 +11114,6 @@ "dev": true, "license": "MIT" }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -14050,42 +11704,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/prebuild-install/node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -14116,6 +11734,7 @@ "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6.0" @@ -14125,6 +11744,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, "license": "MIT" }, "node_modules/progress": { @@ -14137,47 +11757,6 @@ "node": ">=0.4.0" } }, - "node_modules/progress-events": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/progress-events/-/progress-events-1.0.1.tgz", - "integrity": "sha512-MOzLIwhpt64KIVN64h1MwdKWiyKFNc/S6BoYKPIVUHFg0/eIEyBulhWCgn678v/4c0ri3FdGuzXymNCv02MUIw==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/protobufjs": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", - "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/protons-runtime": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-5.6.0.tgz", - "integrity": "sha512-/Kde+sB9DsMFrddJT/UZWe6XqvL7SL5dbag/DBCElFKhkwDj7XKt53S+mzLyaDP5OqS0wXjV5SA572uWDaT0Hg==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "uint8-varint": "^2.0.2", - "uint8arraylist": "^2.4.3", - "uint8arrays": "^5.0.1" - } - }, "node_modules/proxy-agent": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", @@ -14237,6 +11816,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", @@ -14261,6 +11841,24 @@ "node": ">=6" } }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/qrcode-generator": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.5.2.tgz", @@ -14300,57 +11898,6 @@ ], "license": "MIT" }, - "node_modules/quick-lru": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-7.3.0.tgz", - "integrity": "sha512-k9lSsjl36EJdK7I06v7APZCbyGT2vMTsYSRX1Q2nbYmnkBqgUhRkAuzH08Ciotteu/PLJmIF2+tti7o3C/ts2g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/race-event": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/race-event/-/race-event-1.6.1.tgz", - "integrity": "sha512-vi7WH5g5KoTFpu2mme/HqZiWH14XSOtg5rfp6raBskBHl7wnmy3F/biAIyY5MsK+BHWhoPhxtZ1Y2R7OHHaWyQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "abort-error": "^1.0.1" - } - }, - "node_modules/race-signal": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/race-signal/-/race-signal-1.1.3.tgz", - "integrity": "sha512-Mt2NznMgepLfORijhQMncE26IhkmjEphig+/1fKC0OtaKwys/gpvpmswSjoN01SS+VO951mj0L4VIDXdXsjnfA==", - "license": "Apache-2.0 OR MIT" - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -14419,6 +11966,12 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -14467,6 +12020,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -14546,15 +12100,6 @@ "node": ">=10" } }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -14566,12 +12111,6 @@ "node": ">=0.10.0" } }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "license": "MIT" - }, "node_modules/rgb2hex": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.5.tgz", @@ -14665,6 +12204,11 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rx.mini": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/rx.mini/-/rx.mini-1.4.0.tgz", + "integrity": "sha512-8w5cSc1mwNja7fl465DXOkVvIOkpvh2GW4jo31nAIvX4WTXCsRnKJGUfiDBzWtYRInEcHAUYIZfzusjIrea8gA==" + }, "node_modules/sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", @@ -14712,6 +12256,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, "license": "MIT" }, "node_modules/safe-push-apply": { @@ -14783,6 +12328,7 @@ "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -15001,51 +12547,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, "node_modules/sirv": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", @@ -15075,6 +12576,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6.0.0", @@ -15085,6 +12587,7 @@ "version": "2.8.7", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, "license": "MIT", "dependencies": { "ip-address": "^10.0.1", @@ -15168,6 +12671,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, "license": "ISC", "engines": { "node": ">= 10.x" @@ -15253,6 +12757,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -15306,6 +12811,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -15315,6 +12821,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -15694,34 +13201,6 @@ } } }, - "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/teex": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", @@ -15793,6 +13272,12 @@ "readable-stream": "2 || 3" } }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "license": "MIT" + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -15932,19 +13417,6 @@ "pouchdb-wrappers": "^5.0.0" } }, - "node_modules/trystero": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/trystero/-/trystero-0.22.0.tgz", - "integrity": "sha512-VscO7kaTFWNLmuxu1Au1kIxX6FzkVeXcL3+mhb9MaCSz8fm4T5MFWkdfDOujMtNK4iztupQ5AGEqGniP/I8Gvw==", - "license": "MIT", - "dependencies": { - "@noble/secp256k1": "^3.0.0", - "@supabase/supabase-js": "^2.75.0", - "@waku/sdk": "^0.0.35", - "firebase": "^12.4.0", - "mqtt": "^5.14.1" - } - }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -16480,18 +13952,30 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "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==", - "license": "Apache-2.0", + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "license": "MIT", "dependencies": { - "safe-buffer": "^5.0.1" + "tslib": "^1.9.3" }, "engines": { - "node": "*" + "node": ">= 6.0.0" } }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -16505,15 +13989,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/type-fest": { "version": "4.26.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.0.tgz", @@ -16605,12 +14080,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "license": "MIT" - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -16632,34 +14101,6 @@ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "license": "MIT" }, - "node_modules/uint8-varint": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-2.0.4.tgz", - "integrity": "sha512-FwpTa7ZGA/f/EssWAb5/YV6pHgVF1fViKdW8cWaEarjB8t7NyofSWBdOTyFPaGuUG4gx3v1O3PQ8etsiOs3lcw==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "uint8arraylist": "^2.0.0", - "uint8arrays": "^5.0.0" - } - }, - "node_modules/uint8arraylist": { - "version": "2.4.8", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-2.4.8.tgz", - "integrity": "sha512-vc1PlGOzglLF0eae1M8mLRTBivsvrGsdmJ5RbK3e+QRvRLOZfZhQROTwH/OfyF3+ZVUg9/8hE8bmKP2CvP9quQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "uint8arrays": "^5.0.1" - } - }, - "node_modules/uint8arrays": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", - "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "multiformats": "^13.0.0" - } - }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -16693,6 +14134,7 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, "license": "MIT" }, "node_modules/universalify": { @@ -16773,12 +14215,6 @@ "node": ">= 0.8.0" } }, - "node_modules/utf8-codec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/utf8-codec/-/utf8-codec-1.0.0.tgz", - "integrity": "sha512-S/QSLezp3qvG4ld5PUfXiH7mCFxLKjSVZRFkB3DOjgwHuJPFDkInAXc/anf7BAbHt/D38ozDzL+QMZ6/7gsI6w==", - "license": "MIT" - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -17609,43 +15045,6 @@ "node": "^12.20.0 || >=14" } }, - "node_modules/weald": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/weald/-/weald-1.1.1.tgz", - "integrity": "sha512-PaEQShzMCz8J/AD2N3dJMc1hTZWkJeLKS2NMeiVkV5KDHwgZe7qXLEzyodsT/SODxWDdXJJqocuwf3kHzcXhSQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "ms": "^3.0.0-canary.1", - "supports-color": "^10.0.0" - } - }, - "node_modules/weald/node_modules/ms": { - "version": "3.0.0-canary.202508261828", - "resolved": "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.202508261828.tgz", - "integrity": "sha512-NotsCoUCIUkojWCzQff4ttdCfIPoA1UGZsyQbi7KmqkNRfKCrvga8JJi2PknHymHOuor0cJSn/ylj52Cbt2IrQ==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/weald/node_modules/supports-color": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", - "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/web-vitals": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", - "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==", - "license": "Apache-2.0" - }, "node_modules/webdriver": { "version": "9.27.0", "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.27.0.tgz", @@ -17764,27 +15163,177 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "license": "BSD-2-Clause" }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "license": "Apache-2.0", + "node_modules/werift": { + "version": "0.22.9", + "resolved": "https://registry.npmjs.org/werift/-/werift-0.22.9.tgz", + "integrity": "sha512-TE9AxskSRWBMYm0MBRllfnKVXQelqC76JCvyolQyVWpmKabfY5BA/cuvkGg+71JWn3QrGih1YWtpIWGPqoxcoA==", + "license": "MIT", "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" + "@fidm/x509": "^1.2.1", + "@minhducsun2002/leb128": "^1.0.0", + "@noble/curves": "^1.8.1", + "@peculiar/x509": "^1.12.3", + "@shinyoshiaki/binary-data": "^0.6.1", + "@shinyoshiaki/jspack": "^0.0.6", + "aes-js": "^3.1.2", + "buffer": "^6.0.3", + "debug": "4.4.0", + "fast-deep-equal": "^3.1.3", + "int64-buffer": "1.1.0", + "ip": "^2.0.1", + "mp4box": "^0.5.3", + "multicast-dns": "^7.2.5", + "tweetnacl": "^1.0.3", + "werift-common": "*", + "werift-dtls": "*", + "werift-ice": "*", + "werift-rtp": "*", + "werift-sctp": "*" }, "engines": { - "node": ">=0.8.0" + "node": ">=16" } }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "license": "Apache-2.0", + "node_modules/werift-common": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/werift-common/-/werift-common-0.0.3.tgz", + "integrity": "sha512-ma3E4BqKTyZVLhrdfTVs2T1tg9seeUtKMRn5e64LwgrogWa62+3LAUoLBUSl1yPWhgSkXId7GmcHuWDen9IJeQ==", + "license": "MIT", + "dependencies": { + "@shinyoshiaki/jspack": "^0.0.6", + "debug": "^4.4.0" + }, "engines": { - "node": ">=0.8.0" + "node": ">=16" + } + }, + "node_modules/werift-dtls": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/werift-dtls/-/werift-dtls-0.5.7.tgz", + "integrity": "sha512-z2fjbP7fFUFmu/Ky4bCKXzdgPTtmSY1DYi0TUf3GG2zJT4jMQ3TQmGY8y7BSSNGetvL4h3pRZ5un0EcSOWpPog==", + "license": "MIT", + "dependencies": { + "@fidm/x509": "^1.2.1", + "@noble/curves": "^1.3.0", + "@peculiar/x509": "^1.9.2", + "@shinyoshiaki/binary-data": "^0.6.1", + "date-fns": "^2.29.3", + "lodash": "^4.17.21", + "rx.mini": "^1.2.2", + "tweetnacl": "^1.0.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/werift-ice": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/werift-ice/-/werift-ice-0.2.2.tgz", + "integrity": "sha512-td52pHp+JmFnUn5jfDr/SSNO0dMCbknhuPdN1tFp9cfRj5jaktN63qnAdUuZC20QCC3ETWdsOthcm+RalHpFCQ==", + "license": "MIT", + "dependencies": { + "@shinyoshiaki/jspack": "^0.0.6", + "buffer-crc32": "^1.0.0", + "debug": "^4.3.4", + "int64-buffer": "^1.0.1", + "ip": "^2.0.1", + "lodash": "^4.17.21", + "multicast-dns": "^7.2.5", + "p-cancelable": "^2.1.1", + "rx.mini": "^1.2.2" + } + }, + "node_modules/werift-rtp": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/werift-rtp/-/werift-rtp-0.8.8.tgz", + "integrity": "sha512-GiYMSdvCyScQaw5bnEsraSoHUVZpjfokJAiLV4R1FsiB06t6XiebPYPpkqB9nYNNKiA8Z/cYWsym7wISq1sYSQ==", + "license": "MIT", + "dependencies": { + "@minhducsun2002/leb128": "^1.0.0", + "@shinyoshiaki/jspack": "^0.0.6", + "aes-js": "^3.1.2", + "buffer": "^6.0.3", + "mp4box": "^0.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/werift-rtp/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/werift-sctp": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/werift-sctp/-/werift-sctp-0.0.11.tgz", + "integrity": "sha512-7109yuI5U7NTEHjqjn0A8VeynytkgVaxM6lRr1Ziv0D8bPcaB8A7U/P88M7WaCpWDoELHoXiRUjQycMWStIgjQ==", + "license": "MIT", + "dependencies": { + "@shinyoshiaki/jspack": "^0.0.6" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/werift/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/werift/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/whatwg-encoding": { @@ -17821,19 +15370,6 @@ "webidl-conversions": "^3.0.0" } }, - "node_modules/wherearewe": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wherearewe/-/wherearewe-2.0.1.tgz", - "integrity": "sha512-XUguZbDxCA2wBn2LoFtcEhXL6AXo+hVjGonwhSTTTU9SzbWG8Xu3onNIpzf9j/mYUcJQ0f+m37SzG77G851uFw==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "is-electron": "^2.2.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -17966,57 +15502,11 @@ "node": ">=0.10.0" } }, - "node_modules/worker-factory": { - "version": "7.0.49", - "resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.49.tgz", - "integrity": "sha512-lW7tpgy6aUv2dFsQhv1yv+XFzdkCf/leoKRTGMPVK5/die6RrUjqgJHJf556qO+ZfytNG6wPXc17E8zzsOLUDw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.29.2", - "fast-unique-numbers": "^9.0.27", - "tslib": "^2.8.1" - } - }, - "node_modules/worker-timers": { - "version": "8.0.31", - "resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-8.0.31.tgz", - "integrity": "sha512-ngkq5S6JuZyztom8tDgBzorLo9byhBMko/sXfgiUD945AuzKGg1GCgDMCC3NaYkicLpGKXutONM36wEX8UbBCA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.29.2", - "tslib": "^2.8.1", - "worker-timers-broker": "^8.0.16", - "worker-timers-worker": "^9.0.14" - } - }, - "node_modules/worker-timers-broker": { - "version": "8.0.16", - "resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-8.0.16.tgz", - "integrity": "sha512-JyP3AvUGyPGbBGW7XiUewm2+0pN/aYo1QpVf5kdXAfkDZcN3p7NbWrG6XnyDEpDIvfHk/+LCnOW/NsuiU9riYA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.29.2", - "broker-factory": "^3.1.14", - "fast-unique-numbers": "^9.0.27", - "tslib": "^2.8.1", - "worker-timers-worker": "^9.0.14" - } - }, - "node_modules/worker-timers-worker": { - "version": "9.0.14", - "resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-9.0.14.tgz", - "integrity": "sha512-/qF06C60sXmSLfUl7WglvrDIbspmPOM8UrG63Dnn4bi2x4/DfqHS/+dxF5B+MdHnYO5tVuZYLHdAodrKdabTIg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.29.2", - "tslib": "^2.8.1", - "worker-factory": "^7.0.49" - } - }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -18076,6 +15566,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -18085,6 +15576,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -18097,6 +15589,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, "license": "ISC" }, "node_modules/write-stream": { @@ -18117,6 +15610,7 @@ "version": "8.20.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" @@ -18154,6 +15648,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -18187,6 +15682,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -18205,6 +15701,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" diff --git a/package.json b/package.json index dc74e70..fc2d258 100644 --- a/package.json +++ b/package.json @@ -132,17 +132,17 @@ "@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", "commander": "^14.0.3", "diff-match-patch": "^1.0.5", "fflate": "^0.8.2", "idb": "^8.0.3", "markdown-it": "^14.1.1", "minimatch": "^10.2.2", - "node-datachannel": "^0.32.1", "octagonal-wheels": "^0.1.45", "pouchdb-adapter-leveldb": "^9.0.0", "qrcode-generator": "^1.4.4", - "trystero": "^0.22.0", + "werift": "^0.22.9", "xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2" } } diff --git a/src/apps/cli/.gitignore b/src/apps/cli/.gitignore index 9ab466a..69dd5ab 100644 --- a/src/apps/cli/.gitignore +++ b/src/apps/cli/.gitignore @@ -1,5 +1,6 @@ -.livesync -test/* -!test/*.sh -node_modules +.livesync +test/* +!test/*.sh +test/test-init.local.sh +node_modules .*.json \ No newline at end of file diff --git a/src/apps/cli/Dockerfile b/src/apps/cli/Dockerfile new file mode 100644 index 0000000..7e85bdb --- /dev/null +++ b/src/apps/cli/Dockerfile @@ -0,0 +1,111 @@ +# syntax=docker/dockerfile:1 +# +# Self-hosted LiveSync CLI — Docker image +# +# Build (from the repository root): +# docker build -f src/apps/cli/Dockerfile -t livesync-cli . +# +# Run: +# docker run --rm -v /path/to/your/vault:/data livesync-cli sync +# docker run --rm -v /path/to/your/vault:/data livesync-cli ls +# docker run --rm -v /path/to/your/vault:/data livesync-cli init-settings +# docker run --rm -v /path/to/your/vault:/data livesync-cli --help +# +# The first positional argument (database-path) is automatically set to /data. +# Mount your vault at /data, or override with: -e LIVESYNC_DB_PATH=/other/path +# +# P2P (WebRTC) networking — important notes +# ----------------------------------------- +# The P2P replicator (p2p-host / p2p-sync / p2p-peers) uses WebRTC, which +# generates ICE candidates of three kinds: +# +# host — the container's bridge IP (172.17.x.x). Unreachable from outside +# the Docker bridge, so LAN peers cannot connect via this candidate. +# srflx — the host's public IP, obtained via STUN reflection. Works fine +# over the internet even with the default bridge network. +# relay — traffic relayed through a TURN server. Always reachable regardless +# of network mode. +# +# Recommended network modes per use-case: +# +# LAN P2P (Linux only) +# docker run --network host ... +# This exposes the real host IP as the 'host' candidate so LAN peers can +# connect directly. --network host is not available on Docker Desktop for +# macOS or Windows. +# +# LAN P2P (macOS / Windows Docker Desktop) +# Configure a TURN server in settings (P2P_turnServers / P2P_turnUsername / +# P2P_turnCredential). All data is then relayed through the TURN server, +# bypassing the bridge-network limitation. +# +# Internet P2P +# Default bridge network is sufficient; the srflx candidate carries the +# host's public IP and peers can connect normally. +# +# CouchDB sync only (no P2P) +# Default bridge network. No special configuration required. + +# ───────────────────────────────────────────────────────────────────────────── +# Stage 1 — builder +# Full Node.js environment to compile native modules and bundle the CLI. +# ───────────────────────────────────────────────────────────────────────────── +FROM node:22-slim AS builder + +# Build tools required by native Node.js addons (mainly leveldown) +RUN apt-get update \ + && apt-get install -y --no-install-recommends python3 make g++ \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /build + +# Install workspace dependencies first (layer-cache friendly) +COPY package.json ./ +RUN npm install + +# Copy the full source tree and build the CLI bundle +COPY . . +RUN cd src/apps/cli && npm run build + +# ───────────────────────────────────────────────────────────────────────────── +# Stage 2 — runtime-deps +# Install only the external (unbundled) packages that the CLI requires at +# runtime. Native addons are compiled here against the same base image that +# the final runtime stage uses. +# ───────────────────────────────────────────────────────────────────────────── +FROM node:22-slim AS runtime-deps + +# Build tools required to compile native addons +RUN apt-get update \ + && apt-get install -y --no-install-recommends python3 make g++ \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /deps + +# runtime-package.json lists only the packages that Vite leaves external +COPY src/apps/cli/runtime-package.json ./package.json +RUN npm install --omit=dev + +# ───────────────────────────────────────────────────────────────────────────── +# Stage 3 — runtime +# Minimal image: pre-compiled native modules + CLI bundle only. +# No build tools are included, keeping the image small. +# ───────────────────────────────────────────────────────────────────────────── +FROM node:22-slim + +WORKDIR /app + +# Copy pre-compiled external node_modules from runtime-deps stage +COPY --from=runtime-deps /deps/node_modules ./node_modules + +# Copy the built CLI bundle from builder stage +COPY --from=builder /build/src/apps/cli/dist ./dist + +# Install entrypoint wrapper +COPY src/apps/cli/docker-entrypoint.sh /usr/local/bin/livesync-cli +RUN chmod +x /usr/local/bin/livesync-cli + +# Mount your vault / local database directory here +VOLUME ["/data"] + +ENTRYPOINT ["livesync-cli"] diff --git a/src/apps/cli/README.md b/src/apps/cli/README.md index 9f53639..08f8493 100644 --- a/src/apps/cli/README.md +++ b/src/apps/cli/README.md @@ -1,362 +1,416 @@ -# Self-hosted LiveSync CLI -Command-line version of Self-hosted LiveSync plugin for syncing vaults without Obsidian. - -## Features - -- ✅ Sync Obsidian vaults using CouchDB without running Obsidian -- ✅ Compatible with Self-hosted LiveSync plugin settings -- ✅ Supports all core sync features (encryption, conflict resolution, etc.) -- ✅ Lightweight and headless operation -- ✅ Cross-platform (Windows, macOS, Linux) - -## Architecture - -This CLI version is built using the same core as the Obsidian plugin: - -``` -CLI Main - └─ LiveSyncBaseCore - ├─ NodeServiceHub (All services without Obsidian dependencies) - └─ ServiceModules (wired by initialiseServiceModulesCLI) - ├─ FileAccessCLI (Node.js FileSystemAdapter) - ├─ StorageEventManagerCLI - ├─ ServiceFileAccessCLI - ├─ ServiceDatabaseFileAccessCLI - ├─ ServiceFileHandler - └─ ServiceRebuilder -``` - -### Key Components - -1. **Node.js FileSystem Adapter** (`adapters/`) - - Platform-agnostic file operations using Node.js `fs/promises` - - Implements same interface as Obsidian's file system - -2. **Service Modules** (`serviceModules/`) - - Initialised by `initialiseServiceModulesCLI` - - All core sync functionality preserved - -3. **Service Hub and Settings Services** (`services/`) - - `NodeServiceHub` provides the CLI service context - - Node-specific settings and key-value services are provided without Obsidian dependencies - -4. **Main Entry Point** (`main.ts`) - - Command-line interface - - Settings management (JSON file) - - Graceful shutdown handling - -## Installation - -```bash -# Install dependencies (ensure you are in repository root directory, not src/apps/cli) -# due to shared dependencies with webapp and main library -npm install -# Build the project (ensure you are in `src/apps/cli` directory) -npm run build -``` - -## Usage - -### Basic Usage - -As you know, the CLI is designed to be used in a headless environment. Hence all operations are performed against a local vault directory and a settings file. Here are some example commands: - -```bash -# Sync local database with CouchDB (no files will be changed). -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json sync - -# Push files to local database -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json push /your/storage/file.md /vault/path/file.md - -# Pull files from local database -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json pull /vault/path/file.md /your/storage/file.md - -# Verbose logging -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json --verbose - -# Apply setup URI to settings file (settings only; does not run synchronisation) -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json setup "obsidian://setuplivesync?settings=..." - -# Put text from stdin into local database -echo "Hello from stdin" | npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json put /vault/path/file.md - -# Output a file from local database to stdout -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json cat /vault/path/file.md - -# Output a specific revision of a file from local database -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json cat-rev /vault/path/file.md 3-abcdef - -# Pull a specific revision of a file from local database to local storage -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json pull-rev /vault/path/file.md /your/storage/file.old.md 3-abcdef - -# List files in local database -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json ls /vault/path/ - -# Show metadata for a file in local database -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json info /vault/path/file.md - -# Mark a file as deleted in local database -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json rm /vault/path/file.md - -# Resolve conflict by keeping a specific revision -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json resolve /vault/path/file.md 3-abcdef -``` - -### Configuration - -The CLI uses the same settings format as the Obsidian plugin. Create a `.livesync/settings.json` file in your vault directory: - -```json -{ - "couchDB_URI": "http://localhost:5984", - "couchDB_USER": "admin", - "couchDB_PASSWORD": "password", - "couchDB_DBNAME": "obsidian-livesync", - "liveSync": true, - "syncOnSave": true, - "syncOnStart": true, - "encrypt": true, - "passphrase": "your-encryption-passphrase", - "usePluginSync": false, - "isConfigured": true -} -``` - -**Minimum required settings:** - -- `couchDB_URI`: CouchDB server URL -- `couchDB_USER`: CouchDB username -- `couchDB_PASSWORD`: CouchDB password -- `couchDB_DBNAME`: Database name -- `isConfigured`: Set to `true` after configuration - -### Command-line Reference - -``` -Usage: - livesync-cli [database-path] [options] [command] [command-args] - -Arguments: - database-path Path to the local database directory (required except for init-settings) - -Options: - --settings, -s Path to settings file (default: .livesync/settings.json in local database directory) - --force, -f Overwrite existing file on init-settings - --verbose, -v Enable verbose logging - --help, -h Show this help message - -Commands: - init-settings [path] Create settings JSON from DEFAULT_SETTINGS - sync Run one replication cycle and exit - p2p-peers Show discovered peers as [peer] - p2p-sync Synchronise with specified peer-id or peer-name - p2p-host Start P2P host mode and wait until interrupted (Ctrl+C) - push Push local file into local database path - pull Pull file from local database into local file - pull-rev Pull specific revision into local file - setup Apply setup URI to settings file - put Read text from standard input and write to local database - cat Write latest file content from local database to standard output - cat-rev Write specific revision content from local database to standard output - ls [prefix] List files as pathsizemtimerevision[*] - info Show file metadata including current and past revisions, conflicts, and chunk list - rm Mark file as deleted in local database - resolve Resolve conflict by keeping the specified revision - mirror Mirror local file into local database. -``` - -Run via npm script: - -```bash -npm run --silent cli -- [database-path] [options] [command] [command-args] -``` - -#### Detailed Command Descriptions - -##### ls -`ls` lists files in the local database with optional prefix filtering. Output format is: - -```vault/path/file.mdsizemtimerevision[*] -``` -Note: `*` indicates if the file has conflicts. - -##### p2p-peers - -`p2p-peers ` waits for the specified number of seconds, then prints each discovered peer on a separate line: - -```text -[peer] -``` - -Use this command to select a target for `p2p-sync`. - -##### p2p-sync - -`p2p-sync ` discovers peers up to the specified timeout and synchronises with the selected peer. - -- `` accepts either `peer-id` or `peer-name` from `p2p-peers` output. -- On success, the command prints a completion message to standard error and exits with status code `0`. -- On failure, the command prints an error message and exits non-zero. - -##### p2p-host - -`p2p-host` starts the local P2P host and keeps running until interrupted. - -- Other peers can discover and synchronise with this host while it is running. -- Stop the host with `Ctrl+C`. -- In CLI mode, behaviour is non-interactive and acceptance follows settings. - -##### info - -`info` output fields: - -- `id`: Document ID -- `revision`: Current revision -- `conflicts`: Conflicted revisions, or `N/A` -- `filename`: Basename of path -- `path`: Vault-relative path -- `size`: Size in bytes -- `revisions`: Available non-current revisions -- `chunks`: Number of chunk IDs -- `children`: Chunk ID list - -##### mirror - -`mirror` is a command that synchronises your storage with your local vault. It is essentially a process that runs upon startup in Obsidian. - -In other words, it performs the following actions: - -1. **Precondition checks** — Aborts early if any of the following conditions are not met: - - Settings must be configured (`isConfigured: true`). - - File watching must not be suspended (`suspendFileWatching: false`). - - Remediation mode must be inactive (`maxMTimeForReflectEvents: 0`). - -2. **State restoration** — On subsequent runs (after the first successful scan), restores the previous storage state before proceeding. - -3. **Expired deletion cleanup** — If `automaticallyDeleteMetadataOfDeletedFiles` is set to a positive number of days, any document that is marked deleted and whose `mtime` is older than the retention period is permanently removed from the local database. - -4. **File collection** — Enumerates files from two sources: - - **Storage**: all files under the vault path that pass `isTargetFile`. - - **Local database**: all normal documents (fetched with conflict information) whose paths are valid and pass `isTargetFile`. - - Both collections build case-insensitive ↔ case-sensitive path maps, controlled by `handleFilenameCaseSensitive`. - -5. **Categorisation and synchronisation** — The union of both file sets is split into three groups and processed concurrently (up to 10 files at a time): - - | Group | Condition | Action | - |---|---|---| - | **UPDATE DATABASE** | File exists in storage only | Store the file into the local database. | - | **UPDATE STORAGE** | File exists in database only | If the entry is active (not deleted) and not conflicted, restore the file from the database to storage. Deleted entries and conflicted entries are skipped. | - | **SYNC DATABASE AND STORAGE** | File exists in both | Compare `mtime` freshness. If storage is newer → write to database (`STORAGE → DB`). If database is newer → restore to storage (`STORAGE ← DB`). If equal → do nothing. Conflicted documents and files exceeding the size limit are always skipped. | - -6. **Initialisation flag** — On the very first successful run, writes `initialized = true` to the key-value database so that subsequent runs can restore state in step 2. - -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). - -### Planned options: - -- `--immediate`: Perform sync after the command (e.g. `push`, `pull`, `put`, `rm`). -- `serve`: Start CLI in server mode, exposing REST APIs for remote, and batch operations. -- `cause-conflicted `: Mark a file as conflicted without changing its content, to trigger conflict resolution in Obsidian. - -## Use Cases - -### 1. Bootstrap a new headless vault - -Create default settings, apply a setup URI, then run one sync cycle. - -```bash -npm run --silent cli -- init-settings /data/livesync-settings.json -printf '%s\n' "$SETUP_PASSPHRASE" | npm run --silent cli -- /data/vault --settings /data/livesync-settings.json setup "$SETUP_URI" -npm run --silent cli -- /data/vault --settings /data/livesync-settings.json sync -``` - -### 2. Scripted import and export - -Push local files into the database from automation, and pull them back for export or backup. - -```bash -npm run --silent cli -- /data/vault --settings /data/livesync-settings.json push ./note.md notes/note.md -npm run --silent cli -- /data/vault --settings /data/livesync-settings.json pull notes/note.md ./exports/note.md -``` - -### 3. Revision inspection and restore - -List metadata, find an older revision, then restore it by content (`cat-rev`) or file output (`pull-rev`). - -```bash -npm run --silent cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md -npm run --silent cli -- /data/vault --settings /data/livesync-settings.json cat-rev notes/note.md 3-abcdef -npm run --silent cli -- /data/vault --settings /data/livesync-settings.json pull-rev notes/note.md ./restore/note.old.md 3-abcdef -``` - -### 4. Conflict and cleanup workflow - -Inspect conflicted revisions, resolve by keeping one revision, then delete obsolete files. - -```bash -npm run --silent cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md -npm run --silent cli -- /data/vault --settings /data/livesync-settings.json resolve notes/note.md 3-abcdef -npm run --silent cli -- /data/vault --settings /data/livesync-settings.json rm notes/obsolete.md -``` - -### 5. CI smoke test for content round-trip - -Validate that `put`/`cat` is behaving as expected in a pipeline. - -```bash -echo "hello-ci" | npm run --silent cli -- /data/vault --settings /data/livesync-settings.json put ci/test.md -npm run --silent cli -- /data/vault --settings /data/livesync-settings.json cat ci/test.md -``` - -## Development - -### Project Structure - -``` -src/apps/cli/ -├── commands/ # Command dispatcher and command utilities -│ ├── runCommand.ts -│ ├── runCommand.unit.spec.ts -│ ├── types.ts -│ ├── utils.ts -│ └── utils.unit.spec.ts -├── adapters/ # Node.js FileSystem Adapter -│ ├── NodeConversionAdapter.ts -│ ├── NodeFileSystemAdapter.ts -│ ├── NodePathAdapter.ts -│ ├── NodeStorageAdapter.ts -│ ├── NodeStorageAdapter.unit.spec.ts -│ ├── NodeTypeGuardAdapter.ts -│ ├── NodeTypes.ts -│ └── NodeVaultAdapter.ts -├── lib/ -│ └── pouchdb-node.ts -├── managers/ # CLI-specific managers -│ ├── CLIStorageEventManagerAdapter.ts -│ └── StorageEventManagerCLI.ts -├── serviceModules/ # Service modules (ported from main.ts) -│ ├── CLIServiceModules.ts -│ ├── DatabaseFileAccess.ts -│ ├── FileAccessCLI.ts -│ └── ServiceFileAccessImpl.ts -├── services/ -│ ├── NodeKeyValueDBService.ts -│ ├── NodeServiceHub.ts -│ └── NodeSettingService.ts -├── test/ -│ ├── test-e2e-two-vaults-common.sh -│ ├── test-e2e-two-vaults-matrix.sh -│ ├── test-e2e-two-vaults-with-docker-linux.sh -│ ├── test-push-pull-linux.sh -│ ├── test-setup-put-cat-linux.sh -│ └── test-sync-two-local-databases-linux.sh -├── .gitignore -├── entrypoint.ts # CLI executable entry point (shebang) -├── main.ts # CLI entry point -├── main.unit.spec.ts -├── package.json -├── README.md # This file -├── tsconfig.json -├── util/ # Test and local utility scripts -└── vite.config.ts -``` +# Self-hosted LiveSync CLI +Command-line version of Self-hosted LiveSync plugin for syncing vaults without Obsidian. + +## Features + +- ✅ Sync Obsidian vaults using CouchDB without running Obsidian +- ✅ Compatible with Self-hosted LiveSync plugin settings +- ✅ Supports all core sync features (encryption, conflict resolution, etc.) +- ✅ Lightweight and headless operation +- ✅ Cross-platform (Windows, macOS, Linux) + +## Architecture + +This CLI version is built using the same core as the Obsidian plugin: + +``` +CLI Main + └─ LiveSyncBaseCore + ├─ NodeServiceHub (All services without Obsidian dependencies) + └─ ServiceModules (wired by initialiseServiceModulesCLI) + ├─ FileAccessCLI (Node.js FileSystemAdapter) + ├─ StorageEventManagerCLI + ├─ ServiceFileAccessCLI + ├─ ServiceDatabaseFileAccessCLI + ├─ ServiceFileHandler + └─ ServiceRebuilder +``` + +### Key Components + +1. **Node.js FileSystem Adapter** (`adapters/`) + - Platform-agnostic file operations using Node.js `fs/promises` + - Implements same interface as Obsidian's file system + +2. **Service Modules** (`serviceModules/`) + - Initialised by `initialiseServiceModulesCLI` + - All core sync functionality preserved + +3. **Service Hub and Settings Services** (`services/`) + - `NodeServiceHub` provides the CLI service context + - Node-specific settings and key-value services are provided without Obsidian dependencies + +4. **Main Entry Point** (`main.ts`) + - Command-line interface + - Settings management (JSON file) + - Graceful shutdown handling + +## 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 . +``` + +Run: + +```bash +# Sync with CouchDB +docker run --rm -v /path/to/your/vault:/data livesync-cli sync + +# List files in the local database +docker run --rm -v /path/to/your/vault:/data livesync-cli ls + +# Generate a default settings file +docker run --rm -v /path/to/your/vault:/data livesync-cli init-settings +``` + +The vault directory is mounted at `/data` by default. Override with `-e LIVESYNC_DB_PATH=/other/path`. + +### P2P (WebRTC) and Docker networking + +The P2P replicator (`p2p-host`, `p2p-sync`, `p2p-peers`) uses WebRTC and generates +three kinds of ICE candidates. The default Docker bridge network affects which +candidates are usable: + +| Candidate type | Description | Bridge network | +|---|---|---| +| `host` | Container bridge IP (`172.17.x.x`) | Unreachable from LAN peers | +| `srflx` | Host public IP via STUN reflection | Works over the internet | +| `relay` | Traffic relayed via TURN server | Always reachable | + +**LAN P2P on Linux** — use `--network host` so that the real host IP is +advertised as the `host` candidate: + +```bash +docker run --rm --network host -v /path/to/your/vault:/data livesync-cli p2p-host +``` + +> `--network host` is not available on Docker Desktop for macOS or Windows. + +**LAN P2P on macOS / Windows Docker Desktop** — configure a TURN server in the +settings file (`P2P_turnServers`, `P2P_turnUsername`, `P2P_turnCredential`). +All P2P traffic will then be relayed through the TURN server, bypassing the +bridge-network limitation. + +**Internet P2P** — the default bridge network is sufficient. The `srflx` +candidate carries the host's public IP and peers can connect normally. + +**CouchDB sync only (no P2P)** — no special network configuration is required. + +## Installation + +```bash +# Install dependencies (ensure you are in repository root directory, not src/apps/cli) +# due to shared dependencies with webapp and main library +npm install +# Build the project (ensure you are in `src/apps/cli` directory) +npm run build +``` + +## Usage + +### Basic Usage + +As you know, the CLI is designed to be used in a headless environment. Hence all operations are performed against a local vault directory and a settings file. Here are some example commands: + +```bash +# Sync local database with CouchDB (no files will be changed). +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json sync + +# Push files to local database +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json push /your/storage/file.md /vault/path/file.md + +# Pull files from local database +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json pull /vault/path/file.md /your/storage/file.md + +# Verbose logging +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json --verbose + +# Apply setup URI to settings file (settings only; does not run synchronisation) +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json setup "obsidian://setuplivesync?settings=..." + +# Put text from stdin into local database +echo "Hello from stdin" | npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json put /vault/path/file.md + +# Output a file from local database to stdout +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json cat /vault/path/file.md + +# Output a specific revision of a file from local database +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json cat-rev /vault/path/file.md 3-abcdef + +# Pull a specific revision of a file from local database to local storage +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json pull-rev /vault/path/file.md /your/storage/file.old.md 3-abcdef + +# List files in local database +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json ls /vault/path/ + +# Show metadata for a file in local database +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json info /vault/path/file.md + +# Mark a file as deleted in local database +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json rm /vault/path/file.md + +# Resolve conflict by keeping a specific revision +npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json resolve /vault/path/file.md 3-abcdef +``` + +### Configuration + +The CLI uses the same settings format as the Obsidian plugin. Create a `.livesync/settings.json` file in your vault directory: + +```json +{ + "couchDB_URI": "http://localhost:5984", + "couchDB_USER": "admin", + "couchDB_PASSWORD": "password", + "couchDB_DBNAME": "obsidian-livesync", + "liveSync": true, + "syncOnSave": true, + "syncOnStart": true, + "encrypt": true, + "passphrase": "your-encryption-passphrase", + "usePluginSync": false, + "isConfigured": true +} +``` + +**Minimum required settings:** + +- `couchDB_URI`: CouchDB server URL +- `couchDB_USER`: CouchDB username +- `couchDB_PASSWORD`: CouchDB password +- `couchDB_DBNAME`: Database name +- `isConfigured`: Set to `true` after configuration + +### Command-line Reference + +``` +Usage: + livesync-cli [database-path] [options] [command] [command-args] + +Arguments: + database-path Path to the local database directory (required except for init-settings) + +Options: + --settings, -s Path to settings file (default: .livesync/settings.json in local database directory) + --force, -f Overwrite existing file on init-settings + --verbose, -v Enable verbose logging + --help, -h Show this help message + +Commands: + init-settings [path] Create settings JSON from DEFAULT_SETTINGS + sync Run one replication cycle and exit + p2p-peers Show discovered peers as [peer] + p2p-sync Synchronise with specified peer-id or peer-name + p2p-host Start P2P host mode and wait until interrupted (Ctrl+C) + push Push local file into local database path + pull Pull file from local database into local file + pull-rev Pull specific revision into local file + setup Apply setup URI to settings file + put Read text from standard input and write to local database + cat Write latest file content from local database to standard output + cat-rev Write specific revision content from local database to standard output + ls [prefix] List files as pathsizemtimerevision[*] + info Show file metadata including current and past revisions, conflicts, and chunk list + rm Mark file as deleted in local database + resolve Resolve conflict by keeping the specified revision + mirror Mirror local file into local database. +``` + +Run via npm script: + +```bash +npm run --silent cli -- [database-path] [options] [command] [command-args] +``` + +#### Detailed Command Descriptions + +##### ls +`ls` lists files in the local database with optional prefix filtering. Output format is: + +```vault/path/file.mdsizemtimerevision[*] +``` +Note: `*` indicates if the file has conflicts. + +##### p2p-peers + +`p2p-peers ` waits for the specified number of seconds, then prints each discovered peer on a separate line: + +```text +[peer] +``` + +Use this command to select a target for `p2p-sync`. + +##### p2p-sync + +`p2p-sync ` discovers peers up to the specified timeout and synchronises with the selected peer. + +- `` accepts either `peer-id` or `peer-name` from `p2p-peers` output. +- On success, the command prints a completion message to standard error and exits with status code `0`. +- On failure, the command prints an error message and exits non-zero. + +##### p2p-host + +`p2p-host` starts the local P2P host and keeps running until interrupted. + +- Other peers can discover and synchronise with this host while it is running. +- Stop the host with `Ctrl+C`. +- In CLI mode, behaviour is non-interactive and acceptance follows settings. + +##### info + +`info` output fields: + +- `id`: Document ID +- `revision`: Current revision +- `conflicts`: Conflicted revisions, or `N/A` +- `filename`: Basename of path +- `path`: Vault-relative path +- `size`: Size in bytes +- `revisions`: Available non-current revisions +- `chunks`: Number of chunk IDs +- `children`: Chunk ID list + +##### mirror + +`mirror` is a command that synchronises your storage with your local vault. It is essentially a process that runs upon startup in Obsidian. + +In other words, it performs the following actions: + +1. **Precondition checks** — Aborts early if any of the following conditions are not met: + - Settings must be configured (`isConfigured: true`). + - File watching must not be suspended (`suspendFileWatching: false`). + - Remediation mode must be inactive (`maxMTimeForReflectEvents: 0`). + +2. **State restoration** — On subsequent runs (after the first successful scan), restores the previous storage state before proceeding. + +3. **Expired deletion cleanup** — If `automaticallyDeleteMetadataOfDeletedFiles` is set to a positive number of days, any document that is marked deleted and whose `mtime` is older than the retention period is permanently removed from the local database. + +4. **File collection** — Enumerates files from two sources: + - **Storage**: all files under the vault path that pass `isTargetFile`. + - **Local database**: all normal documents (fetched with conflict information) whose paths are valid and pass `isTargetFile`. + - Both collections build case-insensitive ↔ case-sensitive path maps, controlled by `handleFilenameCaseSensitive`. + +5. **Categorisation and synchronisation** — The union of both file sets is split into three groups and processed concurrently (up to 10 files at a time): + + | Group | Condition | Action | + |---|---|---| + | **UPDATE DATABASE** | File exists in storage only | Store the file into the local database. | + | **UPDATE STORAGE** | File exists in database only | If the entry is active (not deleted) and not conflicted, restore the file from the database to storage. Deleted entries and conflicted entries are skipped. | + | **SYNC DATABASE AND STORAGE** | File exists in both | Compare `mtime` freshness. If storage is newer → write to database (`STORAGE → DB`). If database is newer → restore to storage (`STORAGE ← DB`). If equal → do nothing. Conflicted documents and files exceeding the size limit are always skipped. | + +6. **Initialisation flag** — On the very first successful run, writes `initialized = true` to the key-value database so that subsequent runs can restore state in step 2. + +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). + +### Planned options: + +- `--immediate`: Perform sync after the command (e.g. `push`, `pull`, `put`, `rm`). +- `serve`: Start CLI in server mode, exposing REST APIs for remote, and batch operations. +- `cause-conflicted `: Mark a file as conflicted without changing its content, to trigger conflict resolution in Obsidian. + +## Use Cases + +### 1. Bootstrap a new headless vault + +Create default settings, apply a setup URI, then run one sync cycle. + +```bash +npm run --silent cli -- init-settings /data/livesync-settings.json +printf '%s\n' "$SETUP_PASSPHRASE" | npm run --silent cli -- /data/vault --settings /data/livesync-settings.json setup "$SETUP_URI" +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json sync +``` + +### 2. Scripted import and export + +Push local files into the database from automation, and pull them back for export or backup. + +```bash +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json push ./note.md notes/note.md +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json pull notes/note.md ./exports/note.md +``` + +### 3. Revision inspection and restore + +List metadata, find an older revision, then restore it by content (`cat-rev`) or file output (`pull-rev`). + +```bash +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json cat-rev notes/note.md 3-abcdef +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json pull-rev notes/note.md ./restore/note.old.md 3-abcdef +``` + +### 4. Conflict and cleanup workflow + +Inspect conflicted revisions, resolve by keeping one revision, then delete obsolete files. + +```bash +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json resolve notes/note.md 3-abcdef +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json rm notes/obsolete.md +``` + +### 5. CI smoke test for content round-trip + +Validate that `put`/`cat` is behaving as expected in a pipeline. + +```bash +echo "hello-ci" | npm run --silent cli -- /data/vault --settings /data/livesync-settings.json put ci/test.md +npm run --silent cli -- /data/vault --settings /data/livesync-settings.json cat ci/test.md +``` + +## Development + +### Project Structure + +``` +src/apps/cli/ +├── commands/ # Command dispatcher and command utilities +│ ├── runCommand.ts +│ ├── runCommand.unit.spec.ts +│ ├── types.ts +│ ├── utils.ts +│ └── utils.unit.spec.ts +├── adapters/ # Node.js FileSystem Adapter +│ ├── NodeConversionAdapter.ts +│ ├── NodeFileSystemAdapter.ts +│ ├── NodePathAdapter.ts +│ ├── NodeStorageAdapter.ts +│ ├── NodeStorageAdapter.unit.spec.ts +│ ├── NodeTypeGuardAdapter.ts +│ ├── NodeTypes.ts +│ └── NodeVaultAdapter.ts +├── lib/ +│ └── pouchdb-node.ts +├── managers/ # CLI-specific managers +│ ├── CLIStorageEventManagerAdapter.ts +│ └── StorageEventManagerCLI.ts +├── serviceModules/ # Service modules (ported from main.ts) +│ ├── CLIServiceModules.ts +│ ├── DatabaseFileAccess.ts +│ ├── FileAccessCLI.ts +│ └── ServiceFileAccessImpl.ts +├── services/ +│ ├── NodeKeyValueDBService.ts +│ ├── NodeServiceHub.ts +│ └── NodeSettingService.ts +├── test/ +│ ├── test-e2e-two-vaults-common.sh +│ ├── test-e2e-two-vaults-matrix.sh +│ ├── test-e2e-two-vaults-with-docker-linux.sh +│ ├── test-push-pull-linux.sh +│ ├── test-setup-put-cat-linux.sh +│ └── test-sync-two-local-databases-linux.sh +├── .gitignore +├── entrypoint.ts # CLI executable entry point (shebang) +├── main.ts # CLI entry point +├── main.unit.spec.ts +├── package.json +├── README.md # This file +├── tsconfig.json +├── util/ # Test and local utility scripts +└── vite.config.ts +``` diff --git a/src/apps/cli/docker-entrypoint.sh b/src/apps/cli/docker-entrypoint.sh new file mode 100644 index 0000000..bda431b --- /dev/null +++ b/src/apps/cli/docker-entrypoint.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# Entrypoint wrapper for the Self-hosted LiveSync CLI Docker image. +# +# By default, /data is used as the database-path (the vault mount point). +# Override this via the LIVESYNC_DB_PATH environment variable. +# +# Examples: +# docker run -v /path/to/vault:/data livesync-cli sync +# docker run -v /path/to/vault:/data livesync-cli --settings /data/.livesync/settings.json sync +# docker run -v /path/to/vault:/data livesync-cli init-settings +# docker run -e LIVESYNC_DB_PATH=/vault -v /path/to/vault:/vault livesync-cli sync + +set -e + +case "${1:-}" in + init-settings | --help | -h | "") + # Commands that do not require a leading database-path argument + exec node /app/dist/index.cjs "$@" + ;; + *) + # All other commands: prepend the database-path so users only need + # to supply the command and its options. + exec node /app/dist/index.cjs "${LIVESYNC_DB_PATH:-/data}" "$@" + ;; +esac diff --git a/src/apps/cli/entrypoint.ts b/src/apps/cli/entrypoint.ts index 8da7104..9cfc06e 100644 --- a/src/apps/cli/entrypoint.ts +++ b/src/apps/cli/entrypoint.ts @@ -1,10 +1,11 @@ #!/usr/bin/env node -import polyfill from "node-datachannel/polyfill"; +import * as polyfill from "werift"; import { main } from "./main"; -for (const prop in polyfill) { - // @ts-ignore Applying polyfill to globalThis - globalThis[prop] = (polyfill as any)[prop]; +const rtcPolyfillCtor = (polyfill as any).RTCPeerConnection; +if (typeof (globalThis as any).RTCPeerConnection === "undefined" && typeof rtcPolyfillCtor === "function") { + // Fill only the standard WebRTC global in Node CLI runtime. + (globalThis as any).RTCPeerConnection = rtcPolyfillCtor; } main().catch((error) => { diff --git a/src/apps/cli/package.json b/src/apps/cli/package.json index 5701ae1..0bd2999 100644 --- a/src/apps/cli/package.json +++ b/src/apps/cli/package.json @@ -1,31 +1,40 @@ -{ - "name": "self-hosted-livesync-cli", - "private": true, - "version": "0.0.0", - "main": "dist/index.cjs", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview", - "cli": "node dist/index.cjs", - "buildRun": "npm run build && npm run cli --", - "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json", - "test:unit": "cd ../../.. && npx vitest run --config vitest.config.unit.ts src/apps/cli/main.unit.spec.ts src/apps/cli/commands/utils.unit.spec.ts src/apps/cli/commands/runCommand.unit.spec.ts src/apps/cli/commands/p2p.unit.spec.ts", - "test:e2e:two-vaults": "bash test/test-e2e-two-vaults-with-docker-linux.sh", - "test:e2e:two-vaults:common": "bash test/test-e2e-two-vaults-common.sh", - "test:e2e:two-vaults:matrix": "bash test/test-e2e-two-vaults-matrix.sh", - "test:e2e:push-pull": "bash test/test-push-pull-linux.sh", - "test:e2e:setup-put-cat": "bash test/test-setup-put-cat-linux.sh", - "test:e2e:sync-two-local": "bash test/test-sync-two-local-databases-linux.sh", - "test:e2e:p2p": "bash test/test-p2p-three-nodes-conflict-linux.sh", - "test:e2e:p2p-upload-download-repro": "bash test/test-p2p-upload-download-repro-linux.sh", - "test:e2e:p2p-host": "bash test/test-p2p-host-linux.sh", - "test:e2e:p2p-sync": "bash test/test-p2p-sync-linux.sh", - "test:e2e:mirror": "bash test/test-mirror-linux.sh", - "pretest:e2e:all": "npm run build", - "test:e2e:all": " export RUN_BUILD=0 && npm run test:e2e:setup-put-cat && npm run test:e2e:push-pull && npm run test:e2e:sync-two-local && npm run test:e2e:p2p && npm run test:e2e:mirror && npm run test:e2e:two-vaults && npm run test:e2e:p2p" - }, - "dependencies": {}, - "devDependencies": {} -} +{ + "name": "self-hosted-livesync-cli", + "private": true, + "version": "0.0.0", + "main": "dist/index.cjs", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "cli": "node dist/index.cjs", + "buildRun": "npm run build && npm run cli --", + "build:docker": "docker build -f Dockerfile -t livesync-cli ../../..", + "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json", + "test:unit": "cd ../../.. && npx vitest run --config vitest.config.unit.ts src/apps/cli/main.unit.spec.ts src/apps/cli/commands/utils.unit.spec.ts src/apps/cli/commands/runCommand.unit.spec.ts src/apps/cli/commands/p2p.unit.spec.ts", + "test:e2e:two-vaults": "bash test/test-e2e-two-vaults-with-docker-linux.sh", + "test:e2e:two-vaults:common": "bash test/test-e2e-two-vaults-common.sh", + "test:e2e:two-vaults:matrix": "bash test/test-e2e-two-vaults-matrix.sh", + "test:e2e:push-pull": "bash test/test-push-pull-linux.sh", + "test:e2e:setup-put-cat": "bash test/test-setup-put-cat-linux.sh", + "test:e2e:sync-two-local": "bash test/test-sync-two-local-databases-linux.sh", + "test:e2e:p2p": "bash test/test-p2p-three-nodes-conflict-linux.sh", + "test:e2e:p2p-upload-download-repro": "bash test/test-p2p-upload-download-repro-linux.sh", + "test:e2e:p2p-host": "bash test/test-p2p-host-linux.sh", + "test:e2e:p2p-sync": "bash test/test-p2p-sync-linux.sh", + "test:e2e:mirror": "bash test/test-mirror-linux.sh", + "pretest:e2e:all": "npm run build", + "test:e2e:all": " export RUN_BUILD=0 && npm run test:e2e:setup-put-cat && npm run test:e2e:push-pull && npm run test:e2e:sync-two-local && npm run test:e2e:p2p && npm run test:e2e:mirror && npm run test:e2e:two-vaults && npm run test:e2e:p2p", + "pretest:e2e:docker:all": "npm run build:docker", + "test:e2e:docker:push-pull": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-push-pull-linux.sh", + "test:e2e:docker:setup-put-cat": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-setup-put-cat-linux.sh", + "test:e2e:docker:mirror": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-mirror-linux.sh", + "test:e2e:docker:sync-two-local": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-sync-two-local-databases-linux.sh", + "test:e2e:docker:p2p": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-p2p-three-nodes-conflict-linux.sh", + "test:e2e:docker:p2p-sync": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-p2p-sync-linux.sh", + "test:e2e:docker:all": "export RUN_BUILD=0 && npm run test:e2e:docker:setup-put-cat && npm run test:e2e:docker:push-pull && npm run test:e2e:docker:sync-two-local && npm run test:e2e:docker:mirror" + }, + "dependencies": {}, + "devDependencies": {} +} diff --git a/src/apps/cli/runtime-package.json b/src/apps/cli/runtime-package.json new file mode 100644 index 0000000..12f7920 --- /dev/null +++ b/src/apps/cli/runtime-package.json @@ -0,0 +1,24 @@ +{ + "name": "livesync-cli-runtime", + "private": true, + "version": "0.0.0", + "description": "Runtime dependencies for Self-hosted LiveSync CLI Docker image", + "dependencies": { + "commander": "^14.0.3", + "werift": "^0.22.9", + "pouchdb-adapter-http": "^9.0.0", + "pouchdb-adapter-idb": "^9.0.0", + "pouchdb-adapter-indexeddb": "^9.0.0", + "pouchdb-adapter-leveldb": "^9.0.0", + "pouchdb-adapter-memory": "^9.0.0", + "pouchdb-core": "^9.0.0", + "pouchdb-errors": "^9.0.0", + "pouchdb-find": "^9.0.0", + "pouchdb-mapreduce": "^9.0.0", + "pouchdb-merge": "^9.0.0", + "pouchdb-replication": "^9.0.0", + "pouchdb-utils": "^9.0.0", + "pouchdb-wrappers": "*", + "transform-pouch": "^2.0.0" + } +} diff --git a/src/apps/cli/test/test-e2e-two-vaults-common.sh b/src/apps/cli/test/test-e2e-two-vaults-common.sh old mode 100755 new mode 100644 diff --git a/src/apps/cli/test/test-e2e-two-vaults-matrix.sh b/src/apps/cli/test/test-e2e-two-vaults-matrix.sh old mode 100755 new mode 100644 diff --git a/src/apps/cli/test/test-e2e-two-vaults-with-docker-linux.sh b/src/apps/cli/test/test-e2e-two-vaults-with-docker-linux.sh old mode 100755 new mode 100644 diff --git a/src/apps/cli/test/test-helpers-docker.sh b/src/apps/cli/test/test-helpers-docker.sh new file mode 100644 index 0000000..33a6035 --- /dev/null +++ b/src/apps/cli/test/test-helpers-docker.sh @@ -0,0 +1,150 @@ +#!/usr/bin/env bash +# test-helpers-docker.sh +# +# Docker-mode overrides for test-helpers.sh. +# Sourced automatically at the end of test-helpers.sh when +# LIVESYNC_TEST_DOCKER=1 is set, replacing run_cli (and related helpers) +# with a Docker-based implementation. +# +# The Docker container and the host share a common directory layout: +# $WORK_DIR (host) <-> /workdir (container) +# $CLI_DIR (host) <-> /clidir (container) +# +# Usage (run an existing test against the Docker image): +# LIVESYNC_TEST_DOCKER=1 bash test/test-push-pull-linux.sh +# LIVESYNC_TEST_DOCKER=1 bash test/test-mirror-linux.sh +# LIVESYNC_TEST_DOCKER=1 bash test/test-sync-two-local-databases-linux.sh +# LIVESYNC_TEST_DOCKER=1 bash test/test-setup-put-cat-linux.sh +# +# Optional environment variables: +# DOCKER_IMAGE Image name/tag to use (default: livesync-cli) +# RUN_BUILD Set to 1 to rebuild the Docker image before the test +# (default: 0 — assumes the image is already built) +# Build command: npm run build:docker (from src/apps/cli/) +# +# Notes: +# - The container is started with --network host so that it can reach +# CouchDB / P2P relay containers that are also using the host network. +# - On macOS / Windows Docker Desktop --network host behaves differently +# (it is not a true host-network bridge); tests that rely on localhost +# connectivity to other containers may fail on those platforms. + +# Ensure Docker-mode tests do not trigger host-side `npm run build` unless +# explicitly requested by the caller. +RUN_BUILD="${RUN_BUILD:-0}" + +# Override the standard implementation. +# In Docker mode the CLI_CMD array is a no-op sentinel; run_cli is overridden +# directly. +cli_test_init_cli_cmd() { + DOCKER_IMAGE="${DOCKER_IMAGE:-livesync-cli}" + # CLI_CMD is unused in Docker mode; set a sentinel so existing code + # that references it will not error. + CLI_CMD=(__docker__) +} + +# ─── display_test_info ──────────────────────────────────────────────────────── +display_test_info() { + local image="${DOCKER_IMAGE:-livesync-cli}" + local image_id + image_id="$(docker inspect --format='{{slice .Id 7 19}}' "$image" 2>/dev/null || echo "N/A")" + echo "======================" + echo "Script: ${BASH_SOURCE[1]:-$0}" + echo "Date: $(date -u +%Y-%m-%dT%H:%M:%SZ)" + echo "Commit: $(git -C "${SCRIPT_DIR:-.}" rev-parse --short HEAD 2>/dev/null || echo "N/A")" + echo "Mode: Docker image=${image} id=${image_id}" + echo "======================" +} + +# ─── _docker_translate_arg ─────────────────────────────────────────────────── +# Translate a single host filesystem path to its in-container equivalent. +# Paths under WORK_DIR → /workdir/... +# Paths under CLI_DIR → /clidir/... +# Everything else is returned unchanged (relative paths, URIs, plain names). +_docker_translate_arg() { + local arg="$1" + if [[ -n "${WORK_DIR:-}" && "$arg" == "$WORK_DIR"* ]]; then + printf '%s' "/workdir${arg#$WORK_DIR}" + return + fi + if [[ -n "${CLI_DIR:-}" && "$arg" == "$CLI_DIR"* ]]; then + printf '%s' "/clidir${arg#$CLI_DIR}" + return + fi + printf '%s' "$arg" +} + +# ─── run_cli ───────────────────────────────────────────────────────────────── +# Drop-in replacement for run_cli that executes the CLI inside a Docker +# container, translating host paths to container paths automatically. +# +# Calling convention is identical to the native run_cli: +# run_cli [options] [command-args] +# run_cli init-settings [options] +# +# The vault path (first positional argument for regular commands) is forwarded +# via the LIVESYNC_DB_PATH environment variable so that docker-entrypoint.sh +# can inject it before the remaining CLI arguments. +run_cli() { + local args=("$@") + + # ── 1. Translate all host paths to container paths ──────────────────── + local translated=() + for arg in "${args[@]}"; do + translated+=("$(_docker_translate_arg "$arg")") + done + + # ── 2. Split vault path from the rest of the arguments ─────────────── + local first="${translated[0]:-}" + local env_args=() + local cli_args=() + + # These tokens are commands or flags that appear before any vault path. + case "$first" in + "" | --help | -h \ + | init-settings \ + | -v | --verbose | -d | --debug | -f | --force | -s | --settings) + # No leading vault path — pass all translated args as-is. + cli_args=("${translated[@]}") + ;; + *) + # First arg is the vault path; hand it to docker-entrypoint.sh + # via LIVESYNC_DB_PATH so the entrypoint prepends it correctly. + env_args+=(-e "LIVESYNC_DB_PATH=$first") + cli_args=("${translated[@]:1}") + ;; + esac + + # ── 3. Inject verbose / debug flags ────────────────────────────────── + if [[ "${VERBOSE_TEST_LOGGING:-0}" == "1" ]]; then + cli_args=(-v "${cli_args[@]}") + fi + + # ── 4. Volume mounts ────────────────────────────────────────────────── + local vol_args=() + if [[ -n "${WORK_DIR:-}" ]]; then + vol_args+=(-v "${WORK_DIR}:/workdir") + fi + # Mount CLI_DIR (src/apps/cli) for two-vault tests that store vault data + # under $CLI_DIR/.livesync/. + if [[ -n "${CLI_DIR:-}" ]]; then + vol_args+=(-v "${CLI_DIR}:/clidir") + fi + + # ── 5. stdin forwarding ─────────────────────────────────────────────── + # Attach stdin only when it is a pipe (the 'put' command reads from stdin). + # Without -i the pipe data would never reach the container process. + local stdin_flags=() + if [[ ! -t 0 ]]; then + stdin_flags=(-i) + fi + + docker run --rm \ + "${stdin_flags[@]}" \ + --network host \ + --user "$(id -u):$(id -g)" \ + "${vol_args[@]}" \ + "${env_args[@]}" \ + "${DOCKER_IMAGE:-livesync-cli}" \ + "${cli_args[@]}" +} diff --git a/src/apps/cli/test/test-helpers.sh b/src/apps/cli/test/test-helpers.sh index 0e0e236..1e38a04 100644 --- a/src/apps/cli/test/test-helpers.sh +++ b/src/apps/cli/test/test-helpers.sh @@ -1,5 +1,15 @@ #!/usr/bin/env bash +# ─── local init hook ──────────────────────────────────────────────────────── +# If test-init.local.sh exists alongside this file, source it before anything +# else. Use it to set up your local environment (e.g. activate nvm, set +# DOCKER_IMAGE, ...). The file is git-ignored so it is safe to put personal +# or machine-specific configuration there. +_TEST_HELPERS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=/dev/null +[[ -f "$_TEST_HELPERS_DIR/test-init.local.sh" ]] && source "$_TEST_HELPERS_DIR/test-init.local.sh" +unset _TEST_HELPERS_DIR + cli_test_init_cli_cmd() { if [[ "${VERBOSE_TEST_LOGGING:-0}" == "1" ]]; then CLI_CMD=(npm --silent run cli -- -v) @@ -343,4 +353,10 @@ display_test_info(){ echo "Date: $(date -u +%Y-%m-%dT%H:%M:%SZ)" echo "Git commit: $(git -C "$SCRIPT_DIR/.." rev-parse --short HEAD 2>/dev/null || echo "N/A")" echo "======================" -} \ No newline at end of file +} + +# Docker-mode hook — source overrides when LIVESYNC_TEST_DOCKER=1. +if [[ "${LIVESYNC_TEST_DOCKER:-0}" == "1" ]]; then + # shellcheck source=/dev/null + source "$(dirname "${BASH_SOURCE[0]}")/test-helpers-docker.sh" +fi \ No newline at end of file diff --git a/src/apps/cli/test/test-mirror-linux.sh b/src/apps/cli/test/test-mirror-linux.sh old mode 100755 new mode 100644 diff --git a/src/apps/cli/test/test-p2p-three-nodes-conflict-linux.sh b/src/apps/cli/test/test-p2p-three-nodes-conflict-linux.sh old mode 100755 new mode 100644 diff --git a/src/apps/cli/test/test-setup-put-cat-linux.sh b/src/apps/cli/test/test-setup-put-cat-linux.sh old mode 100755 new mode 100644 diff --git a/src/apps/cli/test/test-sync-locked-remote-linux.sh b/src/apps/cli/test/test-sync-locked-remote-linux.sh old mode 100755 new mode 100644 diff --git a/src/apps/cli/test/test-sync-two-local-databases-linux.sh b/src/apps/cli/test/test-sync-two-local-databases-linux.sh old mode 100755 new mode 100644 diff --git a/src/apps/cli/util/p2p-start.sh b/src/apps/cli/util/p2p-start.sh index 90b3e3e..497275e 100755 --- a/src/apps/cli/util/p2p-start.sh +++ b/src/apps/cli/util/p2p-start.sh @@ -1,2 +1,30 @@ #!/bin/bash -docker run -d --name relay-test -p 4000:8080 scsibug/nostr-rs-relay:latest +set -e + +docker run -d --name relay-test -p 4000:7777 \ + --tmpfs /app/strfry-db:rw,size=256m \ + --entrypoint sh \ + ghcr.io/hoytech/strfry:latest \ + -lc 'cat > /tmp/strfry.conf <<"EOF" +db = "./strfry-db/" + +relay { + bind = "0.0.0.0" + port = 7777 + nofiles = 100000 + + info { + name = "livesync test relay" + description = "local relay for livesync p2p tests" + } + + maxWebsocketPayloadSize = 131072 + autoPingSeconds = 55 + + writePolicy { + plugin = "" + } +} +EOF +exec /app/strfry --config /tmp/strfry.conf relay' + diff --git a/src/apps/cli/vite.config.ts b/src/apps/cli/vite.config.ts index 77d8591..e78642c 100644 --- a/src/apps/cli/vite.config.ts +++ b/src/apps/cli/vite.config.ts @@ -12,8 +12,7 @@ const defaultExternal = [ "pouchdb-adapter-leveldb", "commander", "punycode", - "node-datachannel", - "node-datachannel/polyfill", + "werift", ]; export default defineConfig({ plugins: [svelte()], @@ -52,7 +51,7 @@ export default defineConfig({ if (id === "fs" || id === "fs/promises" || id === "path" || id === "crypto" || id === "worker_threads") return true; if (id.startsWith("pouchdb-")) return true; - if (id.startsWith("node-datachannel")) return true; + if (id.startsWith("werift")) return true; if (id.startsWith("node:")) return true; return false; }, diff --git a/src/lib b/src/lib index 3d6d960..bed8afd 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 3d6d9603bf96477895a674398f22585662479723 +Subproject commit bed8afd2dc55c6252e181c9487a25d2d6d28221d diff --git a/test/shell/p2p-start.sh b/test/shell/p2p-start.sh index b4218e0..8c86a45 100755 --- a/test/shell/p2p-start.sh +++ b/test/shell/p2p-start.sh @@ -3,6 +3,31 @@ set -e script_dir=$(dirname "$0") webpeer_dir=$script_dir/../../src/apps/webpeer -docker run -d --name relay-test -p 4000:8080 scsibug/nostr-rs-relay:latest +docker run -d --name relay-test -p 4000:7777 \ + --tmpfs /app/strfry-db:rw,size=256m \ + --entrypoint sh \ + ghcr.io/hoytech/strfry:latest \ + -lc 'cat > /tmp/strfry.conf <<"EOF" +db = "./strfry-db/" + +relay { + bind = "0.0.0.0" + port = 7777 + nofiles = 100000 + + info { + name = "livesync test relay" + description = "local relay for livesync p2p tests" + } + + maxWebsocketPayloadSize = 131072 + autoPingSeconds = 55 + + writePolicy { + plugin = "" + } +} +EOF +exec /app/strfry --config /tmp/strfry.conf relay' npm run --prefix $webpeer_dir build docker run -d --name webpeer-test -p 8081:8043 -v $webpeer_dir/dist:/srv/http pierrezemb/gostatic \ No newline at end of file From 4c0908acde38edbe63df706e8a0b74233d955d10 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 2 Apr 2026 07:47:10 +0100 Subject: [PATCH 117/339] Add CI Build for cli-docker image --- .github/workflows/cli-docker.yml | 101 +++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 .github/workflows/cli-docker.yml diff --git a/.github/workflows/cli-docker.yml b/.github/workflows/cli-docker.yml new file mode 100644 index 0000000..a47e58c --- /dev/null +++ b/.github/workflows/cli-docker.yml @@ -0,0 +1,101 @@ +# Build and push the CLI Docker image to GitHub Container Registry (GHCR).# +# Image tag format: --cli +# Example: 0.25.56-1743500000-cli +# +# The image is also tagged 'latest' for convenience. +# Image name: ghcr.io//livesync-cli +name: Build and Push CLI Docker Image + +on: + push: + tags: + - "*.*.*-cli" + workflow_dispatch: + inputs: + dry_run: + description: Build only (do not push image to GHCR) + required: false + type: boolean + default: true + force: + description: Continue to build/push even if CLI E2E fails (workflow_dispatch only) + required: false + type: boolean + default: false + +jobs: + build-and-push: + runs-on: ubuntu-latest + timeout-minutes: 90 + permissions: + contents: read + packages: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Derive image tag + id: meta + run: | + VERSION=$(jq -r '.version' manifest.json) + EPOCH=$(date +%s) + TAG="${VERSION}-${EPOCH}-cli" + IMAGE="ghcr.io/${{ github.repository_owner }}/livesync-cli" + echo "tag=${TAG}" >> $GITHUB_OUTPUT + echo "image=${IMAGE}" >> $GITHUB_OUTPUT + echo "full=${IMAGE}:${TAG}" >> $GITHUB_OUTPUT + echo "version=${IMAGE}:${VERSION}-cli" >> $GITHUB_OUTPUT + echo "latest=${IMAGE}:latest" >> $GITHUB_OUTPUT + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24.x" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Run CLI E2E (docker) + id: e2e + continue-on-error: ${{ github.event_name == 'workflow_dispatch' && inputs.force }} + working-directory: src/apps/cli + env: + CI: true + run: npm run test:e2e:docker:all + + - name: Stop test containers (safety net) + if: always() + working-directory: src/apps/cli + run: | + # Keep this as a safety net for future suites/steps that may leave containers running. + bash ./util/couchdb-stop.sh >/dev/null 2>&1 || true + bash ./util/minio-stop.sh >/dev/null 2>&1 || true + bash ./util/p2p-stop.sh >/dev/null 2>&1 || true + + - name: Build and push + if: ${{ steps.e2e.outcome == 'success' || (github.event_name == 'workflow_dispatch' && inputs.force) }} + uses: docker/build-push-action@v6 + with: + context: . + file: src/apps/cli/Dockerfile + push: ${{ !(github.event_name == 'workflow_dispatch' && inputs.dry_run) }} + tags: | + ${{ steps.meta.outputs.full }} + ${{ steps.meta.outputs.version }} + ${{ steps.meta.outputs.latest }} + cache-from: type=gha + cache-to: type=gha,mode=max From 3c94a44285b83fe75c3b27a76856b68ff9379467 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 2 Apr 2026 10:30:14 +0100 Subject: [PATCH 118/339] Fixed: Replication progress is now correctly saved and restored in the CLI. --- src/apps/cli/Dockerfile | 2 +- src/apps/cli/README.md | 4 + src/apps/cli/main.ts | 19 +-- src/apps/cli/services/NodeLocalStorage.ts | 111 ++++++++++++++++++ .../services/NodeLocalStorage.unit.spec.ts | 60 ++++++++++ src/apps/cli/services/NodeSettingService.ts | 38 ++---- updates.md | 97 ++------------- updates_old.md | 41 +++++++ 8 files changed, 242 insertions(+), 130 deletions(-) create mode 100644 src/apps/cli/services/NodeLocalStorage.ts create mode 100644 src/apps/cli/services/NodeLocalStorage.unit.spec.ts diff --git a/src/apps/cli/Dockerfile b/src/apps/cli/Dockerfile index 7e85bdb..f1a24cf 100644 --- a/src/apps/cli/Dockerfile +++ b/src/apps/cli/Dockerfile @@ -108,4 +108,4 @@ RUN chmod +x /usr/local/bin/livesync-cli # Mount your vault / local database directory here VOLUME ["/data"] -ENTRYPOINT ["livesync-cli"] +ENTRYPOINT ["/usr/local/bin/livesync-cli"] diff --git a/src/apps/cli/README.md b/src/apps/cli/README.md index 08f8493..8ea8db4 100644 --- a/src/apps/cli/README.md +++ b/src/apps/cli/README.md @@ -45,6 +45,10 @@ CLI Main - Settings management (JSON file) - Graceful shutdown handling +## Something I realised later that could lead to misunderstandings + +The term `vault` in this README refers to the directory containing your local database and settings file. Not the actual files you want to sync. I will fix this later, but please be mind this for now. + ## Docker A Docker image is provided for headless / server deployments. Build from the repository root: diff --git a/src/apps/cli/main.ts b/src/apps/cli/main.ts index fac1072..ecd8afa 100644 --- a/src/apps/cli/main.ts +++ b/src/apps/cli/main.ts @@ -3,25 +3,10 @@ * Command-line version of Self-hosted LiveSync plugin for syncing vaults without Obsidian */ -if (!("localStorage" in globalThis) || typeof (globalThis as any).localStorage?.getItem !== "function") { - const store = new Map(); - (globalThis as any).localStorage = { - getItem: (key: string) => (store.has(key) ? store.get(key)! : null), - setItem: (key: string, value: string) => { - store.set(key, value); - }, - removeItem: (key: string) => { - store.delete(key); - }, - clear: () => { - store.clear(); - }, - }; -} - import * as fs from "fs/promises"; 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"; @@ -43,6 +28,7 @@ import { getPathFromUXFileInfo } from "@lib/common/typeUtils"; import { stripAllPrefixes } from "@lib/string_and_binary/path"; const SETTINGS_FILE = ".livesync/settings.json"; +ensureGlobalNodeLocalStorage(); defaultLoggerEnv.minLogLevel = LOG_LEVEL_DEBUG; function printHelp(): void { @@ -252,6 +238,7 @@ export async function main() { const settingsPath = options.settingsPath ? path.resolve(options.settingsPath) : path.join(vaultPath, SETTINGS_FILE); + configureNodeLocalStorage(path.join(vaultPath, ".livesync", "runtime", "local-storage.json")); infoLog(`Self-hosted LiveSync CLI`); infoLog(`Vault: ${vaultPath}`); diff --git a/src/apps/cli/services/NodeLocalStorage.ts b/src/apps/cli/services/NodeLocalStorage.ts new file mode 100644 index 0000000..c92338a --- /dev/null +++ b/src/apps/cli/services/NodeLocalStorage.ts @@ -0,0 +1,111 @@ +import * as nodeFs from "node:fs"; +import * as nodePath from "node:path"; + +type LocalStorageShape = { + getItem(key: string): string | null; + setItem(key: string, value: string): void; + removeItem(key: string): void; + clear(): void; +}; + +class PersistentNodeLocalStorage { + private storagePath: string | undefined; + private localStore: Record = {}; + + configure(storagePath: string) { + if (this.storagePath === storagePath) { + return; + } + this.storagePath = storagePath; + this.loadFromFile(); + } + + private loadFromFile() { + if (!this.storagePath) { + this.localStore = {}; + return; + } + try { + const loaded = JSON.parse(nodeFs.readFileSync(this.storagePath, "utf-8")) as Record; + this.localStore = { ...loaded }; + } catch { + this.localStore = {}; + } + } + + private flushToFile() { + if (!this.storagePath) { + return; + } + nodeFs.mkdirSync(nodePath.dirname(this.storagePath), { recursive: true }); + nodeFs.writeFileSync(this.storagePath, JSON.stringify(this.localStore, null, 2), "utf-8"); + } + + getItem(key: string): string | null { + return this.localStore[key] ?? null; + } + + setItem(key: string, value: string) { + this.localStore[key] = value; + this.flushToFile(); + } + + removeItem(key: string) { + if (!(key in this.localStore)) { + return; + } + delete this.localStore[key]; + this.flushToFile(); + } + + clear() { + this.localStore = {}; + this.flushToFile(); + } +} + +const persistentNodeLocalStorage = new PersistentNodeLocalStorage(); + +function createNodeLocalStorageShim(): LocalStorageShape { + return { + getItem(key: string) { + return persistentNodeLocalStorage.getItem(key); + }, + setItem(key: string, value: string) { + persistentNodeLocalStorage.setItem(key, value); + }, + removeItem(key: string) { + persistentNodeLocalStorage.removeItem(key); + }, + clear() { + persistentNodeLocalStorage.clear(); + }, + }; +} + +export function ensureGlobalNodeLocalStorage() { + if (!("localStorage" in globalThis) || typeof (globalThis as any).localStorage?.getItem !== "function") { + (globalThis as any).localStorage = createNodeLocalStorageShim(); + } +} + +export function configureNodeLocalStorage(storagePath: string) { + persistentNodeLocalStorage.configure(storagePath); + ensureGlobalNodeLocalStorage(); +} + +export function getNodeLocalStorageItem(key: string): string { + return persistentNodeLocalStorage.getItem(key) ?? ""; +} + +export function setNodeLocalStorageItem(key: string, value: string) { + persistentNodeLocalStorage.setItem(key, value); +} + +export function deleteNodeLocalStorageItem(key: string) { + persistentNodeLocalStorage.removeItem(key); +} + +export function clearNodeLocalStorage() { + persistentNodeLocalStorage.clear(); +} \ No newline at end of file diff --git a/src/apps/cli/services/NodeLocalStorage.unit.spec.ts b/src/apps/cli/services/NodeLocalStorage.unit.spec.ts new file mode 100644 index 0000000..1911508 --- /dev/null +++ b/src/apps/cli/services/NodeLocalStorage.unit.spec.ts @@ -0,0 +1,60 @@ +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { + clearNodeLocalStorage, + configureNodeLocalStorage, + ensureGlobalNodeLocalStorage, + getNodeLocalStorageItem, + setNodeLocalStorageItem, +} from "./NodeLocalStorage"; + +describe("NodeLocalStorage", () => { + const tempDirs: string[] = []; + + afterEach(() => { + clearNodeLocalStorage(); + for (const tempDir of tempDirs.splice(0)) { + fs.rmSync(tempDir, { recursive: true, force: true }); + } + }); + + it("persists values to the configured file", () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "livesync-node-local-storage-")); + tempDirs.push(tempDir); + const storagePath = path.join(tempDir, "runtime", "local-storage.json"); + + configureNodeLocalStorage(storagePath); + setNodeLocalStorageItem("checkpoint", "42"); + + const saved = JSON.parse(fs.readFileSync(storagePath, "utf-8")) as Record; + expect(saved.checkpoint).toBe("42"); + }); + + it("reloads persisted values when configured again", () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "livesync-node-local-storage-")); + tempDirs.push(tempDir); + const storagePath = path.join(tempDir, "runtime", "local-storage.json"); + + fs.mkdirSync(path.dirname(storagePath), { recursive: true }); + fs.writeFileSync(storagePath, JSON.stringify({ persisted: "value" }, null, 2), "utf-8"); + + configureNodeLocalStorage(storagePath); + + expect(getNodeLocalStorageItem("persisted")).toBe("value"); + }); + + it("installs a global localStorage shim backed by the same store", () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "livesync-node-local-storage-")); + tempDirs.push(tempDir); + const storagePath = path.join(tempDir, "runtime", "local-storage.json"); + + configureNodeLocalStorage(storagePath); + ensureGlobalNodeLocalStorage(); + + globalThis.localStorage.setItem("shared", "state"); + + expect(getNodeLocalStorageItem("shared")).toBe("state"); + }); +}); \ No newline at end of file diff --git a/src/apps/cli/services/NodeSettingService.ts b/src/apps/cli/services/NodeSettingService.ts index f231fba..0859183 100644 --- a/src/apps/cli/services/NodeSettingService.ts +++ b/src/apps/cli/services/NodeSettingService.ts @@ -5,17 +5,17 @@ import { handlers } from "@lib/services/lib/HandlerUtils"; import type { ObsidianLiveSyncSettings } from "@lib/common/types"; import type { ServiceContext } from "@lib/services/base/ServiceBase"; import { SettingService, type SettingServiceDependencies } from "@lib/services/base/SettingService"; -import * as nodeFs from "node:fs"; -import * as nodePath from "node:path"; +import { + configureNodeLocalStorage, + deleteNodeLocalStorageItem, + getNodeLocalStorageItem, + setNodeLocalStorageItem, +} from "./NodeLocalStorage"; export class NodeSettingService extends SettingService { - private storagePath: string; - private localStore: Record = {}; - constructor(context: T, dependencies: SettingServiceDependencies, storagePath: string) { super(context, dependencies); - this.storagePath = storagePath; - this.loadLocalStoreFromFile(); + configureNodeLocalStorage(storagePath); this.onSettingSaved.addHandler((settings) => { eventHub.emitEvent(EVENT_SETTING_SAVED, settings); return Promise.resolve(true); @@ -26,34 +26,16 @@ export class NodeSettingService extends SettingService }); } - private loadLocalStoreFromFile() { - try { - const loaded = JSON.parse(nodeFs.readFileSync(this.storagePath, "utf-8")) as Record; - this.localStore = { ...loaded }; - } catch { - this.localStore = {}; - } - } - - private flushLocalStoreToFile() { - nodeFs.mkdirSync(nodePath.dirname(this.storagePath), { recursive: true }); - nodeFs.writeFileSync(this.storagePath, JSON.stringify(this.localStore, null, 2), "utf-8"); - } - protected setItem(key: string, value: string) { - this.localStore[key] = value; - this.flushLocalStoreToFile(); + setNodeLocalStorageItem(key, value); } protected getItem(key: string): string { - return this.localStore[key] ?? ""; + return getNodeLocalStorageItem(key); } protected deleteItem(key: string): void { - if (key in this.localStore) { - delete this.localStore[key]; - this.flushLocalStoreToFile(); - } + deleteNodeLocalStorageItem(key); } public saveData = handlers<{ saveData: (data: ObsidianLiveSyncSettings) => Promise }>().binder("saveData"); diff --git a/updates.md b/updates.md index a0b2ab1..dbc9a70 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 + +2nd April, 2026 + +### CLI + +#### (may) Fixed, to be confirmed + +- Replication progress is now correctly saved and restored in the CLI. + + + ## ~~0.25.55~~ 0.25.56 30th March, 2026 @@ -206,91 +218,6 @@ As a result of recent refactoring, we are able to write tests more easily now! - `ModuleObsidianAPI` has been removed and implemented in `APIService` and `RemoteService`. - Now `APIService` is responsible for the network-online-status, not `databaseService.managers.networkManager`. -## 0.25.44 - -24th February, 2026 - -This release represents a significant architectural overhaul of the plug-in, focusing on modularity, testability, and stability. While many changes are internal, they pave the way for more robust features and easier maintenance. -However, as this update is very substantial, please do feel free to let me know if you encounter any issues. - -### Fixed - -- Ignore files (e.g., `.ignore`) are now handled efficiently. -- Replication & Database: - - Replication statistics are now correctly reset after switching replicators. -- Fixed `File already exists` for .md files has been merged (PR #802) So thanks @waspeer for the contribution! - -### Improved - -- Now we can configure network-error banners as icons, or hide them completely with the new `Network Warning Style` setting in the `General` pane of the settings dialogue. (#770, PR #804) - - Thanks so much to @A-wry! - -### Refactored - -#### Architectural Overhaul: - -- A major transition from Class-based Modules to a Service/Middleware architecture has begun. - - Many modules (for example, `ModulePouchDB`, `ModuleLocalDatabaseObsidian`, `ModuleKeyValueDB`) have been removed or integrated into specific Services (`database`, `keyValueDB`, etc.). - - Reduced reliance on dynamic binding and inverted dependencies; dependencies are now explicit. - - `ObsidianLiveSyncPlugin` properties (`replicator`, `localDatabase`, `storageAccess`, etc.) have been moved to their respective services for better separation of concerns. - - In this refactoring, the Service will henceforth, as a rule, cease to use setHandler, that is to say, simple lazy binding. - - They will be implemented directly in the service. - - However, not everything will be middlewarised. Modules that maintain state or make decisions based on the results of multiple handlers are permitted. -- Lifecycle: - - Application LifeCycle now starts in `Main` rather than `ServiceHub` or `ObsidianMenuModule`, ensuring smoother startup coordination. - -#### New Services & Utilities: - -- Added a `control` service to orchestrate other services (for example, handling stop/start logic during settings realisation). -- Added `UnresolvedErrorManager` to handle and display unresolved errors in a unified way. -- Added `logUtils` to unify logging injection and formatting. -- `VaultService.isTargetFile` now uses multiple, distinct checkers for better extensibility. - -#### Code Separation: - -- Separated Obsidian-specific logic from base logic for `StorageEventManager` and `FileAccess` modules. -- Moved reactive state values and statistics from the main plug-in instance to the services responsible for them. - -#### Internal Cleanups: - -- Many functions have been renamed for clarity (for example, `_isTargetFileByLocalDB` is now `_isTargetAcceptedByLocalDB`). -- Added `override` keywords to overridden items and removed dynamic binding for clearer code inheritance. -- Moved common functions to the common library. - -#### Dependencies: - -- Bumped dependencies simply to a point where they can be considered problem-free (by human-powered-artefacts-diff). - - Svelte, terser, and more something will be bumped later. They have a significant impact on the diff and paint it totally. - - You may be surprised, but when I bump the library, I am actually checking for any unintended code. - -## 0.25.43 - -5th, February, 2026 - -### Fixed - -- Encryption/decryption issues when using Object Storage as remote have been fixed. - - Now the plug-in falls back to V1 encryption/decryption when V2 fails (if not configured as ForceV1). - - This may fix the issue reported in #772. - -### Notice - -Quite a few packages have been updated in this release. Please report if you find any unexpected behaviour after this update. - -## 0.25.42 - -2nd, February, 2026 - -This release is identical to 0.25.41-patched-3, except for the version number. - -### Refactored - -- Now the service context is `protected` instead of `private` in `ServiceBase`. - - This change allows derived classes to access the context directly. -- Some dynamically bound services have been moved to services for better dependency management. -- `WebPeer` has been moved to the main repository from the sub repository `livesync-commonlib` for correct dependency management. -- Migrated from the outdated, unstable platform abstraction layer to services. - - A bit more services will be added in the future for better maintainability. Full notes are in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). diff --git a/updates_old.md b/updates_old.md index bb8fa75..0e219f7 100644 --- a/updates_old.md +++ b/updates_old.md @@ -3,6 +3,47 @@ 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.55~~ 0.25.56 + +30th March, 2026 + +### Fixed + +- No longer `Peer-to-Peer Sync is not enabled. We cannot open a new connection.` error occurs when we have not enabled P2P sync and are not expected to use it (#830). + +### CLI + +- Fixed incomplete localStorage support in the CLI (#831). Thank you so much @rewse ! +- Fixed the issue where the CLI could not be connected to the remote which had been locked once (#833), also thanks to @rewse ! + +## 0.25.54 + +18th March, 2026 + +### Fixed + +- Remote storage size check now works correctly again (#818). +- Some buttons on the settings dialogue now respond correctly again (#827). + +### Refactored + +- P2P replicator has been refactored to be a little more robust and easier to understand. +- Delete items which are no longer used that might cause potential problems + +### CLI + +- Fixed the corrupted display of the help message. +- Remove some unnecessary code. + +### WebApp + +- Fixed the issue where the detail level was not being applied in the log pane. +- Pop-ups are now shown. +- Add coverage for the test. +- Pop-ups are now shown in the web app as well. + ## 0.25.53 17th March, 2026 From 00f2606a2fc0564da97f0da9fe43000b5ec8d4ee Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 2 Apr 2026 10:31:03 +0100 Subject: [PATCH 119/339] Added a bit for development on Windows. --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfdb8b7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf From 2e3e106fb245e4576648a8f374376035b738d7fc Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 2 Apr 2026 10:31:17 +0100 Subject: [PATCH 120/339] Fix dockerfile --- 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 f1a24cf..7e85bdb 100644 --- a/src/apps/cli/Dockerfile +++ b/src/apps/cli/Dockerfile @@ -108,4 +108,4 @@ RUN chmod +x /usr/local/bin/livesync-cli # Mount your vault / local database directory here VOLUME ["/data"] -ENTRYPOINT ["/usr/local/bin/livesync-cli"] +ENTRYPOINT ["livesync-cli"] From 6ce724afb47f3de63b2ab90d5a3a4b29513019ac Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 2 Apr 2026 10:33:13 +0100 Subject: [PATCH 121/339] Add dockerfiles to webapp and webpeer --- src/apps/webapp/Dockerfile | 58 +++++++++++++++++++++++++++++++++++ src/apps/webapp/package.json | 2 ++ src/apps/webpeer/Dockerfile | 57 ++++++++++++++++++++++++++++++++++ src/apps/webpeer/package.json | 2 ++ 4 files changed, 119 insertions(+) create mode 100644 src/apps/webapp/Dockerfile create mode 100644 src/apps/webpeer/Dockerfile diff --git a/src/apps/webapp/Dockerfile b/src/apps/webapp/Dockerfile new file mode 100644 index 0000000..fbad070 --- /dev/null +++ b/src/apps/webapp/Dockerfile @@ -0,0 +1,58 @@ +# syntax=docker/dockerfile:1 +# +# Self-hosted LiveSync WebApp — Docker image +# Browser-based vault sync using the FileSystem API, served by nginx. +# +# Build (from the repository root): +# docker build -f src/apps/webapp/Dockerfile -t livesync-webapp . +# +# Run: +# docker run --rm -p 8080:80 livesync-webapp +# Then open http://localhost:8080/webapp.html in Chrome/Edge 86+. +# +# Notes: +# - This image serves purely static files; no server-side code is involved. +# - The FileSystem API is a browser feature and requires Chrome/Edge 86+ or +# Safari 15.2+ (limited). Firefox is not supported. +# - CouchDB / S3 connections are made directly from the browser; the container +# only serves HTML/JS/CSS assets. + +# ───────────────────────────────────────────────────────────────────────────── +# Stage 1 — builder +# Full Node.js environment to install dependencies and build the Vite bundle. +# ───────────────────────────────────────────────────────────────────────────── +FROM node:22-slim AS builder + +WORKDIR /build + +# Install workspace dependencies (all apps share the root package.json) +COPY package.json ./ +RUN npm install + +# Copy the full source tree and build the WebApp bundle +COPY . . +RUN cd src/apps/webapp && npm run build + +# ───────────────────────────────────────────────────────────────────────────── +# Stage 2 — runtime +# Minimal nginx image that serves the static build output. +# ───────────────────────────────────────────────────────────────────────────── +FROM nginx:stable-alpine + +# Remove the default nginx welcome page +RUN rm -rf /usr/share/nginx/html/* + +# Copy the built static assets +COPY --from=builder /build/src/apps/webapp/dist /usr/share/nginx/html + +# Redirect the root to webapp.html so the app loads on first visit +RUN printf 'server {\n\ + listen 80;\n\ + root /usr/share/nginx/html;\n\ + index webapp.html;\n\ + location / {\n\ + try_files $uri $uri/ =404;\n\ + }\n\ +}\n' > /etc/nginx/conf.d/default.conf + +EXPOSE 80 diff --git a/src/apps/webapp/package.json b/src/apps/webapp/package.json index 414c3e7..07e8f94 100644 --- a/src/apps/webapp/package.json +++ b/src/apps/webapp/package.json @@ -7,6 +7,8 @@ "scripts": { "dev": "vite", "build": "vite build", + "build:docker": "docker build -f Dockerfile -t livesync-webapp ../../..", + "run:docker": "docker run -p 8002:80 livesync-webapp", "preview": "vite preview" }, "dependencies": {}, diff --git a/src/apps/webpeer/Dockerfile b/src/apps/webpeer/Dockerfile new file mode 100644 index 0000000..ed69068 --- /dev/null +++ b/src/apps/webpeer/Dockerfile @@ -0,0 +1,57 @@ +# syntax=docker/dockerfile:1 +# +# Self-hosted LiveSync WebPeer — Docker image +# Browser-based P2P peer daemon served by nginx. +# +# Build (from the repository root): +# docker build -f src/apps/webpeer/Dockerfile -t livesync-webpeer . +# +# Run: +# docker run --rm -p 8081:80 livesync-webpeer +# Then open http://localhost:8081/ in any modern browser. +# +# What is WebPeer? +# WebPeer acts as a pseudo P2P peer that runs entirely in the browser. +# It can replace a CouchDB remote server by replying to sync requests from +# other Self-hosted LiveSync instances over the WebRTC P2P channel. +# +# P2P (WebRTC) networking notes +# ───────────────────────────── +# WebRTC connections are initiated by the *browser* visiting this page, not by +# the nginx container itself. Therefore the Docker network mode of this +# container has NO effect on WebRTC connectivity. +# Simply publish port 80 (as above) and the browser handles all ICE/STUN/TURN +# negotiation on its own. +# +# If the browser is running inside another container or a restricted network, +# configuring a TURN server in the WebPeer settings is recommended. + +# ───────────────────────────────────────────────────────────────────────────── +# Stage 1 — builder +# Full Node.js environment to install dependencies and build the Vite bundle. +# ───────────────────────────────────────────────────────────────────────────── +FROM node:22-slim AS builder + +WORKDIR /build + +# Install workspace dependencies (all apps share the root package.json) +COPY package.json ./ +RUN npm install + +# Copy the full source tree and build the WebPeer bundle +COPY . . +RUN cd src/apps/webpeer && npm run build + +# ───────────────────────────────────────────────────────────────────────────── +# Stage 2 — runtime +# Minimal nginx image that serves the static build output. +# ───────────────────────────────────────────────────────────────────────────── +FROM nginx:stable-alpine + +# Remove the default nginx welcome page +RUN rm -rf /usr/share/nginx/html/* + +# Copy the built static assets +COPY --from=builder /build/src/apps/webpeer/dist /usr/share/nginx/html + +EXPOSE 80 diff --git a/src/apps/webpeer/package.json b/src/apps/webpeer/package.json index 584432c..b2201f6 100644 --- a/src/apps/webpeer/package.json +++ b/src/apps/webpeer/package.json @@ -6,6 +6,8 @@ "scripts": { "dev": "vite", "build": "vite build", + "build:docker": "docker build -f Dockerfile -t livesync-webpeer ../../..", + "run:docker": "docker run -p 8001:80 livesync-webpeer", "preview": "vite preview", "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json" }, From 216861f2c38d0158860df2cf6f8a298852e65bff Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 2 Apr 2026 10:33:36 +0100 Subject: [PATCH 122/339] Prettified --- src/apps/cli/services/NodeLocalStorage.ts | 2 +- src/apps/cli/services/NodeLocalStorage.unit.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/cli/services/NodeLocalStorage.ts b/src/apps/cli/services/NodeLocalStorage.ts index c92338a..3e96822 100644 --- a/src/apps/cli/services/NodeLocalStorage.ts +++ b/src/apps/cli/services/NodeLocalStorage.ts @@ -108,4 +108,4 @@ export function deleteNodeLocalStorageItem(key: string) { export function clearNodeLocalStorage() { persistentNodeLocalStorage.clear(); -} \ No newline at end of file +} diff --git a/src/apps/cli/services/NodeLocalStorage.unit.spec.ts b/src/apps/cli/services/NodeLocalStorage.unit.spec.ts index 1911508..6096eef 100644 --- a/src/apps/cli/services/NodeLocalStorage.unit.spec.ts +++ b/src/apps/cli/services/NodeLocalStorage.unit.spec.ts @@ -57,4 +57,4 @@ describe("NodeLocalStorage", () => { expect(getNodeLocalStorageItem("shared")).toBe("state"); }); -}); \ No newline at end of file +}); From 6cce931a88490de7413919db153d15f4612075f0 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 2 Apr 2026 09:58:25 +0000 Subject: [PATCH 123/339] Add test for CLI --- src/apps/cli/test/test-e2e-two-vaults-common.sh | 16 ++++++++++++++++ updates.md | 4 +--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/apps/cli/test/test-e2e-two-vaults-common.sh b/src/apps/cli/test/test-e2e-two-vaults-common.sh index 812d290..9daab6b 100644 --- a/src/apps/cli/test/test-e2e-two-vaults-common.sh +++ b/src/apps/cli/test/test-e2e-two-vaults-common.sh @@ -136,6 +136,8 @@ fi TARGET_A_ONLY="e2e/a-only-info.md" TARGET_SYNC="e2e/sync-info.md" +TARGET_SYNC_TWICE_FIRST="e2e/sync-twice-first.md" +TARGET_SYNC_TWICE_SECOND="e2e/sync-twice-second.md" TARGET_PUSH="e2e/pushed-from-a.md" TARGET_PUT="e2e/put-from-a.md" TARGET_PUSH_BINARY="e2e/pushed-from-a.bin" @@ -154,6 +156,20 @@ INFO_B_SYNC="$(run_cli_b info "$TARGET_SYNC")" cli_test_assert_contains "$INFO_B_SYNC" "\"path\": \"$TARGET_SYNC\"" "B info should include path after sync" echo "[PASS] sync A->B and B info" +echo "[CASE] B can sync again after first replication has completed" +printf 'first-sync-round-%s\n' "$DB_SUFFIX" | run_cli_a put "$TARGET_SYNC_TWICE_FIRST" >/dev/null +run_cli_a sync >/dev/null +run_cli_b sync >/dev/null +CAT_B_SYNC_TWICE_FIRST="$(run_cli_b cat "$TARGET_SYNC_TWICE_FIRST" | cli_test_sanitise_cat_stdout)" +cli_test_assert_equal "first-sync-round-$DB_SUFFIX" "$CAT_B_SYNC_TWICE_FIRST" "B should receive first update after first sync" + +printf 'second-sync-round-%s\n' "$DB_SUFFIX" | run_cli_a put "$TARGET_SYNC_TWICE_SECOND" >/dev/null +run_cli_a sync >/dev/null +run_cli_b sync >/dev/null +CAT_B_SYNC_TWICE_SECOND="$(run_cli_b cat "$TARGET_SYNC_TWICE_SECOND" | cli_test_sanitise_cat_stdout)" +cli_test_assert_equal "second-sync-round-$DB_SUFFIX" "$CAT_B_SYNC_TWICE_SECOND" "B should receive second update after re-running sync" +echo "[PASS] second sync after completion works" + echo "[CASE] A pushes and puts, both sync, and B can pull and cat" PUSH_SRC="$WORK_DIR/push-source.txt" PULL_DST="$WORK_DIR/pull-destination.txt" diff --git a/updates.md b/updates.md index dbc9a70..48d45d4 100644 --- a/updates.md +++ b/updates.md @@ -9,12 +9,10 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid ### CLI -#### (may) Fixed, to be confirmed +#### Fixed - Replication progress is now correctly saved and restored in the CLI. - - ## ~~0.25.55~~ 0.25.56 30th March, 2026 From 8b40969fa3cf3ab0b234a248703793b7c7bde1b0 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 2 Apr 2026 10:28:58 +0000 Subject: [PATCH 124/339] Add ru locale --- src/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib b/src/lib index bed8afd..c6229cd 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit bed8afd2dc55c6252e181c9487a25d2d6d28221d +Subproject commit c6229cd14a23bc871077349ff0377b2def1c73ae From f17f1ecd93392a5d0adad7826c4353aec4542628 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Fri, 3 Apr 2026 13:47:56 +0100 Subject: [PATCH 125/339] ### Fixed - No unexpected error (about a replicator) during early stage of initialisation. ### New features - Now we can configure multiple Remote Databases of the same type, e.g, multiple CouchDBs or S3 remotes. - We can switch between multiple Remote Databases in the settings dialogue. --- src/LiveSyncBaseCore.ts | 3 + src/apps/webapp/main.ts | 2 + src/lib | 2 +- src/main.ts | 4 + .../SettingDialogue/PaneRemoteConfig.ts | 310 ++++++++++++++++-- updates.md | 17 + 6 files changed, 316 insertions(+), 22 deletions(-) diff --git a/src/LiveSyncBaseCore.ts b/src/LiveSyncBaseCore.ts index 87eb268..f9f0427 100644 --- a/src/LiveSyncBaseCore.ts +++ b/src/LiveSyncBaseCore.ts @@ -13,6 +13,7 @@ import type { CheckPointInfo } from "./lib/src/replication/journal/JournalSyncTy import type { LiveSyncJournalReplicatorEnv } from "./lib/src/replication/journal/LiveSyncJournalReplicatorEnv"; import type { LiveSyncReplicatorEnv } from "./lib/src/replication/LiveSyncAbstractReplicator"; import { useTargetFilters } from "./lib/src/serviceFeatures/targetFilter"; +import { useRemoteConfigurationMigration } from "./lib/src/serviceFeatures/remoteConfig"; import type { ServiceContext } from "./lib/src/services/base/ServiceBase"; import type { InjectableServiceHub } from "./lib/src/services/InjectableServices"; import { AbstractModule } from "./modules/AbstractModule"; @@ -272,6 +273,8 @@ export class LiveSyncBaseCore< useTargetFilters(this); // enable target filter feature. usePrepareDatabaseForUse(this); + // Migration to multiple remote configurations + useRemoteConfigurationMigration(this); } } diff --git a/src/apps/webapp/main.ts b/src/apps/webapp/main.ts index 67db1bd..2d96c83 100644 --- a/src/apps/webapp/main.ts +++ b/src/apps/webapp/main.ts @@ -14,6 +14,7 @@ import { useOfflineScanner } from "@lib/serviceFeatures/offlineScanner"; import { useRedFlagFeatures } from "@/serviceFeatures/redFlag"; import { useCheckRemoteSize } from "@lib/serviceFeatures/checkRemoteSize"; import { useSetupURIFeature } from "@lib/serviceFeatures/setupObsidian/setupUri"; +import { useRemoteConfiguration } from "@lib/serviceFeatures/remoteConfig"; import { SetupManager } from "@/modules/features/SetupManager"; import { useSetupManagerHandlersFeature } from "@/serviceFeatures/setupObsidian/setupManagerHandlers"; import { useP2PReplicatorCommands } from "@/lib/src/replication/trystero/useP2PReplicatorCommands"; @@ -132,6 +133,7 @@ class LiveSyncWebApp { useOfflineScanner(core); useRedFlagFeatures(core); useCheckRemoteSize(core); + useRemoteConfiguration(core); const replicator = useP2PReplicatorFeature(core); useP2PReplicatorCommands(core, replicator); const setupManager = core.getModule(SetupManager); diff --git a/src/lib b/src/lib index c6229cd..d14de2d 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit c6229cd14a23bc871077349ff0377b2def1c73ae +Subproject commit d14de2d8fc5b712354d30772a2422b0599916883 diff --git a/src/main.ts b/src/main.ts index ab89820..a07ce40 100644 --- a/src/main.ts +++ b/src/main.ts @@ -33,6 +33,7 @@ import { SetupManager } from "./modules/features/SetupManager.ts"; import { ModuleMigration } from "./modules/essential/ModuleMigration.ts"; import { enableI18nFeature } from "./serviceFeatures/onLayoutReady/enablei18n.ts"; import { useOfflineScanner } from "@lib/serviceFeatures/offlineScanner.ts"; +import { useRemoteConfiguration } from "@lib/serviceFeatures/remoteConfig.ts"; import { useCheckRemoteSize } from "@lib/serviceFeatures/checkRemoteSize.ts"; import { useRedFlagFeatures } from "./serviceFeatures/redFlag.ts"; import { useSetupProtocolFeature } from "./serviceFeatures/setupObsidian/setupProtocol.ts"; @@ -174,6 +175,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin { const curriedFeature = () => featuresInitialiser(core); core.services.appLifecycle.onLayoutReady.addHandler(curriedFeature); const setupManager = core.getModule(SetupManager); + + useRemoteConfiguration(core); + useSetupProtocolFeature(core, setupManager); useSetupQRCodeFeature(core); useSetupURIFeature(core); diff --git a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts index 216fa47..e3e3883 100644 --- a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts +++ b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts @@ -2,6 +2,7 @@ import { REMOTE_COUCHDB, REMOTE_MINIO, REMOTE_P2P, + DEFAULT_SETTINGS, type ObsidianLiveSyncSettings, } from "../../../lib/src/common/types.ts"; import { $msg } from "../../../lib/src/common/i18n.ts"; @@ -21,6 +22,13 @@ import { import { SETTING_KEY_P2P_DEVICE_NAME } from "../../../lib/src/common/types.ts"; import { SetupManager, UserMode } from "../SetupManager.ts"; import { OnDialogSettingsDefault, type AllSettings } from "./settingConstants.ts"; +import { activateRemoteConfiguration } from "../../../lib/src/serviceFeatures/remoteConfig.ts"; +import { ConnectionStringParser } from "../../../lib/src/common/ConnectionString.ts"; +import type { RemoteConfiguration } from "../../../lib/src/common/models/setting.type.ts"; +import SetupRemote from "../SetupWizard/dialogs/SetupRemote.svelte"; +import SetupRemoteCouchDB from "../SetupWizard/dialogs/SetupRemoteCouchDB.svelte"; +import SetupRemoteBucket from "../SetupWizard/dialogs/SetupRemoteBucket.svelte"; +import SetupRemoteP2P from "../SetupWizard/dialogs/SetupRemoteP2P.svelte"; function getSettingsFromEditingSettings(editingSettings: AllSettings): ObsidianLiveSyncSettings { const workObj = { ...editingSettings } as ObsidianLiveSyncSettings; @@ -39,17 +47,31 @@ const toggleActiveSyncClass = (el: HTMLElement, isActive: () => boolean) => { return {}; }; +function createRemoteConfigurationId(): string { + return `remote-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`; +} + +function cloneRemoteConfigurations( + configs: Record | undefined +): Record { + return Object.fromEntries(Object.entries(configs || {}).map(([id, config]) => [id, { ...config }])); +} + +function serializeRemoteConfiguration(settings: ObsidianLiveSyncSettings): string { + if (settings.remoteType === REMOTE_MINIO) { + return ConnectionStringParser.serialize({ type: "s3", settings }); + } + if (settings.remoteType === REMOTE_P2P) { + return ConnectionStringParser.serialize({ type: "p2p", settings }); + } + return ConnectionStringParser.serialize({ type: "couchdb", settings }); +} + export function paneRemoteConfig( this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement, { addPanel, addPane }: PageFunctions ): void { - const remoteNameMap = { - [REMOTE_COUCHDB]: $msg("obsidianLiveSyncSettingTab.optionCouchDB"), - [REMOTE_MINIO]: $msg("obsidianLiveSyncSettingTab.optionMinioS3R2"), - [REMOTE_P2P]: "Only Peer-to-Peer", - } as const; - { /* E2EE */ const E2EEInitialProps = { @@ -91,24 +113,268 @@ export function paneRemoteConfig( }); } { + // TODO: very WIP. need to refactor the UI. void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleRemoteServer"), () => {}).then((paneEl) => { - const setting = new Setting(paneEl).setName($msg("Active Remote Configuration")); + const actions = new Setting(paneEl).setName("Remote Databases"); + // actions.addButton((button) => + // button + // .setButtonText("Change Remote and Setup") + // .setCta() + // .onClick(async () => { + // const setupManager = this.core.getModule(SetupManager); + // const originalSettings = getSettingsFromEditingSettings(this.editingSettings); + // await setupManager.onSelectServer(originalSettings, UserMode.Update); + // }) + // ); - const el = setting.controlEl.createDiv({}); - el.setText(`${remoteNameMap[this.editingSettings.remoteType] || " - "}`); - setting.addButton((button) => - button - .setButtonText("Change Remote and Setup") - .setCta() - .onClick(async () => { - const setupManager = this.core.getModule(SetupManager); - const originalSettings = getSettingsFromEditingSettings(this.editingSettings); - await setupManager.onSelectServer(originalSettings, UserMode.Update); - }) + // Connection List + const listContainer = paneEl.createDiv({ cls: "sls-remote-list" }); + const syncRemoteConfigurationBuffers = () => { + const currentConfigs = cloneRemoteConfigurations(this.core.settings.remoteConfigurations); + this.editingSettings.remoteConfigurations = currentConfigs; + this.editingSettings.activeConfigurationId = this.core.settings.activeConfigurationId; + if (this.initialSettings) { + this.initialSettings.remoteConfigurations = cloneRemoteConfigurations(currentConfigs); + this.initialSettings.activeConfigurationId = this.core.settings.activeConfigurationId; + } + }; + const persistRemoteConfigurations = async (synchroniseActiveRemote: boolean = false) => { + await this.services.setting.updateSettings((currentSettings) => { + currentSettings.remoteConfigurations = cloneRemoteConfigurations( + this.editingSettings.remoteConfigurations + ); + currentSettings.activeConfigurationId = this.editingSettings.activeConfigurationId; + if (synchroniseActiveRemote && currentSettings.activeConfigurationId) { + const activated = activateRemoteConfiguration( + currentSettings, + currentSettings.activeConfigurationId + ); + if (activated) { + return activated; + } + } + return currentSettings; + }, true); + + if (synchroniseActiveRemote) { + await this.saveAllDirtySettings(); + } + + syncRemoteConfigurationBuffers(); + this.requestUpdate(); + }; + const runRemoteSetup = async ( + baseSettings: ObsidianLiveSyncSettings, + remoteType?: typeof REMOTE_COUCHDB | typeof REMOTE_MINIO | typeof REMOTE_P2P + ): Promise => { + const setupManager = this.core.getModule(SetupManager); + const dialogManager = setupManager.dialogManager; + let targetRemoteType = remoteType; + + if (targetRemoteType === undefined) { + const method = await dialogManager.openWithExplicitCancel(SetupRemote); + if (method === "cancelled") { + return false; + } + targetRemoteType = + method === "bucket" ? REMOTE_MINIO : method === "p2p" ? REMOTE_P2P : REMOTE_COUCHDB; + } + + if (targetRemoteType === REMOTE_MINIO) { + const bucketConf = await dialogManager.openWithExplicitCancel(SetupRemoteBucket, baseSettings); + if (bucketConf === "cancelled" || typeof bucketConf !== "object") { + return false; + } + return { ...baseSettings, ...bucketConf, remoteType: REMOTE_MINIO }; + } + + if (targetRemoteType === REMOTE_P2P) { + const p2pConf = await dialogManager.openWithExplicitCancel(SetupRemoteP2P, baseSettings); + if (p2pConf === "cancelled" || typeof p2pConf !== "object") { + return false; + } + return { ...baseSettings, ...p2pConf, remoteType: REMOTE_P2P }; + } + + const couchConf = await dialogManager.openWithExplicitCancel(SetupRemoteCouchDB, baseSettings); + if (couchConf === "cancelled" || typeof couchConf !== "object") { + return false; + } + return { ...baseSettings, ...couchConf, remoteType: REMOTE_COUCHDB }; + }; + const createBaseRemoteSettings = (): ObsidianLiveSyncSettings => ({ + ...DEFAULT_SETTINGS, + ...getSettingsFromEditingSettings(this.editingSettings), + }); + const createNewRemoteSettings = (): ObsidianLiveSyncSettings => ({ + ...DEFAULT_SETTINGS, + encrypt: this.editingSettings.encrypt, + usePathObfuscation: this.editingSettings.usePathObfuscation, + passphrase: this.editingSettings.passphrase, + configPassphraseStore: this.editingSettings.configPassphraseStore, + }); + const addRemoteConfiguration = async () => { + const name = await this.services.UI.confirm.askString("Remote name", "Display name", "New Remote"); + if (name === false) { + return; + } + const nextSettings = await runRemoteSetup(createNewRemoteSettings()); + if (!nextSettings) { + return; + } + const id = createRemoteConfigurationId(); + const configs = cloneRemoteConfigurations(this.editingSettings.remoteConfigurations); + configs[id] = { + id, + name: name.trim() || "New Remote", + uri: serializeRemoteConfiguration(nextSettings), + isEncrypted: nextSettings.encrypt, + }; + this.editingSettings.remoteConfigurations = configs; + if (!this.editingSettings.activeConfigurationId) { + this.editingSettings.activeConfigurationId = id; + } + await persistRemoteConfigurations(this.editingSettings.activeConfigurationId === id); + refreshList(); + }; + actions.addButton((button) => + button.setButtonText("Add New Connection").onClick(async () => { + await addRemoteConfiguration(); + }) ); + const refreshList = () => { + listContainer.empty(); + const configs = this.editingSettings.remoteConfigurations || {}; + for (const config of Object.values(configs)) { + const row = new Setting(listContainer) + .setName(config.name) + .setDesc(config.uri.split("@").pop() || ""); // Show host part for privacy + + if (config.id === this.editingSettings.activeConfigurationId) { + row.nameEl.addClass("sls-active-remote-name"); + row.nameEl.appendText(" (Active)"); + } + + row.addButton((btn) => + btn.setButtonText("Configure").onClick(async () => { + const parsed = ConnectionStringParser.parse(config.uri); + const workSettings = createBaseRemoteSettings(); + if (parsed.type === "couchdb") { + workSettings.remoteType = REMOTE_COUCHDB; + } else if (parsed.type === "s3") { + workSettings.remoteType = REMOTE_MINIO; + } else { + workSettings.remoteType = REMOTE_P2P; + } + Object.assign(workSettings, parsed.settings); + + const nextSettings = await runRemoteSetup(workSettings, workSettings.remoteType); + if (!nextSettings) { + return; + } + + const nextConfigs = cloneRemoteConfigurations(this.editingSettings.remoteConfigurations); + nextConfigs[config.id] = { + ...config, + uri: serializeRemoteConfiguration(nextSettings), + isEncrypted: nextSettings.encrypt, + }; + this.editingSettings.remoteConfigurations = nextConfigs; + await persistRemoteConfigurations(config.id === this.editingSettings.activeConfigurationId); + refreshList(); + }) + ); + row.addButton((btn) => + btn.setButtonText("Rename").onClick(async () => { + const nextName = await this.services.UI.confirm.askString( + "Remote name", + "Display name", + config.name + ); + if (nextName === false) { + return; + } + const nextConfigs = cloneRemoteConfigurations(this.editingSettings.remoteConfigurations); + nextConfigs[config.id] = { + ...config, + name: nextName.trim() || config.name, + }; + this.editingSettings.remoteConfigurations = nextConfigs; + await persistRemoteConfigurations(); + refreshList(); + }) + ); + row.addButton((btn) => + btn.setButtonText("Duplicate").onClick(async () => { + const nextName = await this.services.UI.confirm.askString( + "Duplicate remote", + "Display name", + `${config.name} (Copy)` + ); + if (nextName === false) { + return; + } + + const nextId = createRemoteConfigurationId(); + const nextConfigs = cloneRemoteConfigurations(this.editingSettings.remoteConfigurations); + nextConfigs[nextId] = { + ...config, + id: nextId, + name: nextName.trim() || `${config.name} (Copy)`, + }; + this.editingSettings.remoteConfigurations = nextConfigs; + await persistRemoteConfigurations(); + refreshList(); + }) + ); + row.addButton((btn) => + btn + .setButtonText("Delete") + .setWarning() + .onClick(async () => { + const confirmed = await this.services.UI.confirm.askYesNoDialog( + `Delete remote configuration '${config.name}'?`, + { title: "Delete Remote Configuration", defaultOption: "No" } + ); + if (confirmed !== "yes") { + return; + } + + const nextConfigs = cloneRemoteConfigurations( + this.editingSettings.remoteConfigurations + ); + delete nextConfigs[config.id]; + this.editingSettings.remoteConfigurations = nextConfigs; + + let syncActiveRemote = false; + if (this.editingSettings.activeConfigurationId === config.id) { + const nextActiveId = Object.keys(nextConfigs)[0] || ""; + this.editingSettings.activeConfigurationId = nextActiveId; + syncActiveRemote = nextActiveId !== ""; + } + + await persistRemoteConfigurations(syncActiveRemote); + refreshList(); + }) + ); + + row.addButton((btn) => + btn + .setButtonText("Activate") + .setDisabled(config.id === this.editingSettings.activeConfigurationId) + .onClick(async () => { + this.editingSettings.activeConfigurationId = config.id; + await persistRemoteConfigurations(true); + refreshList(); + }) + ); + } + }; + refreshList(); }); } - { + // eslint-disable-next-line no-constant-condition + if (false) { const initialProps = { info: getCouchDBConfigSummary(this.editingSettings), }; @@ -143,7 +409,8 @@ export function paneRemoteConfig( ); }); } - { + // eslint-disable-next-line no-constant-condition + if (false) { const initialProps = { info: getBucketConfigSummary(this.editingSettings), }; @@ -178,7 +445,8 @@ export function paneRemoteConfig( ); }); } - { + // eslint-disable-next-line no-constant-condition + if (false) { const getDevicePeerId = () => this.services.config.getSmallConfig(SETTING_KEY_P2P_DEVICE_NAME) || ""; const initialProps = { info: getP2PConfigSummary(this.editingSettings, { diff --git a/updates.md b/updates.md index 48d45d4..df9579f 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,23 @@ 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 2 + +3rd April, 2026 + +As this commit is a bit of a fragile matter, I shall add a note here. + +You know that untagged updates shall not be tested well. please be careful to use your own build. In most cases, I check that the warnings have disappeared, that the code compiles successfully without any warnings, and that it runs on the desktop. + +### Fixed + +- No unexpected error (about a replicator) during early stage of initialisation. + +### New features + +- Now we can configure multiple Remote Databases of the same type, e.g, multiple CouchDBs or S3 remotes. +- We can switch between multiple Remote Databases in the settings dialogue. + ## Unreleased 2nd April, 2026 From d7088be8af5ba47ea1f8848c481ffae23a3b3b8c Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sun, 5 Apr 2026 16:00:57 +0900 Subject: [PATCH 126/339] Improved: remote management --- docs/design_docs_of_remote_configurations.md | 206 +++++++++++++++ src/apps/cli/commands/runCommand.ts | 2 +- src/apps/cli/commands/runCommand.unit.spec.ts | 7 +- src/apps/cli/package.json | 80 +++--- src/apps/cli/runtime-package.json | 48 ++-- src/apps/webpeer/src/P2PReplicatorShim.ts | 2 +- .../P2PReplicator/P2PReplicatorPaneView.ts | 2 +- src/lib | 2 +- .../ModuleObsidianSettingAsMarkdown.ts | 4 +- .../SettingDialogue/PaneRemoteConfig.ts | 247 ++++++++++++------ src/modules/features/SetupManager.ts | 11 +- .../features/SetupManager.unit.spec.ts | 157 +++++++++++ src/serviceFeatures/redFlag.ts | 2 +- src/serviceFeatures/redFlag.unit.spec.ts | 8 +- 14 files changed, 619 insertions(+), 159 deletions(-) create mode 100644 docs/design_docs_of_remote_configurations.md create mode 100644 src/modules/features/SetupManager.unit.spec.ts diff --git a/docs/design_docs_of_remote_configurations.md b/docs/design_docs_of_remote_configurations.md new file mode 100644 index 0000000..1fdd2fb --- /dev/null +++ b/docs/design_docs_of_remote_configurations.md @@ -0,0 +1,206 @@ +# The design document of remote configuration management + +## Goal + +- Allow us to manage multiple remote connections in a single vault. +- Keep the existing synchronisation implementations working without requiring a large rewrite. +- Provide a safe migration path from the previous single-remote configuration model. +- Allow connections to be imported and exported in a compact and reusable format. + +## Motivation + +Historically, Self-hosted LiveSync stored one effective remote configuration directly in the main settings. This was simple, but it had several limitations. + +- We could only keep one CouchDB, one bucket, or one Peer-to-Peer target as the effective configuration at a time. +- Switching between same-type-remotes required manually rewriting the active settings. +- Setup URI, QR code, CLI setup, and similar entry points all restored settings differently, which made migration logic easy to miss. +- The internal settings shape had gradually become a mix of user-facing settings, transport-specific credentials, and compatibility-oriented values. + +Once multiple remotes of the same type became desirable, the previous model no longer scaled well enough. We therefore needed a structure that could store many remotes, still expose one effective remote to the replication logic, and keep migration and import behaviour consistent. + +## Prerequisite + +- Existing synchronisation features must continue to read an effective remote configuration from the current settings. +- Existing vaults must continue to work without requiring manual reconfiguration. +- Setup URI, QR code, CLI setup, protocol handlers, and other imported settings must be normalised in the same way. +- Import and export must be compact enough to be shared easily. +- We must be explicit that exported connection strings may contain credentials or secrets. + +## Outlined methods and implementation plans + +### Abstract + +The current settings now have two layers for remote configuration. + +1. A stored collection of named remotes. +2. One active remote projected into the legacy flat settings fields. + +This means the replication and database layers can continue to read the effective remote from the existing settings fields, while the settings dialogue and migration logic can manage many stored remotes. + +In short, the list is the source of truth for saved remotes, and the legacy fields remain the runtime compatibility layer. + +### Data model + +The main settings now contain the following properties. + +```typescript +type RemoteConfiguration = { + id: string; + name: string; + uri: string; + isEncrypted: boolean; +}; + +type RemoteConfigurations = { + remoteConfigurations: Record; + activeConfigurationId: string; +}; +``` + +Each entry stores a connection string in `uri`. + +- `sls+http://` or `sls+https://` for CouchDB-compatible remotes +- `sls+s3://` for bucket-style remotes +- `sls+p2p://` for Peer-to-Peer remotes + +This structure allows multiple remotes of the same type to be stored without adding a large number of duplicated settings fields. + +### Runtime compatibility + +The replication logic still reads the effective remote from legacy flat settings such as the following. + +- `remoteType` +- `couchDB_URI`, `couchDB_USER`, `couchDB_PASSWORD`, `couchDB_DBNAME` +- `endpoint`, `bucket`, `accessKey`, `secretKey`, and related bucket fields +- `P2P_roomID`, `P2P_passphrase`, and related Peer-to-Peer fields + +When a remote is activated, its connection string is parsed and projected into these legacy fields. Therefore, existing services do not need to know whether the remote came from an old vault, a Setup URI, or the new remote list. + +This projection is intentionally one-way at runtime. The stored remote list is the persistent catalogue, while the flat fields describe the remote currently in use. + +### Connection string format + +The connection string is the transport-neutral storage format for a remote entry. + +Benefits: + +- It is compact enough for clipboard-based workflows. +- It can be used for import and export in the settings dialogue. +- It avoids introducing a separate serialisation format only for the remote list. +- It can be parsed into the legacy settings shape whenever the active remote changes. + +This is not equivalent to Setup URI. + +- Setup URI represents a broader settings transfer workflow. +- A remote connection string represents one remote only. + +### Import and export + +The settings dialogue now supports the following workflows. + +- Add connection: create a new remote by using the remote setup dialogues. +- Import connection: paste a connection string, validate it, and save it as a named remote. +- Export: copy a stored remote connection string to the clipboard. + +Import normalises the string by parsing and serialising it again before saving. This ensures that equivalent but differently formatted URIs are saved in a canonical form. + +Export is intentionally simple. It copies the connection string itself, because this is the most direct representation of one remote entry. + +### Security note + +Connection strings may include credentials, secrets, JWT-related values, or Peer-to-Peer passphrases. + +Therefore: + +- Export is a deliberate clipboard operation. +- Import trusts the supplied connection string as-is after parsing. +- We should regard exported connection strings as sensitive information, much like Setup URI or a credentials-bearing configuration file. + +The `isEncrypted` field is currently reserved for future expansion. At present, the connection string itself is stored plainly inside the settings data, in the same sense that the effective runtime configuration can contain usable remote credentials. + +### Migration strategy + +Older vaults store only one effective remote in the flat settings fields. The migration creates a first remote list from those values. + +Rules: + +- If no remote list exists and the legacy fields contain a CouchDB configuration, create `legacy-couchdb`. +- If no remote list exists and the legacy fields contain a bucket configuration, create `legacy-s3`. +- If no remote list exists and the legacy fields contain a Peer-to-Peer configuration, create `legacy-p2p`. +- If more than one legacy remote is populated, create all possible entries and select the active one according to `remoteType`. + +This migration is intentionally additive. It does not remove the flat fields because they remain necessary as the active runtime projection. + +### Normalisation and application paths + +One important design lesson from this work is that migration cannot rely only on loading `data.json`. + +Settings may enter the system from several routes: + +- normal settings load +- Setup URI +- QR code +- protocol handler +- CLI setup +- Peer-to-Peer remote configuration retrieval +- red flag based remote adjustment +- settings markdown import + +To keep behaviour consistent, normalisation is centralised in the settings service. + +- `adjustSettings` is responsible for in-place normalisation and migration of a settings object. +- `applyExternalSettings` is responsible for applying imported or externally supplied settings after passing them through the same normalisation flow. + +This ensures that imported settings can migrate to the current remote list model even if they never passed through the ordinary `loadSettings` path. + +### Why not store only the remote list + +It would be possible to let all consumers parse the active remote every time and stop using the flat fields entirely. However, this would require broader changes across replication, diagnostics, and compatibility layers. + +The current design keeps the change set limited. + +- The remote list improves storage and UX. +- The flat fields preserve compatibility and reduce migration risk. + +This is a pragmatic transitional architecture, not an accidental duplication. + +## Test strategy + +The feature should be tested from four viewpoints. + +1. Migration from old settings. + - A vault with only legacy flat remote settings should gain a remote list automatically. + - The correct active remote should be selected according to `remoteType`. + +2. Runtime activation. + - Activating a stored remote should correctly project its values into the effective flat settings. + +3. External import paths. + - Setup URI, QR code, CLI setup, Peer-to-Peer remote config, red flag adjustment, and settings markdown import should all pass through the same normalisation path. + +4. Import and export. + - Imported connection strings should be parsed, canonicalised, named, and stored correctly. + - Export should copy the exact saved connection string. + +## Documentation strategy + +- This document explains the design and compatibility model of remote configuration management. +- User-facing setup documents should explain only how to add, import, export, and activate remotes. +- Release notes may refer to this document when changes in remote handling are significant. + +## Outlook + +Import/export configuration strings should also be encrypted in the future, but this is a separate feature that can be added on top of the current design. + +## Consideration and conclusion + +The remote configuration list solves the practical need to manage multiple remotes without forcing the whole codebase to abandon the previous effective-settings model at once. + +Its core idea is modest but effective. + +- Store named remotes as connection strings. +- Select one active remote. +- Project it into the legacy settings for runtime use. +- Normalise every imported settings object through the same path. + +This keeps the implementation understandable and migration-friendly. It also opens the door for future work, such as encrypted per-remote storage, richer remote metadata, or remote-scoped options, without forcing another large redesign of how remotes are represented. \ No newline at end of file diff --git a/src/apps/cli/commands/runCommand.ts b/src/apps/cli/commands/runCommand.ts index 7672d50..12a315b 100644 --- a/src/apps/cli/commands/runCommand.ts +++ b/src/apps/cli/commands/runCommand.ts @@ -166,7 +166,7 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext } as ObsidianLiveSyncSettings; console.log(`[Command] setup -> ${settingsPath}`); - await core.services.setting.applyPartial(nextSettings, true); + await core.services.setting.applyExternalSettings(nextSettings, true); await core.services.control.applySettings(); return true; } diff --git a/src/apps/cli/commands/runCommand.unit.spec.ts b/src/apps/cli/commands/runCommand.unit.spec.ts index a616656..1a5b3da 100644 --- a/src/apps/cli/commands/runCommand.unit.spec.ts +++ b/src/apps/cli/commands/runCommand.unit.spec.ts @@ -14,6 +14,7 @@ function createCoreMock() { applySettings: vi.fn(async () => {}), }, setting: { + applyExternalSettings: vi.fn(async () => {}), applyPartial: vi.fn(async () => {}), }, }, @@ -176,9 +177,9 @@ describe("runCommand abnormal cases", () => { }); expect(result).toBe(true); - expect(core.services.setting.applyPartial).toHaveBeenCalledTimes(1); + expect(core.services.setting.applyExternalSettings).toHaveBeenCalledTimes(1); expect(core.services.control.applySettings).toHaveBeenCalledTimes(1); - const [appliedSettings, saveImmediately] = core.services.setting.applyPartial.mock.calls[0]; + const [appliedSettings, saveImmediately] = core.services.setting.applyExternalSettings.mock.calls[0]; expect(saveImmediately).toBe(true); expect(appliedSettings.couchDB_URI).toBe("http://127.0.0.1:5984"); expect(appliedSettings.couchDB_DBNAME).toBe("livesync-test-db"); @@ -198,7 +199,7 @@ describe("runCommand abnormal cases", () => { }) ).rejects.toThrow(); - expect(core.services.setting.applyPartial).not.toHaveBeenCalled(); + expect(core.services.setting.applyExternalSettings).not.toHaveBeenCalled(); expect(core.services.control.applySettings).not.toHaveBeenCalled(); }); }); diff --git a/src/apps/cli/package.json b/src/apps/cli/package.json index 0bd2999..4deaade 100644 --- a/src/apps/cli/package.json +++ b/src/apps/cli/package.json @@ -1,40 +1,40 @@ -{ - "name": "self-hosted-livesync-cli", - "private": true, - "version": "0.0.0", - "main": "dist/index.cjs", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview", - "cli": "node dist/index.cjs", - "buildRun": "npm run build && npm run cli --", - "build:docker": "docker build -f Dockerfile -t livesync-cli ../../..", - "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json", - "test:unit": "cd ../../.. && npx vitest run --config vitest.config.unit.ts src/apps/cli/main.unit.spec.ts src/apps/cli/commands/utils.unit.spec.ts src/apps/cli/commands/runCommand.unit.spec.ts src/apps/cli/commands/p2p.unit.spec.ts", - "test:e2e:two-vaults": "bash test/test-e2e-two-vaults-with-docker-linux.sh", - "test:e2e:two-vaults:common": "bash test/test-e2e-two-vaults-common.sh", - "test:e2e:two-vaults:matrix": "bash test/test-e2e-two-vaults-matrix.sh", - "test:e2e:push-pull": "bash test/test-push-pull-linux.sh", - "test:e2e:setup-put-cat": "bash test/test-setup-put-cat-linux.sh", - "test:e2e:sync-two-local": "bash test/test-sync-two-local-databases-linux.sh", - "test:e2e:p2p": "bash test/test-p2p-three-nodes-conflict-linux.sh", - "test:e2e:p2p-upload-download-repro": "bash test/test-p2p-upload-download-repro-linux.sh", - "test:e2e:p2p-host": "bash test/test-p2p-host-linux.sh", - "test:e2e:p2p-sync": "bash test/test-p2p-sync-linux.sh", - "test:e2e:mirror": "bash test/test-mirror-linux.sh", - "pretest:e2e:all": "npm run build", - "test:e2e:all": " export RUN_BUILD=0 && npm run test:e2e:setup-put-cat && npm run test:e2e:push-pull && npm run test:e2e:sync-two-local && npm run test:e2e:p2p && npm run test:e2e:mirror && npm run test:e2e:two-vaults && npm run test:e2e:p2p", - "pretest:e2e:docker:all": "npm run build:docker", - "test:e2e:docker:push-pull": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-push-pull-linux.sh", - "test:e2e:docker:setup-put-cat": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-setup-put-cat-linux.sh", - "test:e2e:docker:mirror": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-mirror-linux.sh", - "test:e2e:docker:sync-two-local": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-sync-two-local-databases-linux.sh", - "test:e2e:docker:p2p": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-p2p-three-nodes-conflict-linux.sh", - "test:e2e:docker:p2p-sync": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-p2p-sync-linux.sh", - "test:e2e:docker:all": "export RUN_BUILD=0 && npm run test:e2e:docker:setup-put-cat && npm run test:e2e:docker:push-pull && npm run test:e2e:docker:sync-two-local && npm run test:e2e:docker:mirror" - }, - "dependencies": {}, - "devDependencies": {} -} +{ + "name": "self-hosted-livesync-cli", + "private": true, + "version": "0.0.0", + "main": "dist/index.cjs", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "cli": "node dist/index.cjs", + "buildRun": "npm run build && npm run cli --", + "build:docker": "docker build -f Dockerfile -t livesync-cli ../../..", + "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json", + "test:unit": "cd ../../.. && npx vitest run --config vitest.config.unit.ts src/apps/cli/main.unit.spec.ts src/apps/cli/commands/utils.unit.spec.ts src/apps/cli/commands/runCommand.unit.spec.ts src/apps/cli/commands/p2p.unit.spec.ts", + "test:e2e:two-vaults": "bash test/test-e2e-two-vaults-with-docker-linux.sh", + "test:e2e:two-vaults:common": "bash test/test-e2e-two-vaults-common.sh", + "test:e2e:two-vaults:matrix": "bash test/test-e2e-two-vaults-matrix.sh", + "test:e2e:push-pull": "bash test/test-push-pull-linux.sh", + "test:e2e:setup-put-cat": "bash test/test-setup-put-cat-linux.sh", + "test:e2e:sync-two-local": "bash test/test-sync-two-local-databases-linux.sh", + "test:e2e:p2p": "bash test/test-p2p-three-nodes-conflict-linux.sh", + "test:e2e:p2p-upload-download-repro": "bash test/test-p2p-upload-download-repro-linux.sh", + "test:e2e:p2p-host": "bash test/test-p2p-host-linux.sh", + "test:e2e:p2p-sync": "bash test/test-p2p-sync-linux.sh", + "test:e2e:mirror": "bash test/test-mirror-linux.sh", + "pretest:e2e:all": "npm run build", + "test:e2e:all": " export RUN_BUILD=0 && npm run test:e2e:setup-put-cat && npm run test:e2e:push-pull && npm run test:e2e:sync-two-local && npm run test:e2e:p2p && npm run test:e2e:mirror && npm run test:e2e:two-vaults && npm run test:e2e:p2p", + "pretest:e2e:docker:all": "npm run build:docker", + "test:e2e:docker:push-pull": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-push-pull-linux.sh", + "test:e2e:docker:setup-put-cat": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-setup-put-cat-linux.sh", + "test:e2e:docker:mirror": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-mirror-linux.sh", + "test:e2e:docker:sync-two-local": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-sync-two-local-databases-linux.sh", + "test:e2e:docker:p2p": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-p2p-three-nodes-conflict-linux.sh", + "test:e2e:docker:p2p-sync": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-p2p-sync-linux.sh", + "test:e2e:docker:all": "export RUN_BUILD=0 && npm run test:e2e:docker:setup-put-cat && npm run test:e2e:docker:push-pull && npm run test:e2e:docker:sync-two-local && npm run test:e2e:docker:mirror" + }, + "dependencies": {}, + "devDependencies": {} +} diff --git a/src/apps/cli/runtime-package.json b/src/apps/cli/runtime-package.json index 12f7920..5791992 100644 --- a/src/apps/cli/runtime-package.json +++ b/src/apps/cli/runtime-package.json @@ -1,24 +1,24 @@ -{ - "name": "livesync-cli-runtime", - "private": true, - "version": "0.0.0", - "description": "Runtime dependencies for Self-hosted LiveSync CLI Docker image", - "dependencies": { - "commander": "^14.0.3", - "werift": "^0.22.9", - "pouchdb-adapter-http": "^9.0.0", - "pouchdb-adapter-idb": "^9.0.0", - "pouchdb-adapter-indexeddb": "^9.0.0", - "pouchdb-adapter-leveldb": "^9.0.0", - "pouchdb-adapter-memory": "^9.0.0", - "pouchdb-core": "^9.0.0", - "pouchdb-errors": "^9.0.0", - "pouchdb-find": "^9.0.0", - "pouchdb-mapreduce": "^9.0.0", - "pouchdb-merge": "^9.0.0", - "pouchdb-replication": "^9.0.0", - "pouchdb-utils": "^9.0.0", - "pouchdb-wrappers": "*", - "transform-pouch": "^2.0.0" - } -} +{ + "name": "livesync-cli-runtime", + "private": true, + "version": "0.0.0", + "description": "Runtime dependencies for Self-hosted LiveSync CLI Docker image", + "dependencies": { + "commander": "^14.0.3", + "werift": "^0.22.9", + "pouchdb-adapter-http": "^9.0.0", + "pouchdb-adapter-idb": "^9.0.0", + "pouchdb-adapter-indexeddb": "^9.0.0", + "pouchdb-adapter-leveldb": "^9.0.0", + "pouchdb-adapter-memory": "^9.0.0", + "pouchdb-core": "^9.0.0", + "pouchdb-errors": "^9.0.0", + "pouchdb-find": "^9.0.0", + "pouchdb-mapreduce": "^9.0.0", + "pouchdb-merge": "^9.0.0", + "pouchdb-replication": "^9.0.0", + "pouchdb-utils": "^9.0.0", + "pouchdb-wrappers": "*", + "transform-pouch": "^2.0.0" + } +} diff --git a/src/apps/webpeer/src/P2PReplicatorShim.ts b/src/apps/webpeer/src/P2PReplicatorShim.ts index fd461ef..45b7631 100644 --- a/src/apps/webpeer/src/P2PReplicatorShim.ts +++ b/src/apps/webpeer/src/P2PReplicatorShim.ts @@ -276,7 +276,7 @@ export class P2PReplicatorShim implements P2PReplicatorBase { } } } - await this.services.setting.applyPartial(remoteConfig, true); + await this.services.setting.applyExternalSettings(remoteConfig, true); if (yn !== DROP) { await this.plugin.core.services.appLifecycle.scheduleRestart(); } diff --git a/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts b/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts index 5ab5031..30073ad 100644 --- a/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts +++ b/src/features/P2PSync/P2PReplicator/P2PReplicatorPaneView.ts @@ -87,7 +87,7 @@ And you can also drop the local database to rebuild from the remote device.`, // this.plugin.settings = remoteConfig; // await this.plugin.saveSettings(); - await this.core.services.setting.applyPartial(remoteConfig); + await this.core.services.setting.applyExternalSettings(remoteConfig); if (yn === DROP) { await this.core.rebuilder.scheduleFetch(); } else { diff --git a/src/lib b/src/lib index d14de2d..cdd8693 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit d14de2d8fc5b712354d30772a2422b0599916883 +Subproject commit cdd8693498388e06b9cf36469374f5688d207f77 diff --git a/src/modules/features/ModuleObsidianSettingAsMarkdown.ts b/src/modules/features/ModuleObsidianSettingAsMarkdown.ts index 21f1008..154e43e 100644 --- a/src/modules/features/ModuleObsidianSettingAsMarkdown.ts +++ b/src/modules/features/ModuleObsidianSettingAsMarkdown.ts @@ -162,8 +162,8 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractModule { result == APPLY_AND_REBUILD || result == APPLY_AND_FETCH ) { - this.core.settings = settingToApply; - await this.services.setting.saveSettingData(); + await this.services.setting.applyExternalSettings(settingToApply, true); + this.services.setting.clearUsedPassphrase(); if (result == APPLY_ONLY) { this._log("Loaded settings have been applied!", LOG_LEVEL_NOTICE); return; diff --git a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts index e3e3883..8c648de 100644 --- a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts +++ b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts @@ -3,8 +3,10 @@ import { REMOTE_MINIO, REMOTE_P2P, DEFAULT_SETTINGS, + LOG_LEVEL_NOTICE, type ObsidianLiveSyncSettings, } from "../../../lib/src/common/types.ts"; +import { Menu } from "@/deps.ts"; import { $msg } from "../../../lib/src/common/i18n.ts"; import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts"; import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts"; @@ -24,6 +26,7 @@ import { SetupManager, UserMode } from "../SetupManager.ts"; import { OnDialogSettingsDefault, type AllSettings } from "./settingConstants.ts"; import { activateRemoteConfiguration } from "../../../lib/src/serviceFeatures/remoteConfig.ts"; import { ConnectionStringParser } from "../../../lib/src/common/ConnectionString.ts"; +import type { RemoteConfigurationResult } from "../../../lib/src/common/ConnectionString.ts"; import type { RemoteConfiguration } from "../../../lib/src/common/models/setting.type.ts"; import SetupRemote from "../SetupWizard/dialogs/SetupRemote.svelte"; import SetupRemoteCouchDB from "../SetupWizard/dialogs/SetupRemoteCouchDB.svelte"; @@ -67,6 +70,29 @@ function serializeRemoteConfiguration(settings: ObsidianLiveSyncSettings): strin return ConnectionStringParser.serialize({ type: "couchdb", settings }); } +function setEmojiButton(button: any, emoji: string, tooltip: string) { + button.setButtonText(emoji); + button.setTooltip(tooltip, { delay: 10, placement: "top" }); + // button.buttonEl.addClass("clickable-icon"); + button.buttonEl.addClass("mod-muted"); + return button; +} + +function suggestRemoteConfigurationName(parsed: RemoteConfigurationResult): string { + if (parsed.type === "couchdb") { + try { + const url = new URL(parsed.settings.couchDB_URI); + return `CouchDB ${url.host}`; + } catch { + return "Imported CouchDB"; + } + } + if (parsed.type === "s3") { + return `S3 ${parsed.settings.bucket || parsed.settings.endpoint}`; + } + return `P2P ${parsed.settings.P2P_roomID || "Remote"}`; +} + export function paneRemoteConfig( this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement, @@ -237,11 +263,60 @@ export function paneRemoteConfig( await persistRemoteConfigurations(this.editingSettings.activeConfigurationId === id); refreshList(); }; + const importRemoteConfiguration = async () => { + const importedURI = await this.services.UI.confirm.askString( + "Import connection", + "Paste a connection string", + "" + ); + if (importedURI === false) { + return; + } + + const trimmedURI = importedURI.trim(); + if (trimmedURI === "") { + return; + } + + let parsed: RemoteConfigurationResult; + try { + parsed = ConnectionStringParser.parse(trimmedURI); + } catch (ex) { + this.services.API.addLog(`Failed to import remote configuration: ${ex}`, LOG_LEVEL_NOTICE); + return; + } + + const defaultName = suggestRemoteConfigurationName(parsed); + const name = await this.services.UI.confirm.askString("Remote name", "Display name", defaultName); + if (name === false) { + return; + } + + const id = createRemoteConfigurationId(); + const configs = cloneRemoteConfigurations(this.editingSettings.remoteConfigurations); + configs[id] = { + id, + name: name.trim() || defaultName, + uri: ConnectionStringParser.serialize(parsed), + isEncrypted: false, + }; + this.editingSettings.remoteConfigurations = configs; + if (!this.editingSettings.activeConfigurationId) { + this.editingSettings.activeConfigurationId = id; + } + await persistRemoteConfigurations(this.editingSettings.activeConfigurationId === id); + refreshList(); + }; actions.addButton((button) => - button.setButtonText("Add New Connection").onClick(async () => { + setEmojiButton(button, "➕", "Add new connection").onClick(async () => { await addRemoteConfiguration(); }) ); + actions.addButton((button) => + setEmojiButton(button, "📥", "Import connection").onClick(async () => { + await importRemoteConfiguration(); + }) + ); const refreshList = () => { listContainer.empty(); const configs = this.editingSettings.remoteConfigurations || {}; @@ -256,7 +331,7 @@ export function paneRemoteConfig( } row.addButton((btn) => - btn.setButtonText("Configure").onClick(async () => { + setEmojiButton(btn, "🔧", "Configure").onClick(async () => { const parsed = ConnectionStringParser.parse(config.uri); const workSettings = createBaseRemoteSettings(); if (parsed.type === "couchdb") { @@ -284,83 +359,10 @@ export function paneRemoteConfig( refreshList(); }) ); - row.addButton((btn) => - btn.setButtonText("Rename").onClick(async () => { - const nextName = await this.services.UI.confirm.askString( - "Remote name", - "Display name", - config.name - ); - if (nextName === false) { - return; - } - const nextConfigs = cloneRemoteConfigurations(this.editingSettings.remoteConfigurations); - nextConfigs[config.id] = { - ...config, - name: nextName.trim() || config.name, - }; - this.editingSettings.remoteConfigurations = nextConfigs; - await persistRemoteConfigurations(); - refreshList(); - }) - ); - row.addButton((btn) => - btn.setButtonText("Duplicate").onClick(async () => { - const nextName = await this.services.UI.confirm.askString( - "Duplicate remote", - "Display name", - `${config.name} (Copy)` - ); - if (nextName === false) { - return; - } - - const nextId = createRemoteConfigurationId(); - const nextConfigs = cloneRemoteConfigurations(this.editingSettings.remoteConfigurations); - nextConfigs[nextId] = { - ...config, - id: nextId, - name: nextName.trim() || `${config.name} (Copy)`, - }; - this.editingSettings.remoteConfigurations = nextConfigs; - await persistRemoteConfigurations(); - refreshList(); - }) - ); row.addButton((btn) => btn - .setButtonText("Delete") - .setWarning() - .onClick(async () => { - const confirmed = await this.services.UI.confirm.askYesNoDialog( - `Delete remote configuration '${config.name}'?`, - { title: "Delete Remote Configuration", defaultOption: "No" } - ); - if (confirmed !== "yes") { - return; - } - - const nextConfigs = cloneRemoteConfigurations( - this.editingSettings.remoteConfigurations - ); - delete nextConfigs[config.id]; - this.editingSettings.remoteConfigurations = nextConfigs; - - let syncActiveRemote = false; - if (this.editingSettings.activeConfigurationId === config.id) { - const nextActiveId = Object.keys(nextConfigs)[0] || ""; - this.editingSettings.activeConfigurationId = nextActiveId; - syncActiveRemote = nextActiveId !== ""; - } - - await persistRemoteConfigurations(syncActiveRemote); - refreshList(); - }) - ); - - row.addButton((btn) => - btn - .setButtonText("Activate") + .setButtonText("✅") + .setTooltip("Activate", { delay: 10, placement: "top" }) .setDisabled(config.id === this.editingSettings.activeConfigurationId) .onClick(async () => { this.editingSettings.activeConfigurationId = config.id; @@ -368,6 +370,97 @@ export function paneRemoteConfig( refreshList(); }) ); + + row.addButton((btn) => + setEmojiButton(btn, "…", "More actions").onClick(() => { + const menu = new Menu() + .addItem((item) => { + item.setTitle("🪪 Rename").onClick(async () => { + const nextName = await this.services.UI.confirm.askString( + "Remote name", + "Display name", + config.name + ); + if (nextName === false) { + return; + } + const nextConfigs = cloneRemoteConfigurations( + this.editingSettings.remoteConfigurations + ); + nextConfigs[config.id] = { + ...config, + name: nextName.trim() || config.name, + }; + this.editingSettings.remoteConfigurations = nextConfigs; + await persistRemoteConfigurations(); + refreshList(); + }); + }) + .addItem((item) => { + item.setTitle("📤 Export").onClick(async () => { + await this.services.UI.promptCopyToClipboard( + `Remote configuration: ${config.name}`, + config.uri + ); + }); + }) + .addItem((item) => { + item.setTitle("🧬 Duplicate").onClick(async () => { + const nextName = await this.services.UI.confirm.askString( + "Duplicate remote", + "Display name", + `${config.name} (Copy)` + ); + if (nextName === false) { + return; + } + + const nextId = createRemoteConfigurationId(); + const nextConfigs = cloneRemoteConfigurations( + this.editingSettings.remoteConfigurations + ); + nextConfigs[nextId] = { + ...config, + id: nextId, + name: nextName.trim() || `${config.name} (Copy)`, + }; + this.editingSettings.remoteConfigurations = nextConfigs; + await persistRemoteConfigurations(); + refreshList(); + }); + }) + .addSeparator() + .addItem((item) => { + item.setTitle("🗑 Delete").onClick(async () => { + const confirmed = await this.services.UI.confirm.askYesNoDialog( + `Delete remote configuration '${config.name}'?`, + { title: "Delete Remote Configuration", defaultOption: "No" } + ); + if (confirmed !== "yes") { + return; + } + + const nextConfigs = cloneRemoteConfigurations( + this.editingSettings.remoteConfigurations + ); + delete nextConfigs[config.id]; + this.editingSettings.remoteConfigurations = nextConfigs; + + let syncActiveRemote = false; + if (this.editingSettings.activeConfigurationId === config.id) { + const nextActiveId = Object.keys(nextConfigs)[0] || ""; + this.editingSettings.activeConfigurationId = nextActiveId; + syncActiveRemote = nextActiveId !== ""; + } + + await persistRemoteConfigurations(syncActiveRemote); + refreshList(); + }); + }); + const rect = btn.buttonEl.getBoundingClientRect(); + menu.showAtPosition({ x: rect.left, y: rect.bottom }); + }) + ); } }; refreshList(); diff --git a/src/modules/features/SetupManager.ts b/src/modules/features/SetupManager.ts index 02eeffc..1bd40be 100644 --- a/src/modules/features/SetupManager.ts +++ b/src/modules/features/SetupManager.ts @@ -275,6 +275,10 @@ export class SetupManager extends AbstractModule { activate: boolean = true, extra: () => void = () => {} ): Promise { + newConf = await this.services.setting.adjustSettings({ + ...this.settings, + ...newConf, + }); let userMode = _userMode; if (userMode === UserMode.Unknown) { if (isObjectDifferent(this.settings, newConf, true) === false) { @@ -368,13 +372,8 @@ export class SetupManager extends AbstractModule { * @returns Promise that resolves to true if settings applied successfully, false otherwise */ async applySetting(newConf: ObsidianLiveSyncSettings, userMode: UserMode) { - const newSetting = { - ...this.core.settings, - ...newConf, - }; - this.core.settings = newSetting; this.services.setting.clearUsedPassphrase(); - await this.services.setting.saveSettingData(); + await this.services.setting.applyExternalSettings(newConf, true); return true; } } diff --git a/src/modules/features/SetupManager.unit.spec.ts b/src/modules/features/SetupManager.unit.spec.ts new file mode 100644 index 0000000..ef94c4d --- /dev/null +++ b/src/modules/features/SetupManager.unit.spec.ts @@ -0,0 +1,157 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { DEFAULT_SETTINGS, REMOTE_COUCHDB, type ObsidianLiveSyncSettings } from "../../lib/src/common/types"; +import { SettingService } from "../../lib/src/services/base/SettingService"; +import { ServiceContext } from "../../lib/src/services/base/ServiceBase"; + +vi.mock("./SetupWizard/dialogs/Intro.svelte", () => ({ default: {} })); +vi.mock("./SetupWizard/dialogs/SelectMethodNewUser.svelte", () => ({ default: {} })); +vi.mock("./SetupWizard/dialogs/SelectMethodExisting.svelte", () => ({ default: {} })); +vi.mock("./SetupWizard/dialogs/ScanQRCode.svelte", () => ({ default: {} })); +vi.mock("./SetupWizard/dialogs/UseSetupURI.svelte", () => ({ default: {} })); +vi.mock("./SetupWizard/dialogs/OutroNewUser.svelte", () => ({ default: {} })); +vi.mock("./SetupWizard/dialogs/OutroExistingUser.svelte", () => ({ default: {} })); +vi.mock("./SetupWizard/dialogs/OutroAskUserMode.svelte", () => ({ default: {} })); +vi.mock("./SetupWizard/dialogs/SetupRemote.svelte", () => ({ default: {} })); +vi.mock("./SetupWizard/dialogs/SetupRemoteCouchDB.svelte", () => ({ default: {} })); +vi.mock("./SetupWizard/dialogs/SetupRemoteBucket.svelte", () => ({ default: {} })); +vi.mock("./SetupWizard/dialogs/SetupRemoteP2P.svelte", () => ({ default: {} })); +vi.mock("./SetupWizard/dialogs/SetupRemoteE2EE.svelte", () => ({ default: {} })); + +vi.mock("../../lib/src/API/processSetting.ts", () => ({ + decodeSettingsFromQRCodeData: vi.fn(), +})); + +import { decodeSettingsFromQRCodeData } from "../../lib/src/API/processSetting.ts"; +import { SetupManager, UserMode } from "./SetupManager"; + +class TestSettingService extends SettingService { + protected setItem(_key: string, _value: string): void {} + protected getItem(_key: string): string { + return ""; + } + protected deleteItem(_key: string): void {} + protected saveData(_setting: ObsidianLiveSyncSettings): Promise { + return Promise.resolve(); + } + protected loadData(): Promise { + return Promise.resolve(undefined); + } +} + +function createLegacyRemoteSetting(): ObsidianLiveSyncSettings { + return { + ...DEFAULT_SETTINGS, + remoteConfigurations: {}, + activeConfigurationId: "", + remoteType: REMOTE_COUCHDB, + couchDB_URI: "http://localhost:5984", + couchDB_USER: "user", + couchDB_PASSWORD: "password", + couchDB_DBNAME: "vault", + }; +} + +function createSetupManager() { + const setting = new TestSettingService(new ServiceContext(), { + APIService: { + getSystemVaultName: vi.fn(() => "vault"), + getAppID: vi.fn(() => "app"), + confirm: { + askString: vi.fn(() => Promise.resolve("")), + }, + addLog: vi.fn(), + addCommand: vi.fn(), + registerWindow: vi.fn(), + addRibbonIcon: vi.fn(), + registerProtocolHandler: vi.fn(), + } as any, + }); + setting.settings = { + ...DEFAULT_SETTINGS, + remoteConfigurations: {}, + activeConfigurationId: "", + }; + vi.spyOn(setting, "saveSettingData").mockResolvedValue(); + + const dialogManager = { + openWithExplicitCancel: vi.fn(), + open: vi.fn(), + }; + const services = { + API: { + addLog: vi.fn(), + addCommand: vi.fn(), + registerWindow: vi.fn(), + addRibbonIcon: vi.fn(), + registerProtocolHandler: vi.fn(), + }, + UI: { + dialogManager, + }, + setting, + } as any; + const core: any = { + _services: services, + rebuilder: { + scheduleRebuild: vi.fn(() => Promise.resolve()), + scheduleFetch: vi.fn(() => Promise.resolve()), + }, + }; + Object.defineProperty(core, "services", { + get() { + return services; + }, + }); + Object.defineProperty(core, "settings", { + get() { + return setting.settings; + }, + set(value: ObsidianLiveSyncSettings) { + setting.settings = value; + }, + }); + + return { + manager: new SetupManager(core), + setting, + dialogManager, + core, + }; +} + +describe("SetupManager", () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.restoreAllMocks(); + }); + + it("onUseSetupURI should normalise imported legacy remote settings before applying", async () => { + const { manager, setting, dialogManager } = createSetupManager(); + dialogManager.openWithExplicitCancel + .mockResolvedValueOnce(createLegacyRemoteSetting()) + .mockResolvedValueOnce("compatible-existing-user"); + + const result = await manager.onUseSetupURI(UserMode.Unknown, "mock-config://settings"); + + expect(result).toBe(true); + expect(setting.currentSettings().remoteConfigurations["legacy-couchdb"]?.uri).toContain( + "sls+http://user:password@localhost:5984" + ); + expect(setting.currentSettings().activeConfigurationId).toBe("legacy-couchdb"); + }); + + it("decodeQR should normalise imported legacy remote settings before applying", async () => { + const { manager, setting, dialogManager } = createSetupManager(); + vi.mocked(decodeSettingsFromQRCodeData).mockReturnValue(createLegacyRemoteSetting()); + dialogManager.openWithExplicitCancel.mockResolvedValueOnce("compatible-existing-user"); + + const result = await manager.decodeQR("qr-data"); + + expect(result).toBe(true); + expect(decodeSettingsFromQRCodeData).toHaveBeenCalledWith("qr-data"); + expect(setting.currentSettings().remoteConfigurations["legacy-couchdb"]?.uri).toContain( + "sls+http://user:password@localhost:5984" + ); + expect(setting.currentSettings().activeConfigurationId).toBe("legacy-couchdb"); + }); +}); diff --git a/src/serviceFeatures/redFlag.ts b/src/serviceFeatures/redFlag.ts index e4f2b0e..90e052e 100644 --- a/src/serviceFeatures/redFlag.ts +++ b/src/serviceFeatures/redFlag.ts @@ -176,7 +176,7 @@ export async function adjustSettingToRemote( ...config, ...Object.fromEntries(differentItems), } satisfies ObsidianLiveSyncSettings; - await host.services.setting.applyPartial(config, true); + await host.services.setting.applyExternalSettings(config, true); log("Remote configuration applied.", LOG_LEVEL_NOTICE); canProceed = true; const updatedConfig = host.services.setting.currentSettings(); diff --git a/src/serviceFeatures/redFlag.unit.spec.ts b/src/serviceFeatures/redFlag.unit.spec.ts index 65fcef4..2643642 100644 --- a/src/serviceFeatures/redFlag.unit.spec.ts +++ b/src/serviceFeatures/redFlag.unit.spec.ts @@ -49,6 +49,10 @@ const createSettingServiceMock = () => { return { settings, currentSettings: vi.fn(() => settings), + applyExternalSettings: vi.fn((partial: any, _feedback?: boolean) => { + Object.assign(settings, partial); + return Promise.resolve(); + }), applyPartial: vi.fn((partial: any, _feedback?: boolean) => { Object.assign(settings, partial); return Promise.resolve(); @@ -552,7 +556,7 @@ describe("Red Flag Feature", () => { await adjustSettingToRemote(host as any, createLoggerMock(), config); expect(host.mocks.ui.confirm.askSelectStringDialogue).toHaveBeenCalled(); - expect(host.mocks.setting.applyPartial).toHaveBeenCalled(); + expect(host.mocks.setting.applyExternalSettings).toHaveBeenCalled(); } ); const mismatchAcceptedKeys = Object.keys(TweakValuesRecommendedTemplate).filter( @@ -579,7 +583,7 @@ describe("Red Flag Feature", () => { await adjustSettingToRemote(host as any, createLoggerMock(), config); - expect(host.mocks.setting.applyPartial).toHaveBeenCalled(); + expect(host.mocks.setting.applyExternalSettings).toHaveBeenCalled(); expect(host.mocks.ui.confirm.askSelectStringDialogue).not.toHaveBeenCalled(); } ); From d709bcc1d03f2b371c173bd97caa549f94f9ae31 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sun, 5 Apr 2026 16:24:34 +0900 Subject: [PATCH 127/339] =?UTF-8?q?Add=20encryption=E3=80=80for=20connecti?= =?UTF-8?q?on=20management?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib b/src/lib index cdd8693..d802c84 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit cdd8693498388e06b9cf36469374f5688d207f77 +Subproject commit d802c84a34d16102cf2470568b16241dc346b733 From bc22d61a3adc22dd1d47814e378a2d60217a7e18 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sun, 5 Apr 2026 17:43:29 +0900 Subject: [PATCH 128/339] Fixed: Now error messages are kept hidden if the show status inside the editor is disabled. --- src/lib | 2 +- src/modules/features/ModuleLog.ts | 51 ++++++++++++------- .../features/SettingDialogue/PaneGeneral.ts | 3 ++ 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/lib b/src/lib index d802c84..37b8e28 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit d802c84a34d16102cf2470568b16241dc346b733 +Subproject commit 37b8e2813edbd09611d340e4810a0e31fa537521 diff --git a/src/modules/features/ModuleLog.ts b/src/modules/features/ModuleLog.ts index 467de2b..16de2d9 100644 --- a/src/modules/features/ModuleLog.ts +++ b/src/modules/features/ModuleLog.ts @@ -277,27 +277,36 @@ export class ModuleLog extends AbstractObsidianModule { } async updateMessageArea() { - if (this.messageArea) { - const messageLines = []; - const fileStatus = this.activeFileStatus.value; - if (fileStatus && !this.settings.hideFileWarningNotice) messageLines.push(fileStatus); - const messages = (await this.services.appLifecycle.getUnresolvedMessages()).flat().filter((e) => e); - const stringMessages = messages.filter((m): m is string => typeof m === "string"); // for 'startsWith' - const networkMessages = stringMessages.filter((m) => m.startsWith(MARK_LOG_NETWORK_ERROR)); - const otherMessages = stringMessages.filter((m) => !m.startsWith(MARK_LOG_NETWORK_ERROR)); + if (!this.messageArea) return; - messageLines.push(...otherMessages); - - if ( - this.settings.networkWarningStyle !== NetworkWarningStyles.ICON && - this.settings.networkWarningStyle !== NetworkWarningStyles.HIDDEN - ) { - messageLines.push(...networkMessages); - } else if (this.settings.networkWarningStyle === NetworkWarningStyles.ICON) { - if (networkMessages.length > 0) messageLines.push("🔗❌"); - } - this.messageArea.innerText = messageLines.map((e) => `⚠️ ${e}`).join("\n"); + const showStatusOnEditor = this.settings?.showStatusOnEditor ?? false; + if (this.statusDiv) { + this.statusDiv.style.display = showStatusOnEditor ? "" : "none"; } + if (!showStatusOnEditor) { + this.messageArea.innerText = ""; + return; + } + + const messageLines = []; + const fileStatus = this.activeFileStatus.value; + if (fileStatus && !this.settings.hideFileWarningNotice) messageLines.push(fileStatus); + const messages = (await this.services.appLifecycle.getUnresolvedMessages()).flat().filter((e) => e); + const stringMessages = messages.filter((m): m is string => typeof m === "string"); // for 'startsWith' + const networkMessages = stringMessages.filter((m) => m.startsWith(MARK_LOG_NETWORK_ERROR)); + const otherMessages = stringMessages.filter((m) => !m.startsWith(MARK_LOG_NETWORK_ERROR)); + + messageLines.push(...otherMessages); + + if ( + this.settings.networkWarningStyle !== NetworkWarningStyles.ICON && + this.settings.networkWarningStyle !== NetworkWarningStyles.HIDDEN + ) { + messageLines.push(...networkMessages); + } else if (this.settings.networkWarningStyle === NetworkWarningStyles.ICON) { + if (networkMessages.length > 0) messageLines.push("🔗❌"); + } + this.messageArea.innerText = messageLines.map((e) => `⚠️ ${e}`).join("\n"); } onActiveLeafChange() { @@ -326,6 +335,9 @@ export class ModuleLog extends AbstractObsidianModule { } this.statusBar?.setText(newMsg.split("\n")[0]); + if (this.statusDiv) { + this.statusDiv.style.display = this.settings?.showStatusOnEditor ? "" : "none"; + } if (this.settings?.showStatusOnEditor && this.statusDiv) { if (this.settings.showLongerLogInsideEditor) { const now = new Date().getTime(); @@ -402,6 +414,7 @@ export class ModuleLog extends AbstractObsidianModule { this.messageArea = this.statusDiv.createDiv({ cls: "livesync-status-messagearea" }); this.logMessage = this.statusDiv.createDiv({ cls: "livesync-status-logmessage" }); this.logHistory = this.statusDiv.createDiv({ cls: "livesync-status-loghistory" }); + this.statusDiv.style.display = this.settings?.showStatusOnEditor ? "" : "none"; eventHub.onEvent(EVENT_LAYOUT_READY, () => this.adjustStatusDivPosition()); if (this.settings?.showStatusOnStatusbar) { this.statusBar = this.services.API.addStatusBarItem(); diff --git a/src/modules/features/SettingDialogue/PaneGeneral.ts b/src/modules/features/SettingDialogue/PaneGeneral.ts index 213d27a..fc9cf33 100644 --- a/src/modules/features/SettingDialogue/PaneGeneral.ts +++ b/src/modules/features/SettingDialogue/PaneGeneral.ts @@ -21,6 +21,9 @@ export function paneGeneral( }); this.addOnSaved("displayLanguage", () => this.display()); new Setting(paneEl).autoWireToggle("showStatusOnEditor"); + this.addOnSaved("showStatusOnEditor", () => { + eventHub.emitEvent(EVENT_ON_UNRESOLVED_ERROR); + }); new Setting(paneEl).autoWireToggle("showOnlyIconsOnEditor", { onUpdate: visibleOnly(() => this.isConfiguredAs("showStatusOnEditor", true)), }); From 0dbf4cface0b21dc53c80c83324424e07442c1d3 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sun, 5 Apr 2026 17:47:45 +0900 Subject: [PATCH 129/339] bump --- manifest.json | 2 +- package-lock.json | 527 ++++++++++++++++++++++------------------------ package.json | 2 +- updates.md | 17 +- 4 files changed, 267 insertions(+), 281 deletions(-) diff --git a/manifest.json b/manifest.json index 7c0aee9..560d7c8 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.56", + "version": "0.25.56+patched2", "minAppVersion": "0.9.12", "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 c552fe9..ef8ebca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.56", + "version": "0.25.56+patched2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.56", + "version": "0.25.56+patched2", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", @@ -1717,9 +1717,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", - "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", "cpu": [ "arm64" ], @@ -1924,9 +1924,9 @@ } }, "node_modules/@eslint/core": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz", - "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==", + "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": { @@ -3054,19 +3054,6 @@ "bare-path": "^3.0.0" } }, - "node_modules/@puppeteer/browsers/node_modules/tar-stream": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", - "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "b4a": "^1.6.4", - "bare-fs": "^4.5.5", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz", @@ -4433,9 +4420,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.12.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", - "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", + "version": "24.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", "dev": true, "license": "MIT", "dependencies": { @@ -5290,9 +5277,9 @@ } }, "node_modules/@wdio/repl/node_modules/@types/node": { - "version": "20.19.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", - "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", + "version": "20.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", "dev": true, "license": "MIT", "dependencies": { @@ -5320,9 +5307,9 @@ } }, "node_modules/@wdio/types/node_modules/@types/node": { - "version": "20.19.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", - "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", + "version": "20.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", "dev": true, "license": "MIT", "dependencies": { @@ -5691,19 +5678,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/archiver/node_modules/tar-stream": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", - "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "b4a": "^1.6.4", - "bare-fs": "^4.5.5", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -7054,9 +7028,9 @@ } }, "node_modules/dotenv": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", - "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.0.tgz", + "integrity": "sha512-kCKF62fwtzwYm0IGBNjRUjtJgMfGapII+FslMHIjMR5KTnwEmBmWLDRSnc3XSNP8bNy34tekgQyDT0hr7pERRQ==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -10237,9 +10211,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash.clonedeep": { @@ -10456,12 +10430,12 @@ } }, "node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -13201,6 +13175,19 @@ } } }, + "node_modules/tar-stream": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", + "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/teex": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", @@ -13471,9 +13458,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", - "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", "cpu": [ "ppc64" ], @@ -13488,9 +13475,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/android-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", - "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", "cpu": [ "arm" ], @@ -13505,9 +13492,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/android-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", - "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", "cpu": [ "arm64" ], @@ -13522,9 +13509,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/android-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", - "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", "cpu": [ "x64" ], @@ -13539,9 +13526,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", - "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", "cpu": [ "arm64" ], @@ -13556,9 +13543,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/darwin-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", - "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", "cpu": [ "x64" ], @@ -13573,9 +13560,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", - "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", "cpu": [ "arm64" ], @@ -13590,9 +13577,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", - "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", "cpu": [ "x64" ], @@ -13607,9 +13594,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", - "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", "cpu": [ "arm" ], @@ -13624,9 +13611,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", - "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", "cpu": [ "arm64" ], @@ -13641,9 +13628,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", - "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", "cpu": [ "ia32" ], @@ -13658,9 +13645,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-loong64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", - "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", "cpu": [ "loong64" ], @@ -13675,9 +13662,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", - "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", "cpu": [ "mips64el" ], @@ -13692,9 +13679,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", - "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", "cpu": [ "ppc64" ], @@ -13709,9 +13696,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", - "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", "cpu": [ "riscv64" ], @@ -13726,9 +13713,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-s390x": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", - "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", "cpu": [ "s390x" ], @@ -13743,9 +13730,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", - "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", "cpu": [ "x64" ], @@ -13760,9 +13747,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", - "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", "cpu": [ "arm64" ], @@ -13777,9 +13764,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", - "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", "cpu": [ "x64" ], @@ -13794,9 +13781,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", - "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", "cpu": [ "arm64" ], @@ -13811,9 +13798,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", - "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", "cpu": [ "x64" ], @@ -13828,9 +13815,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/sunos-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", - "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", "cpu": [ "x64" ], @@ -13845,9 +13832,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/win32-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", - "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", "cpu": [ "arm64" ], @@ -13862,9 +13849,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/win32-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", - "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", "cpu": [ "ia32" ], @@ -13879,9 +13866,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/win32-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", - "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", "cpu": [ "x64" ], @@ -13896,9 +13883,9 @@ } }, "node_modules/tsx/node_modules/esbuild": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", - "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -13909,32 +13896,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.4", - "@esbuild/android-arm": "0.27.4", - "@esbuild/android-arm64": "0.27.4", - "@esbuild/android-x64": "0.27.4", - "@esbuild/darwin-arm64": "0.27.4", - "@esbuild/darwin-x64": "0.27.4", - "@esbuild/freebsd-arm64": "0.27.4", - "@esbuild/freebsd-x64": "0.27.4", - "@esbuild/linux-arm": "0.27.4", - "@esbuild/linux-arm64": "0.27.4", - "@esbuild/linux-ia32": "0.27.4", - "@esbuild/linux-loong64": "0.27.4", - "@esbuild/linux-mips64el": "0.27.4", - "@esbuild/linux-ppc64": "0.27.4", - "@esbuild/linux-riscv64": "0.27.4", - "@esbuild/linux-s390x": "0.27.4", - "@esbuild/linux-x64": "0.27.4", - "@esbuild/netbsd-arm64": "0.27.4", - "@esbuild/netbsd-x64": "0.27.4", - "@esbuild/openbsd-arm64": "0.27.4", - "@esbuild/openbsd-x64": "0.27.4", - "@esbuild/openharmony-arm64": "0.27.4", - "@esbuild/sunos-x64": "0.27.4", - "@esbuild/win32-arm64": "0.27.4", - "@esbuild/win32-ia32": "0.27.4", - "@esbuild/win32-x64": "0.27.4" + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" } }, "node_modules/tsx/node_modules/fsevents": { @@ -14121,9 +14108,9 @@ } }, "node_modules/undici": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz", - "integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz", + "integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==", "dev": true, "license": "MIT", "engines": { @@ -14368,9 +14355,9 @@ } }, "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", - "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", "cpu": [ "ppc64" ], @@ -14385,9 +14372,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", - "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", "cpu": [ "arm" ], @@ -14402,9 +14389,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", - "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", "cpu": [ "arm64" ], @@ -14419,9 +14406,9 @@ } }, "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", - "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", "cpu": [ "x64" ], @@ -14436,9 +14423,9 @@ } }, "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", - "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", "cpu": [ "arm64" ], @@ -14453,9 +14440,9 @@ } }, "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", - "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", "cpu": [ "x64" ], @@ -14470,9 +14457,9 @@ } }, "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", - "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", "cpu": [ "arm64" ], @@ -14487,9 +14474,9 @@ } }, "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", - "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", "cpu": [ "x64" ], @@ -14504,9 +14491,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", - "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", "cpu": [ "arm" ], @@ -14521,9 +14508,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", - "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", "cpu": [ "arm64" ], @@ -14538,9 +14525,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", - "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", "cpu": [ "ia32" ], @@ -14555,9 +14542,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", - "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", "cpu": [ "loong64" ], @@ -14572,9 +14559,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", - "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", "cpu": [ "mips64el" ], @@ -14589,9 +14576,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", - "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", "cpu": [ "ppc64" ], @@ -14606,9 +14593,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", - "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", "cpu": [ "riscv64" ], @@ -14623,9 +14610,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", - "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", "cpu": [ "s390x" ], @@ -14640,9 +14627,9 @@ } }, "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", - "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", "cpu": [ "x64" ], @@ -14657,9 +14644,9 @@ } }, "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", - "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", "cpu": [ "arm64" ], @@ -14674,9 +14661,9 @@ } }, "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", - "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", "cpu": [ "x64" ], @@ -14691,9 +14678,9 @@ } }, "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", - "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", "cpu": [ "arm64" ], @@ -14708,9 +14695,9 @@ } }, "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", - "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", "cpu": [ "x64" ], @@ -14725,9 +14712,9 @@ } }, "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", - "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", "cpu": [ "x64" ], @@ -14742,9 +14729,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", - "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", "cpu": [ "arm64" ], @@ -14759,9 +14746,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", - "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", "cpu": [ "ia32" ], @@ -14776,9 +14763,9 @@ } }, "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", - "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", "cpu": [ "x64" ], @@ -14793,9 +14780,9 @@ } }, "node_modules/vite/node_modules/esbuild": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", - "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -14806,32 +14793,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.4", - "@esbuild/android-arm": "0.27.4", - "@esbuild/android-arm64": "0.27.4", - "@esbuild/android-x64": "0.27.4", - "@esbuild/darwin-arm64": "0.27.4", - "@esbuild/darwin-x64": "0.27.4", - "@esbuild/freebsd-arm64": "0.27.4", - "@esbuild/freebsd-x64": "0.27.4", - "@esbuild/linux-arm": "0.27.4", - "@esbuild/linux-arm64": "0.27.4", - "@esbuild/linux-ia32": "0.27.4", - "@esbuild/linux-loong64": "0.27.4", - "@esbuild/linux-mips64el": "0.27.4", - "@esbuild/linux-ppc64": "0.27.4", - "@esbuild/linux-riscv64": "0.27.4", - "@esbuild/linux-s390x": "0.27.4", - "@esbuild/linux-x64": "0.27.4", - "@esbuild/netbsd-arm64": "0.27.4", - "@esbuild/netbsd-x64": "0.27.4", - "@esbuild/openbsd-arm64": "0.27.4", - "@esbuild/openbsd-x64": "0.27.4", - "@esbuild/openharmony-arm64": "0.27.4", - "@esbuild/sunos-x64": "0.27.4", - "@esbuild/win32-arm64": "0.27.4", - "@esbuild/win32-ia32": "0.27.4", - "@esbuild/win32-x64": "0.27.4" + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" } }, "node_modules/vite/node_modules/fdir": { @@ -15069,9 +15056,9 @@ } }, "node_modules/webdriver/node_modules/@types/node": { - "version": "20.19.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", - "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", + "version": "20.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", "dev": true, "license": "MIT", "dependencies": { @@ -15141,9 +15128,9 @@ } }, "node_modules/webdriverio/node_modules/@types/node": { - "version": "20.19.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", - "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", + "version": "20.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index fc2d258..c2d31bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.56", + "version": "0.25.56+patched2", "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 df9579f..2fcd417 100644 --- a/updates.md +++ b/updates.md @@ -3,27 +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. -## Unreleased 2 +## 0.25.56+patched1 -3rd April, 2026 +5th April, 2026 -As this commit is a bit of a fragile matter, I shall add a note here. +Beta release tagging is now changed to +patched1, +patched2, and so on. -You know that untagged updates shall not be tested well. please be careful to use your own build. In most cases, I check that the warnings have disappeared, that the code compiles successfully without any warnings, and that it runs on the desktop. +### Translations + +- Russian translation has been added! Thank you so much for the contribution! ### Fixed -- No unexpected error (about a replicator) during early stage of initialisation. +- No unexpected error (about a replicator) during the early stage of initialisation. +- Now error messages are kept hidden if the show status inside the editor is disabled. ### New features - Now we can configure multiple Remote Databases of the same type, e.g, multiple CouchDBs or S3 remotes. - We can switch between multiple Remote Databases in the settings dialogue. -## Unreleased - -2nd April, 2026 - ### CLI #### Fixed From 3e03d1dbd5ff749188db6bac7209e11cb72e6fcc Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sun, 5 Apr 2026 17:48:00 +0900 Subject: [PATCH 130/339] bump --- updates.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/updates.md b/updates.md index 2fcd417..24c78e5 100644 --- a/updates.md +++ b/updates.md @@ -3,7 +3,7 @@ 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.56+patched1 +## 0.25.56+patched2 5th April, 2026 From 8c4e62e7c1a0bb6c2207607d83d289d4d4b87465 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sun, 5 Apr 2026 18:20:56 +0900 Subject: [PATCH 131/339] ### Fixed - Now surely remote configurations are editable in the settings dialogue. - We can fetch remote settings from the remote and apply them to the local settings for each remote configuration entry. - No longer layout breaking occurs when the description of a remote configuration entry is too long. --- src/lib | 2 +- .../SettingDialogue/PaneRemoteConfig.ts | 43 ++++++++++++++++++- styles.css | 6 +++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/lib b/src/lib index 37b8e28..963a21f 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 37b8e2813edbd09611d340e4810a0e31fa537521 +Subproject commit 963a21f1d20f48d0336edbea92da41629050dc7a diff --git a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts index 8c648de..7684479 100644 --- a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts +++ b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts @@ -332,7 +332,16 @@ export function paneRemoteConfig( row.addButton((btn) => setEmojiButton(btn, "🔧", "Configure").onClick(async () => { - const parsed = ConnectionStringParser.parse(config.uri); + let parsed: RemoteConfigurationResult; + try { + parsed = ConnectionStringParser.parse(config.uri); + } catch (ex) { + this.services.API.addLog( + `Failed to parse remote configuration '${config.id}' for editing: ${ex}`, + LOG_LEVEL_NOTICE + ); + return; + } const workSettings = createBaseRemoteSettings(); if (parsed.type === "couchdb") { workSettings.remoteType = REMOTE_COUCHDB; @@ -430,6 +439,38 @@ export function paneRemoteConfig( }); }) .addSeparator() + .addItem((item) => { + item.setTitle("📡 Fetch remote settings").onClick(async () => { + let parsed: RemoteConfigurationResult; + try { + parsed = ConnectionStringParser.parse(config.uri); + } catch (ex) { + this.services.API.addLog( + `Failed to parse remote configuration '${config.id}': ${ex}`, + LOG_LEVEL_NOTICE + ); + return; + } + const workSettings = createBaseRemoteSettings(); + if (parsed.type === "couchdb") { + workSettings.remoteType = REMOTE_COUCHDB; + } else if (parsed.type === "s3") { + workSettings.remoteType = REMOTE_MINIO; + } else { + workSettings.remoteType = REMOTE_P2P; + } + Object.assign(workSettings, parsed.settings); + const newTweaks = + await this.services.tweakValue.checkAndAskUseRemoteConfiguration( + workSettings + ); + if (newTweaks.result !== false) { + this.editingSettings = { ...this.editingSettings, ...newTweaks.result }; + this.requestUpdate(); + } + }); + }) + .addSeparator() .addItem((item) => { item.setTitle("🗑 Delete").onClick(async () => { const confirmed = await this.services.UI.confirm.askYesNoDialog( diff --git a/styles.css b/styles.css index 0125ecc..a3b1792 100644 --- a/styles.css +++ b/styles.css @@ -73,6 +73,12 @@ overflow-y: scroll; } +.sls-remote-list .setting-item-description { + white-space: normal; + overflow-wrap: anywhere; + word-break: break-word; +} + .sls-plugins-tbl { border: 1px solid var(--background-modifier-border); width: 100%; From b0a9bd84d6c22c02847e79a52a89ca0cf6983ee9 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sun, 5 Apr 2026 18:21:38 +0900 Subject: [PATCH 132/339] bump --- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- updates.md | 10 ++++++++++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/manifest.json b/manifest.json index 560d7c8..1fdb8d6 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.56+patched2", + "version": "0.25.56+patched3", "minAppVersion": "0.9.12", "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 ef8ebca..6eef787 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.56+patched2", + "version": "0.25.56+patched3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.56+patched2", + "version": "0.25.56+patched3", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index c2d31bb..e4d7f51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.56+patched2", + "version": "0.25.56+patched3", "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 24c78e5..ea2ad0b 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,16 @@ 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.56+patched3 + +5th April, 2026 + +### Fixed + +- Now surely remote configurations are editable in the settings dialogue. +- We can fetch remote settings from the remote and apply them to the local settings for each remote configuration entry. +- No longer layout breaking occurs when the description of a remote configuration entry is too long. + ## 0.25.56+patched2 5th April, 2026 From 3e4db571cd86e0ab7a4604ada587d8a2a391c7db Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 6 Apr 2026 11:45:26 +0100 Subject: [PATCH 133/339] Fixed - Remote configuration URIs are now correctly encrypted when saved after editing in the settings dialogue. - Fixed an issue where devices could no longer upload after another device performed 'Fresh Start Wipe' and 'Overwrite remote' in Object Storage mode (#848). --- src/lib | 2 +- src/modules/features/SettingDialogue/PaneRemoteConfig.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib b/src/lib index 963a21f..d97288d 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 963a21f1d20f48d0336edbea92da41629050dc7a +Subproject commit d97288da2bf7041d4e34f4d430797a6f850dc343 diff --git a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts index 7684479..6c4b21e 100644 --- a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts +++ b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts @@ -254,7 +254,7 @@ export function paneRemoteConfig( id, name: name.trim() || "New Remote", uri: serializeRemoteConfiguration(nextSettings), - isEncrypted: nextSettings.encrypt, + isEncrypted: false, }; this.editingSettings.remoteConfigurations = configs; if (!this.editingSettings.activeConfigurationId) { @@ -361,7 +361,7 @@ export function paneRemoteConfig( nextConfigs[config.id] = { ...config, uri: serializeRemoteConfiguration(nextSettings), - isEncrypted: nextSettings.encrypt, + isEncrypted: false, }; this.editingSettings.remoteConfigurations = nextConfigs; await persistRemoteConfigurations(config.id === this.editingSettings.activeConfigurationId); From 4d0203e4ca742965ea97941b856ce23ea48a5fc9 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 6 Apr 2026 11:46:51 +0100 Subject: [PATCH 134/339] Update: beta tagging --- devs.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/devs.md b/devs.md index 9bd24fc..d44a68b 100644 --- a/devs.md +++ b/devs.md @@ -153,17 +153,17 @@ export class ModuleExample extends AbstractObsidianModule { ## Beta Policy -- Beta versions are denoted by appending `-patched-N` to the base version number. +- Beta versions are denoted by appending `+patchedN` to the base version number. - `The base version` mostly corresponds to the stable release version. - - e.g., v0.25.41-patched-1 is equivalent to v0.25.42-beta1. + - e.g., v0.25.41+patched1 is equivalent to v0.25.42-beta1. - This notation is due to SemVer incompatibility of Obsidian's plugin system. - - Hence, this release is `0.25.41-patched-1`. + - Hence, this release is `0.25.41+patched1`. - Each beta version may include larger changes, but bug fixes will often not be included. - I think that in most cases, bug fixes will cause the stable releases. - They will not be released per branch or backported; they will simply be released. - Bug fixes for previous versions will be applied to the latest beta version. - This means, if xx.yy.02-patched-1 exists and there is a defect in xx.yy.01, a fix is applied to xx.yy.02-patched-1 and yields xx.yy.02-patched-2. - If the fix is required immediately, it is released as xx.yy.02 (with xx.yy.01-patched-1). + This means, if xx.yy.02+patched1 exists and there is a defect in xx.yy.01, a fix is applied to xx.yy.02+patched1 and yields xx.yy.02+patched2. + If the fix is required immediately, it is released as xx.yy.02 (with xx.yy.01+patched1). - This procedure remains unchanged from the current one. - At the very least, I am using the latest beta. - However, I will not be using a beta continuously for a week after it has been released. It is probably closer to an RC in nature. From d59b5dc2f98b8d5dfa559df428332104e15bd9f6 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 6 Apr 2026 11:47:19 +0100 Subject: [PATCH 135/339] Fixed - Remote configuration URIs are now correctly encrypted when saved after editing in the settings dialogue. - Fixed an issue where devices could no longer upload after another device performed 'Fresh Start Wipe' and 'Overwrite remote' in Object Storage mode (#848). --- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- updates.md | 11 +++++++++++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/manifest.json b/manifest.json index 1fdb8d6..019f1f2 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.56+patched3", + "version": "0.25.56+patched4", "minAppVersion": "0.9.12", "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 6eef787..deff58d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.56+patched3", + "version": "0.25.56+patched4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.56+patched3", + "version": "0.25.56+patched4", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index e4d7f51..9cf7bf7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.56+patched3", + "version": "0.25.56+patched4", "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 ea2ad0b..fbcf6bc 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,17 @@ 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.56+patched4 + +6th April, 2026 + +### Fixed + +- Remote configuration URIs are now correctly encrypted when saved after editing in the settings dialogue. +- Fixed an issue where devices could no longer upload after another device performed 'Fresh Start Wipe' and 'Overwrite remote' in Object Storage mode (#848). + - Each device's local deduplication caches (`knownIDs`, `sentIDs`, `receivedFiles`, `sentFiles`) now track the remote journal epoch (derived from the encryption parameters stored on the remote). + - When the epoch changes, the plugin verifies whether the device's last uploaded file still exists on the remote. If the file is gone, it confirms a remote wipe and automatically clears the stale caches. If the file is still present (e.g. a protocol upgrade without a wipe), the caches are preserved and only the epoch is updated. This means normal upgrades never cause unnecessary re-processing. + ## 0.25.56+patched3 5th April, 2026 From 99b40378206ed5e9f229aea45c8a39dfec9c76b3 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 6 Apr 2026 12:51:09 +0100 Subject: [PATCH 136/339] Fixed - Packing a batch during the journal sync now continues even if the batch contains no items to upload. --- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- src/lib | 2 +- updates.md | 8 ++++++++ 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/manifest.json b/manifest.json index 019f1f2..b547fec 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.56+patched4", + "version": "0.25.56+patched5", "minAppVersion": "0.9.12", "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 deff58d..e624a39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.56+patched4", + "version": "0.25.56+patched5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.56+patched4", + "version": "0.25.56+patched5", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 9cf7bf7..46d2ded 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.56+patched4", + "version": "0.25.56+patched5", "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 d97288d..dd48d97 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit d97288da2bf7041d4e34f4d430797a6f850dc343 +Subproject commit dd48d97e712e16ed5262b6cde937fc41416a2b6f diff --git a/updates.md b/updates.md index fbcf6bc..b4462dd 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.56+patched5 + +6th April, 2026 + +### Fixed + +- Packing a batch during the journal sync now continues even if the batch contains no items to upload. + ## 0.25.56+patched4 6th April, 2026 From 7a863625bcb7b70c55188c5d127d65a1a2d56e49 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 9 Apr 2026 04:32:34 +0100 Subject: [PATCH 137/339] bump --- manifest.json | 2 +- package-lock.json | 16 ++++++++-------- package.json | 2 +- updates.md | 44 ++++++++------------------------------------ 4 files changed, 18 insertions(+), 46 deletions(-) diff --git a/manifest.json b/manifest.json index b547fec..8bcd242 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.56+patched5", + "version": "0.25.57", "minAppVersion": "0.9.12", "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 e624a39..26946b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.56+patched5", + "version": "0.25.57", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.56+patched5", + "version": "0.25.57", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", @@ -6073,9 +6073,9 @@ } }, "node_modules/basic-ftp": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz", - "integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.1.tgz", + "integrity": "sha512-0yaL8JdxTknKDILitVpfYfV2Ob6yb3udX/hK97M7I3jOeznBNxQPtVvTUtnhUkyHlxFWyr5Lvknmgzoc7jf+1Q==", "dev": true, "license": "MIT", "engines": { @@ -14218,9 +14218,9 @@ } }, "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "dev": true, "license": "MIT", "peer": true, diff --git a/package.json b/package.json index 46d2ded..4bbcb79 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.56+patched5", + "version": "0.25.57", "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 b4462dd..208a73a 100644 --- a/updates.md +++ b/updates.md @@ -3,60 +3,32 @@ 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.56+patched5 +## 0.25.57 -6th April, 2026 - -### Fixed +9th April, 2026 - Packing a batch during the journal sync now continues even if the batch contains no items to upload. - -## 0.25.56+patched4 - -6th April, 2026 - -### Fixed - -- Remote configuration URIs are now correctly encrypted when saved after editing in the settings dialogue. +- No unexpected error (about a replicator) during the early stage of initialisation. +- Now error messages are kept hidden if the show status inside the editor is disabled (related: #829). - Fixed an issue where devices could no longer upload after another device performed 'Fresh Start Wipe' and 'Overwrite remote' in Object Storage mode (#848). - Each device's local deduplication caches (`knownIDs`, `sentIDs`, `receivedFiles`, `sentFiles`) now track the remote journal epoch (derived from the encryption parameters stored on the remote). - - When the epoch changes, the plugin verifies whether the device's last uploaded file still exists on the remote. If the file is gone, it confirms a remote wipe and automatically clears the stale caches. If the file is still present (e.g. a protocol upgrade without a wipe), the caches are preserved and only the epoch is updated. This means normal upgrades never cause unnecessary re-processing. - -## 0.25.56+patched3 - -5th April, 2026 - -### Fixed - -- Now surely remote configurations are editable in the settings dialogue. -- We can fetch remote settings from the remote and apply them to the local settings for each remote configuration entry. -- No longer layout breaking occurs when the description of a remote configuration entry is too long. - -## 0.25.56+patched2 - -5th April, 2026 - -Beta release tagging is now changed to +patched1, +patched2, and so on. + - When the epoch changes, the plugin verifies whether the device's last uploaded file still exists on the remote. If the file is gone, it confirms a remote wipe and automatically clears the stale caches. If the file is still present (e.g. a protocol upgrade without a wipe), the caches are preserved, and only the epoch is updated. This means normal upgrades never cause unnecessary re-processing. ### Translations -- Russian translation has been added! Thank you so much for the contribution! - -### Fixed - -- No unexpected error (about a replicator) during the early stage of initialisation. -- Now error messages are kept hidden if the show status inside the editor is disabled. +- Russian translation has been added! Thank you so much for the contribution, @vipka1n! (#845) ### New features - Now we can configure multiple Remote Databases of the same type, e.g, multiple CouchDBs or S3 remotes. + - A user interface for managing multiple remote databases has been added to the settings dialogue. I think no explanation is needed for the UI, but please let me know if you have any questions. - We can switch between multiple Remote Databases in the settings dialogue. ### CLI #### Fixed -- Replication progress is now correctly saved and restored in the CLI. +- Replication progress is now correctly saved and restored in the CLI (related: #846). ## ~~0.25.55~~ 0.25.56 From a91258580057ddc06be373150540df182ca0ad2e Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 25 Apr 2026 14:01:18 +0900 Subject: [PATCH 138/339] Improve issue template Co-authored-by: Copilot --- .github/ISSUE_TEMPLATE/issue-report.md | 107 +++++++++++------- docs/to_issue_reporting.md | 145 +++++++++++++++++++++++++ 2 files changed, 212 insertions(+), 40 deletions(-) create mode 100644 docs/to_issue_reporting.md diff --git a/.github/ISSUE_TEMPLATE/issue-report.md b/.github/ISSUE_TEMPLATE/issue-report.md index 22579c8..2ca8a66 100644 --- a/.github/ISSUE_TEMPLATE/issue-report.md +++ b/.github/ISSUE_TEMPLATE/issue-report.md @@ -2,77 +2,104 @@ name: Issue report about: Create a report to help us improve title: '' -labels: '' +labels: 'bug' assignees: '' --- Thank you for taking the time to report this issue! -To improve the process, I would like to ask you to let me know the information in advance. +Before filling in this form, please read: [How to report an issue](../docs/to_issue_reporting.md). -All instructions and examples, and empty entries can be deleted. -Just for your information, a [filled example](https://docs.vrtmrz.net/LiveSync/hintandtrivia/Issue+example) is also written. +Issues with sufficient information will be prioritised. -## Abstract -The synchronisation hung up immediately after connecting. +--- -## Expected behaviour -- Synchronisation ends with the message `Replication completed` -- Everything synchronised +## Required -## Actually happened -- Synchronisation has been cancelled with the message `TypeError ... ` (captured in the attached log, around LL.10-LL.12) -- No files synchronised +### Abstract + -## Reproducing procedure +### Expected behaviour + -1. Configure LiveSync as in the attached material. -2. Click the replication button on the ribbon. -3. Synchronising has begun. -4. About two or three seconds later, we got the error `TypeError ... `. -5. Replication has been stopped. No files synchronised. +### Actually happened + -Note: If you do not catch the reproducing procedure, please let me know the frequency and signs. - -## Report materials -If the information is not available, do not hesitate to report it as it is. You can also of course omit it if you think this is indeed unnecessary. If it is necessary, I will ask you. - -### Report from the LiveSync -For more information, please refer to [Making the report](https://docs.vrtmrz.net/LiveSync/hintandtrivia/Making+the+report). -
-Report from hatch - -``` - -``` -
+### Reproducing procedure + ### Obsidian debug info +Please provide debug info for **each device involved**. The primary device (where the issue occurred) is required; others are strongly recommended. If your issue involves synchronisation between devices, debug info from relevant devices is very helpful. +To get it: open the command palette → "Show debug info". +
-Debug info +Device 1 (primary) ``` ```
+
+Device 2 (if applicable) + +``` + +``` +
+ +### LiveSync version +The hatch report (below) includes version information. If you cannot provide the report, please fill in the version here. + +- Self-hosted LiveSync version: + +### Report from LiveSync +Open the `Hatch` pane in LiveSync settings and press `Make report`. Paste here or upload to [Gist](https://gist.github.com/) and share the link. + +
+Report from hatch (primary) + +``` + +``` +
+ +
+Report from hatch (if applicable) + +``` + +``` +
+ + ### Plug-in log -We can see the log by tapping the Document box icon. If you noticed something suspicious, please let me know. -Note: **Please enable `Verbose Log`**. For detail, refer to [Logging](https://docs.vrtmrz.net/LiveSync/hintandtrivia/Logging), please. +Enable `Verbose Log` in General Settings first, then reproduce the issue and copy the log (tap the document box icon in the ribbon). +Paste here or upload to [Gist](https://gist.github.com/) and share the link.
-Plug-in log +Plug-in log (primary) ``` - + ```
-### Network log -Network logs displayed in DevTools will possibly help with connection-related issues. To capture that, please refer to [DevTools](https://docs.vrtmrz.net/LiveSync/hintandtrivia/DevTools). + +
+Plug-in log (if applicable) + +``` + +``` +
+ +--- + +## Optional ### Screenshots If applicable, please add screenshots to help explain your problem. -### Other information, insights and intuition. +### Other information, insights and intuition Please provide any additional context or information about the problem. diff --git a/docs/to_issue_reporting.md b/docs/to_issue_reporting.md new file mode 100644 index 0000000..615171b --- /dev/null +++ b/docs/to_issue_reporting.md @@ -0,0 +1,145 @@ +# How to report an issue + +Thank you for helping improve Self-hosted LiveSync! + +This document explains how to collect the information needed for an issue report. Issues with sufficient information will be prioritised. + +--- + +## Filled example + +Here is an example of a well-filled report for reference. + +### Abstract + +The synchronisation hung up immediately after connecting. + +### Expected behaviour + +- Synchronisation ends with the message `Replication completed` +- Everything synchronised + +### Actually happened + +- Synchronisation was cancelled with the message `TypeError: Failed to fetch` (visible in the plug-in log around lines 10–12) +- No files synchronised + +### Reproducing procedure + +1. Configure LiveSync with the settings shown in the attached report. +2. Click the sync button on the ribbon. +3. Synchronisation begins. +4. About two or three seconds later, the error `TypeError: Failed to fetch` appears. +5. Replication stops. No files synchronised. + +### Obsidian debug info (Device 1 — Windows desktop) + +``` +SYSTEM INFO: + Obsidian version: v1.2.8 + Installer version: v1.1.15 + Operating system: Windows 10 Pro 10.0.19044 + Login status: logged in + Catalyst license: supporter + Insider build toggle: off + Community theme: Minimal v6.1.11 + Snippets enabled: 3 + Restricted mode: off + Plugins installed: 35 + Plugins enabled: 11 + 1: Self-hosted LiveSync v0.19.4 + ... +``` + +### Report from LiveSync + +``` +----remote config---- +cors: + credentials: "true" + ... +---- Plug-in config --- +couchDB_URI: self-hosted +couchDB_USER: 𝑅𝐸𝐷𝐴𝐶𝑇𝐸𝐷 +... +``` + +### Plug-in log + +``` +2023/5/24 10:50:33->HTTP:GET to:/ -> failed +2023/5/24 10:50:33->TypeError:Failed to fetch +2023/5/24 10:50:33->could not connect to https://example.com/ : your vault +(TypeError:Failed to fetch) +``` + +--- + +## How to collect each piece of information + +### Obsidian debug info + +Open the command palette (`Ctrl/Cmd + P`) and run **"Show debug info"**. Copy the output and paste it into the issue. + +If multiple devices are involved in the problem (e.g., sync between a phone and a desktop), please provide the debug info for each device. The device where the issue occurred is required; information from other devices is strongly recommended. + +### Report from LiveSync (hatch report) + +1. Open LiveSync settings. +2. Go to the **Hatch** pane. +3. Press the **Make report** button. + +The report will be copied to your clipboard. It contains your LiveSync configuration and the remote server configuration, with credentials automatically redacted. + +**Tip:** For large reports, consider uploading to [GitHub Gist](https://gist.github.com/) and sharing the link instead of pasting directly into the issue. This makes it easier to manage, and if you accidentally leave sensitive data in, a Gist can be deleted. + +If you paste directly, wrap it in a `
` tag to keep the issue readable: + +``` +
+Report from hatch + +``` +----remote config---- + : +``` +
+``` + +### Plug-in log + +The plug-in log is volatile by default (not saved to disk) and shown only in the log dialogue, which can be opened by tapping the **document box icon** in the ribbon. + +#### Enable verbose log + +Before reproducing the issue, enable **Verbose Log** in LiveSync's **General Settings** pane. Without this, many diagnostic messages will be suppressed. + +#### Persist the log to a file (optional) + +If you need to capture a log across a restart, enable **"Write logs into the file"** in General Settings. Note that log files may contain sensitive information — use this option only for troubleshooting, and disable it afterwards. + +As with the hatch report, consider uploading large logs to [GitHub Gist](https://gist.github.com/). + +### Network log (for connection-related issues only) + +If the issue is related to network connectivity (e.g., cannot connect to the server, authentication errors), a network log captured from browser DevTools can be very helpful. You do not need to include this for non-connection issues. + +#### Opening DevTools + +| Platform | Shortcut | +|----------|----------| +| Windows / Linux | `Ctrl + Shift + I` | +| macOS | `Cmd + Shift + I` | +| Android | Use [Chrome remote debugging](https://developer.chrome.com/docs/devtools/remote-debugging/) | +| iOS | Use [Safari Web Inspector](https://developer.apple.com/documentation/safari-developer-tools/inspecting-ios) on a Mac | + +#### What to capture + +1. Open the **Network** pane in DevTools. +2. Reproduce the issue. +3. Look for requests marked in red. +4. Capture screenshots of the **Headers**, **Payload**, and **Response** tabs for those requests. + +**Important — redact before sharing:** +- Headers: conceal the request URL path, Remote Address, `authority`, and `authorisation` values. +- Payload / Response: the `_id` field contains your file paths — redact if needed. From 6ef56063b36f02332b88ee5cb593f0aab96eef23 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 25 Apr 2026 15:03:38 +0900 Subject: [PATCH 139/339] Fixed: No longer credentials are broken during object storage configuration (related: #852). Co-authored-by: Copilot --- .../SettingDialogue/PaneRemoteConfig.ts | 6 ++ .../SettingDialogue/remoteConfigBuffer.ts | 17 ++++ .../remoteConfigBuffer.unit.spec.ts | 83 +++++++++++++++++++ updates.md | 6 ++ 4 files changed, 112 insertions(+) create mode 100644 src/modules/features/SettingDialogue/remoteConfigBuffer.ts create mode 100644 src/modules/features/SettingDialogue/remoteConfigBuffer.unit.spec.ts diff --git a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts index 6c4b21e..e609e39 100644 --- a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts +++ b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts @@ -32,6 +32,7 @@ import SetupRemote from "../SetupWizard/dialogs/SetupRemote.svelte"; import SetupRemoteCouchDB from "../SetupWizard/dialogs/SetupRemoteCouchDB.svelte"; import SetupRemoteBucket from "../SetupWizard/dialogs/SetupRemoteBucket.svelte"; import SetupRemoteP2P from "../SetupWizard/dialogs/SetupRemoteP2P.svelte"; +import { syncActivatedRemoteSettings } from "./remoteConfigBuffer.ts"; function getSettingsFromEditingSettings(editingSettings: AllSettings): ObsidianLiveSyncSettings { const workObj = { ...editingSettings } as ObsidianLiveSyncSettings; @@ -183,6 +184,11 @@ export function paneRemoteConfig( }, true); if (synchroniseActiveRemote) { + // Keep both buffers aligned with the newly activated remote before saving any remaining dirty keys. + syncActivatedRemoteSettings(this.editingSettings, this.core.settings); + if (this.initialSettings) { + syncActivatedRemoteSettings(this.initialSettings, this.core.settings); + } await this.saveAllDirtySettings(); } diff --git a/src/modules/features/SettingDialogue/remoteConfigBuffer.ts b/src/modules/features/SettingDialogue/remoteConfigBuffer.ts new file mode 100644 index 0000000..be23e0d --- /dev/null +++ b/src/modules/features/SettingDialogue/remoteConfigBuffer.ts @@ -0,0 +1,17 @@ +import { pickBucketSyncSettings, pickCouchDBSyncSettings, pickP2PSyncSettings } from "@lib/common/utils.ts"; +import type { ObsidianLiveSyncSettings } from "@lib/common/types.ts"; + +// Keep the setting dialogue buffer aligned with the current core settings before persisting other dirty keys. +// This also clears stale dirty values left from editing a different remote type before switching active remotes. +export function syncActivatedRemoteSettings( + target: Partial, + source: ObsidianLiveSyncSettings +): void { + Object.assign(target, { + remoteType: source.remoteType, + activeConfigurationId: source.activeConfigurationId, + ...pickBucketSyncSettings(source), + ...pickCouchDBSyncSettings(source), + ...pickP2PSyncSettings(source), + }); +} diff --git a/src/modules/features/SettingDialogue/remoteConfigBuffer.unit.spec.ts b/src/modules/features/SettingDialogue/remoteConfigBuffer.unit.spec.ts new file mode 100644 index 0000000..224c7ce --- /dev/null +++ b/src/modules/features/SettingDialogue/remoteConfigBuffer.unit.spec.ts @@ -0,0 +1,83 @@ +import { describe, expect, it } from "vitest"; +import { DEFAULT_SETTINGS, REMOTE_COUCHDB, REMOTE_MINIO } from "../../../lib/src/common/types"; +import { syncActivatedRemoteSettings } from "./remoteConfigBuffer"; + +describe("syncActivatedRemoteSettings", () => { + it("should copy active MinIO credentials into the editing buffer", () => { + const target = { + ...DEFAULT_SETTINGS, + remoteType: REMOTE_COUCHDB, + activeConfigurationId: "old-remote", + accessKey: "", + secretKey: "", + endpoint: "", + bucket: "", + region: "", + encrypt: true, + }; + const source = { + ...DEFAULT_SETTINGS, + remoteType: REMOTE_MINIO, + activeConfigurationId: "remote-s3", + accessKey: "access", + secretKey: "secret", + endpoint: "https://minio.example.test", + bucket: "vault", + region: "sz-hq", + bucketPrefix: "folder/", + useCustomRequestHandler: false, + forcePathStyle: true, + bucketCustomHeaders: "", + }; + + syncActivatedRemoteSettings(target, source); + + expect(target.remoteType).toBe(REMOTE_MINIO); + expect(target.activeConfigurationId).toBe("remote-s3"); + expect(target.accessKey).toBe("access"); + expect(target.secretKey).toBe("secret"); + expect(target.endpoint).toBe("https://minio.example.test"); + expect(target.bucket).toBe("vault"); + expect(target.region).toBe("sz-hq"); + expect(target.bucketPrefix).toBe("folder/"); + expect(target.encrypt).toBe(true); + }); + + it("should clear stale dirty values from a different remote type", () => { + const target = { + ...DEFAULT_SETTINGS, + remoteType: REMOTE_MINIO, + activeConfigurationId: "remote-s3", + accessKey: "access", + secretKey: "secret", + endpoint: "https://minio.example.test", + bucket: "vault", + region: "sz-hq", + couchDB_URI: "https://edited.invalid", + couchDB_USER: "edited-user", + couchDB_PASSWORD: "edited-pass", + couchDB_DBNAME: "edited-db", + }; + const source = { + ...DEFAULT_SETTINGS, + remoteType: REMOTE_MINIO, + activeConfigurationId: "remote-s3", + accessKey: "access", + secretKey: "secret", + endpoint: "https://minio.example.test", + bucket: "vault", + region: "sz-hq", + couchDB_URI: "https://current.example.test", + couchDB_USER: "current-user", + couchDB_PASSWORD: "current-pass", + couchDB_DBNAME: "current-db", + }; + + syncActivatedRemoteSettings(target, source); + + expect(target.couchDB_URI).toBe("https://current.example.test"); + expect(target.couchDB_USER).toBe("current-user"); + expect(target.couchDB_PASSWORD).toBe("current-pass"); + expect(target.couchDB_DBNAME).toBe("current-db"); + }); +}); diff --git a/updates.md b/updates.md index 208a73a..24634a3 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,12 @@ 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 credentials are broken during object storage configuration (related: #852). + ## 0.25.57 9th April, 2026 From 1ef2955d002e187c8bb1aac7663bf9949c166475 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 25 Apr 2026 16:51:37 +0900 Subject: [PATCH 140/339] - Fixed a worker-side recursion issue that could raise `Maximum call stack size exceeded` during chunk splitting (related: #855). - Improved background worker crash cleanup so pending split/encryption tasks are released cleanly instead of being left in a waiting state (related: #855). - On start-up, the selected remote configuration is now applied to runtime connection fields as well, reducing intermittent authentication failures caused by stale runtime settings (related: #855). Co-authored-by: Copilot --- src/lib | 2 +- updates.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib b/src/lib index dd48d97..54408cd 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit dd48d97e712e16ed5262b6cde937fc41416a2b6f +Subproject commit 54408cd882b0f3534734ef9be17fdfd087a62c1d diff --git a/updates.md b/updates.md index 24634a3..db215ed 100644 --- a/updates.md +++ b/updates.md @@ -8,6 +8,9 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid ### Fixed - No longer credentials are broken during object storage configuration (related: #852). +- Fixed a worker-side recursion issue that could raise `Maximum call stack size exceeded` during chunk splitting (related: #855). +- Improved background worker crash cleanup so pending split/encryption tasks are released cleanly instead of being left in a waiting state (related: #855). +- On start-up, the selected remote configuration is now applied to runtime connection fields as well, reducing intermittent authentication failures caused by stale runtime settings (related: #855). ## 0.25.57 From b5d054f259e52bbb0e1f64040efa1d31923b7389 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 25 Apr 2026 17:09:43 +0900 Subject: [PATCH 141/339] Fixed: Issue report generation now redacts `remoteConfigurations` connection strings and keeps only the scheme (e.g. `sls+https://`), so credentials are not exposed in reports. Co-authored-by: Copilot --- src/lib | 2 +- .../features/SettingDialogue/PaneHatch.ts | 17 +++++++++++++++++ updates.md | 1 + 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/lib b/src/lib index 54408cd..5dc3b21 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 54408cd882b0f3534734ef9be17fdfd087a62c1d +Subproject commit 5dc3b21d36882607291100e21b494efe396de072 diff --git a/src/modules/features/SettingDialogue/PaneHatch.ts b/src/modules/features/SettingDialogue/PaneHatch.ts index 92b9326..ebfb377 100644 --- a/src/modules/features/SettingDialogue/PaneHatch.ts +++ b/src/modules/features/SettingDialogue/PaneHatch.ts @@ -137,6 +137,23 @@ export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement, pluginConfig.accessKey = REDACTED; pluginConfig.secretKey = REDACTED; const redact = (source: string) => `${REDACTED}(${source.length} letters)`; + const toSchemeOnly = (uri: string) => { + try { + return `${new URL(uri).protocol}//`; + } catch { + const matched = uri.match(/^[A-Za-z][A-Za-z0-9+.-]*:\/\//); + return matched?.[0] ?? REDACTED; + } + }; + pluginConfig.remoteConfigurations = Object.fromEntries( + Object.entries(pluginConfig.remoteConfigurations || {}).map(([id, config]) => [ + id, + { + ...config, + uri: toSchemeOnly(config.uri), + }, + ]) + ); pluginConfig.region = redact(pluginConfig.region); pluginConfig.bucket = redact(pluginConfig.bucket); pluginConfig.pluginSyncExtendedSetting = {}; diff --git a/updates.md b/updates.md index db215ed..39b918f 100644 --- a/updates.md +++ b/updates.md @@ -11,6 +11,7 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid - Fixed a worker-side recursion issue that could raise `Maximum call stack size exceeded` during chunk splitting (related: #855). - Improved background worker crash cleanup so pending split/encryption tasks are released cleanly instead of being left in a waiting state (related: #855). - On start-up, the selected remote configuration is now applied to runtime connection fields as well, reducing intermittent authentication failures caused by stale runtime settings (related: #855). +- Issue report generation now redacts `remoteConfigurations` connection strings and keeps only the scheme (e.g. `sls+https://`), so credentials are not exposed in reports. ## 0.25.57 From 31bd2708699b77c1ab5f3356b41f7578eee7de6e Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 25 Apr 2026 17:22:25 +0900 Subject: [PATCH 142/339] Fixed: Hidden file JSON conflicts no longer keep re-opening and dismissing the merge dialogue before we can act, which fixes persistent unresolvable `data.json` conflicts in plug-in settings sync (related: #850). Co-authored-by: Copilot --- .../HiddenFileCommon/JsonResolvePane.svelte | 3 +- .../HiddenFileSync/CmdHiddenFileSync.ts | 60 ++++++++++++++----- updates.md | 1 + 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/src/features/HiddenFileCommon/JsonResolvePane.svelte b/src/features/HiddenFileCommon/JsonResolvePane.svelte index bb1b22b..a950bf9 100644 --- a/src/features/HiddenFileCommon/JsonResolvePane.svelte +++ b/src/features/HiddenFileCommon/JsonResolvePane.svelte @@ -30,7 +30,8 @@ type JSONData = Record | [any]; const docsArray = $derived.by(() => { - if (docs && docs.length >= 1) { + // The merge pane compares two revisions, so guard against incomplete input before reading docs[1]. + if (docs && docs.length >= 2) { if (keepOrder || docs[0].mtime < docs[1].mtime) { return { a: docs[0], b: docs[1] } as const; } else { diff --git a/src/features/HiddenFileSync/CmdHiddenFileSync.ts b/src/features/HiddenFileSync/CmdHiddenFileSync.ts index d20ae55..ead080a 100644 --- a/src/features/HiddenFileSync/CmdHiddenFileSync.ts +++ b/src/features/HiddenFileSync/CmdHiddenFileSync.ts @@ -636,10 +636,24 @@ Offline Changed files: ${processFiles.length}`; // --> Conflict processing + // Keep one in-flight conflict check per path so repeated sync events do not close the active merge dialogue. + pendingConflictChecks = new Set(); + queueConflictCheck(path: FilePathWithPrefix) { + if (this.pendingConflictChecks.has(path)) return; + this.pendingConflictChecks.add(path); this.conflictResolutionProcessor.enqueue(path); } + finishConflictCheck(path: FilePathWithPrefix) { + this.pendingConflictChecks.delete(path); + } + + requeueConflictCheck(path: FilePathWithPrefix) { + this.finishConflictCheck(path); + this.queueConflictCheck(path); + } + async resolveConflictOnInternalFiles() { // Scan all conflicted internal files const conflicted = this.localDatabase.findEntries(ICHeader, ICHeaderEnd, { conflicts: true }); @@ -648,7 +662,7 @@ Offline Changed files: ${processFiles.length}`; for await (const doc of conflicted) { if (!("_conflicts" in doc)) continue; if (isInternalMetadata(doc._id)) { - this.conflictResolutionProcessor.enqueue(doc.path); + this.queueConflictCheck(doc.path); } } } catch (ex) { @@ -679,21 +693,27 @@ Offline Changed files: ${processFiles.length}`; const cc = await this.localDatabase.getRaw(id, { conflicts: true }); if (cc._conflicts?.length === 0) { await this.extractInternalFileFromDatabase(stripAllPrefixes(path)); + this.finishConflictCheck(path); } else { - this.conflictResolutionProcessor.enqueue(path); + this.requeueConflictCheck(path); } // check the file again } conflictResolutionProcessor = new QueueProcessor( async (paths: FilePathWithPrefix[]) => { const path = paths[0]; - sendSignal(`cancel-internal-conflict:${path}`); try { // Retrieve data const id = await this.path2id(path, ICHeader); const doc = await this.localDatabase.getRaw(id, { conflicts: true }); - if (doc._conflicts === undefined) return []; - if (doc._conflicts.length == 0) return []; + if (doc._conflicts === undefined) { + this.finishConflictCheck(path); + return []; + } + if (doc._conflicts.length == 0) { + this.finishConflictCheck(path); + return []; + } this._log(`Hidden file conflicted:${path}`); const conflicts = doc._conflicts.sort((a, b) => Number(a.split("-")[0]) - Number(b.split("-")[0])); const revA = doc._rev; @@ -725,7 +745,7 @@ Offline Changed files: ${processFiles.length}`; await this.storeInternalFileToDatabase({ path: filename, ...stat }); await this.extractInternalFileFromDatabase(filename); await this.localDatabase.removeRevision(id, revB); - this.conflictResolutionProcessor.enqueue(path); + this.requeueConflictCheck(path); return []; } else { this._log(`Object merge is not applicable.`, LOG_LEVEL_VERBOSE); @@ -743,6 +763,7 @@ Offline Changed files: ${processFiles.length}`; await this.resolveByNewerEntry(id, path, doc, revA, revB); return []; } catch (ex) { + this.finishConflictCheck(path); this._log(`Failed to resolve conflict (Hidden): ${path}`); this._log(ex, LOG_LEVEL_VERBOSE); return []; @@ -761,15 +782,22 @@ Offline Changed files: ${processFiles.length}`; const prefixedPath = addPrefix(path, ICHeader); const docAMerge = await this.localDatabase.getDBEntry(prefixedPath, { rev: revA }); const docBMerge = await this.localDatabase.getDBEntry(prefixedPath, { rev: revB }); - if (docAMerge != false && docBMerge != false) { - if (await this.showJSONMergeDialogAndMerge(docAMerge, docBMerge)) { - // Again for other conflicted revisions. - this.conflictResolutionProcessor.enqueue(path); + try { + if (docAMerge != false && docBMerge != false) { + if (await this.showJSONMergeDialogAndMerge(docAMerge, docBMerge)) { + // Again for other conflicted revisions. + this.requeueConflictCheck(path); + } else { + this.finishConflictCheck(path); + } + return; + } else { + // If either revision could not read, force resolving by the newer one. + await this.resolveByNewerEntry(id, path, doc, revA, revB); } - return; - } else { - // If either revision could not read, force resolving by the newer one. - await this.resolveByNewerEntry(id, path, doc, revA, revB); + } catch (ex) { + this.finishConflictCheck(path); + throw ex; } }, { @@ -793,6 +821,8 @@ Offline Changed files: ${processFiles.length}`; const storeFilePath = strippedPath; const displayFilename = `${storeFilePath}`; // const path = this.prefixedConfigDir2configDir(stripAllPrefixes(docA.path)) || docA.path; + // Cancel only when replacing an existing dialogue for the same path, not on every queue pass. + sendSignal(`cancel-internal-conflict:${docA.path}`); const modal = new JsonResolveModal(this.app, storageFilePath, [docA, docB], async (keep, result) => { // modal.close(); try { @@ -1164,7 +1194,7 @@ Offline Changed files: ${files.length}`; // Check if the file is conflicted, and if so, enqueue to resolve. // Until the conflict is resolved, the file will not be processed. if (docMeta._conflicts && docMeta._conflicts.length > 0) { - this.conflictResolutionProcessor.enqueue(path); + this.queueConflictCheck(path); this._log(`${headerLine} Hidden file conflicted, enqueued to resolve`); return true; } diff --git a/updates.md b/updates.md index 39b918f..45360d1 100644 --- a/updates.md +++ b/updates.md @@ -12,6 +12,7 @@ The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsid - Improved background worker crash cleanup so pending split/encryption tasks are released cleanly instead of being left in a waiting state (related: #855). - On start-up, the selected remote configuration is now applied to runtime connection fields as well, reducing intermittent authentication failures caused by stale runtime settings (related: #855). - Issue report generation now redacts `remoteConfigurations` connection strings and keeps only the scheme (e.g. `sls+https://`), so credentials are not exposed in reports. +- Hidden file JSON conflicts no longer keep re-opening and dismissing the merge dialogue before we can act, which fixes persistent unresolvable `data.json` conflicts in plug-in settings sync (related: #850). ## 0.25.57 From 354f0be9a3d79964444fd2095c94acf1082cf054 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sat, 25 Apr 2026 20:16:50 +0900 Subject: [PATCH 143/339] bump Co-authored-by: Copilot --- manifest.json | 2 +- package-lock.json | 75 +++++++++++++++++++++++++++-------------------- package.json | 2 +- updates.md | 2 +- 4 files changed, 47 insertions(+), 34 deletions(-) diff --git a/manifest.json b/manifest.json index 8bcd242..3c46372 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.57", + "version": "0.25.58", "minAppVersion": "0.9.12", "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 26946b2..b1ed022 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.57", + "version": "0.25.58", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.57", + "version": "0.25.58", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", @@ -924,13 +924,13 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.15", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.15.tgz", - "integrity": "sha512-PxMRlCFNiQnke9YR29vjFQwz4jq+6Q04rOVFeTDR2K7Qpv9h9FOWOxG+zJjageimYbWqE3bTuLjmryWHAWbvaA==", + "version": "3.972.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.19.tgz", + "integrity": "sha512-Cw8IOMdBUEIl8ZlhRC3Dc/E64D5B5/8JhV6vhPLiPfJwcRC84S6F8aBOIi/N4vR9ZyA4I5Cc0Ateb/9EHaJXeQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", - "fast-xml-parser": "5.5.8", + "@smithy/types": "^4.14.1", + "fast-xml-parser": "5.7.1", "tslib": "^2.6.2" }, "engines": { @@ -2668,6 +2668,18 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@nodable/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3945,9 +3957,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", - "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.1.tgz", + "integrity": "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -6073,9 +6085,9 @@ } }, "node_modules/basic-ftp": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.1.tgz", - "integrity": "sha512-0yaL8JdxTknKDILitVpfYfV2Ob6yb3udX/hK97M7I3jOeznBNxQPtVvTUtnhUkyHlxFWyr5Lvknmgzoc7jf+1Q==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.0.tgz", + "integrity": "sha512-5K9eNNn7ywHPsYnFwjKgYH8Hf8B5emh7JKcPaVjjrMJFQQwGpwowEnZNEtHs7DfR7hCZsmaK3VA4HUK0YarT+w==", "dev": true, "license": "MIT", "engines": { @@ -8155,9 +8167,9 @@ "license": "MIT" }, "node_modules/fast-xml-builder": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", - "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.5.tgz", + "integrity": "sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA==", "funding": [ { "type": "github", @@ -8170,9 +8182,9 @@ } }, "node_modules/fast-xml-parser": { - "version": "5.5.8", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.8.tgz", - "integrity": "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.1.tgz", + "integrity": "sha512-8Cc3f8GUGUULg34pBch/KGyPLglS+OFs05deyOlY7fL2MTagYPKrVQNmR1fLF/yJ9PH5ZSTd3YDF6pnmeZU+zA==", "funding": [ { "type": "github", @@ -8181,9 +8193,10 @@ ], "license": "MIT", "dependencies": { - "fast-xml-builder": "^1.1.4", - "path-expression-matcher": "^1.2.0", - "strnum": "^2.2.0" + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.5", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" @@ -11013,9 +11026,9 @@ } }, "node_modules/path-expression-matcher": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.0.tgz", - "integrity": "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", "funding": [ { "type": "github", @@ -11238,9 +11251,9 @@ } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", "dev": true, "funding": [ { @@ -12927,9 +12940,9 @@ } }, "node_modules/strnum": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.2.tgz", - "integrity": "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", + "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", "funding": [ { "type": "github", diff --git a/package.json b/package.json index 4bbcb79..4b51920 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.57", + "version": "0.25.58", "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 45360d1..2d7c144 100644 --- a/updates.md +++ b/updates.md @@ -3,7 +3,7 @@ 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.58 ### Fixed From 14ec35b25790206ff1b8b88610f81637da0f5e74 Mon Sep 17 00:00:00 2001 From: koteitan Date: Mon, 27 Apr 2026 00:12:57 +0900 Subject: [PATCH 144/339] fix: strip trailing slash from couchDB_URI to avoid double-slash 401 When couchDB_URI ends with a trailing slash (e.g. https://host/), the database name concatenation produces a double-slash path (https://host//obsidiannotes), which causes CouchDB to reject requests with 401 "Name or password is incorrect". Strip trailing slashes from couchDB_URI / baseUri at the path concatenation sites in: - src/common/utils.ts (_requestToCouchDBFetch, _requestToCouchDB) - src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts The companion fix for the replication path is in the livesync-commonlib submodule. Ref: #859 --- src/common/utils.ts | 4 ++-- src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/common/utils.ts b/src/common/utils.ts index eacc758..b008f2c 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -138,7 +138,7 @@ export const _requestToCouchDBFetch = async ( authorization: authHeader, "content-type": "application/json", }; - const uri = `${baseUri}/${path}`; + const uri = `${baseUri.replace(/\/+$/, "")}/${path}`; const requestParam = { url: uri, method: method || (body ? "PUT" : "GET"), @@ -162,7 +162,7 @@ export const _requestToCouchDB = async ( const authHeaderGen = new AuthorizationHeaderGenerator(); const authHeader = await authHeaderGen.getAuthorizationHeader(credentials); const transformedHeaders: Record = { authorization: authHeader, origin: origin, ...customHeaders }; - const uri = `${baseUri}/${path}`; + const uri = `${baseUri.replace(/\/+$/, "")}/${path}`; const requestParam: RequestUrlParam = { url: uri, method: method || (body ? "PUT" : "GET"), diff --git a/src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts b/src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts index 1f75385..fb8651c 100644 --- a/src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts +++ b/src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts @@ -781,7 +781,8 @@ Success: ${successCount}, Errored: ${errored}`; const credential = generateCredentialObject(this.settings); const request = async (path: string, method: string = "GET", body: any = undefined) => { const req = await _requestToCouchDB( - this.settings.couchDB_URI + (this.settings.couchDB_DBNAME ? `/${this.settings.couchDB_DBNAME}` : ""), + this.settings.couchDB_URI.replace(/\/+$/, "") + + (this.settings.couchDB_DBNAME ? `/${this.settings.couchDB_DBNAME}` : ""), credential, window.origin, path, From 7c9db6376fa20695263e4230eeaf3397db27c422 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 27 Apr 2026 11:14:06 +0900 Subject: [PATCH 145/339] Fixed: - No longer Setup-wizard drops username and password silently. (#865) - Setup URI is now correctly imported (#859). - now French translation is added. --- src/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib b/src/lib index 5dc3b21..e4380cc 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 5dc3b21d36882607291100e21b494efe396de072 +Subproject commit e4380ccd000278ed99c3de767f2e872087cca1db From bb69eb13e7e14d49c4389073f09886103be0b4eb Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 27 Apr 2026 11:15:07 +0900 Subject: [PATCH 146/339] bump --- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- updates.md | 13 +++++++++++++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/manifest.json b/manifest.json index 3c46372..1c1e14c 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.58", + "version": "0.25.59", "minAppVersion": "0.9.12", "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 b1ed022..62114d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.58", + "version": "0.25.59", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.58", + "version": "0.25.59", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index 4b51920..b4d2478 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.58", + "version": "0.25.59", "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 2d7c144..576f123 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +3,19 @@ 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.59 + +### Fixed + +- No longer Setup-wizard drops username and password silently. (#865) + - Thank you so much for @koteitan ! +- Setup URI is now correctly imported (#859). + - Also thank you so much for @koteitan ! + +### Improved + +- now French translation is added by @foXaCe ! Thank you so much! + ## 0.25.58 ### Fixed From 4c0af0b608e43d9143407c59d76baf4d7179f7a5 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 29 Apr 2026 12:22:00 +0900 Subject: [PATCH 147/339] Fixed(cli): - `ls` and `mirror` commands now provide informative feedback when no documents are found or filters skip all files, resolving the issue where they would exit silently (#860). - The command-line argument `vault` has been renamed to a more appropriate name, `databaseDir`. - The `mirror` command now accepts a `vault` directory, which specifies the location where the actual files are stored. For compatibility reasons, the previous behaviour is still supported. Co-authored-by: Copilot --- src/apps/cli/README.md | 73 ++++++++++++---- src/apps/cli/commands/runCommand.ts | 54 ++++++------ src/apps/cli/commands/runCommand.unit.spec.ts | 2 +- src/apps/cli/commands/types.ts | 2 +- src/apps/cli/commands/utils.ts | 14 ++-- src/apps/cli/commands/utils.unit.spec.ts | 24 +++--- src/apps/cli/main.ts | 36 ++++---- src/apps/cli/main.unit.spec.ts | 16 ++-- src/apps/cli/services/NodeServiceHub.ts | 8 +- src/apps/cli/test/repro-issue-860.sh | 49 +++++++++++ src/apps/cli/test/test-mirror-linux.sh | 83 ++++++++++++++----- src/lib | 2 +- updates.md | 9 ++ 13 files changed, 261 insertions(+), 111 deletions(-) create mode 100755 src/apps/cli/test/repro-issue-860.sh mode change 100644 => 100755 src/apps/cli/test/test-mirror-linux.sh diff --git a/src/apps/cli/README.md b/src/apps/cli/README.md index 8ea8db4..02dba18 100644 --- a/src/apps/cli/README.md +++ b/src/apps/cli/README.md @@ -45,9 +45,46 @@ CLI Main - Settings management (JSON file) - Graceful shutdown handling -## Something I realised later that could lead to misunderstandings +## Usage -The term `vault` in this README refers to the directory containing your local database and settings file. Not the actual files you want to sync. I will fix this later, but please be mind this for now. +The CLI operates on a **database directory** which contains PouchDB data and settings. + +```bash +lsync [database-path] [command] [args...] +``` + +### Arguments + +- `database-path`: Path to the directory where `.livesync` folder and `settings.json` are (or will be) located. + - Note: In previous versions, this was referred to as the "vault" path. Now it is clearly distinguished from the actual vault (the directory containing your `.md` files). + +### Commands + +- `sync`: Run one replication cycle with the remote CouchDB. +- `mirror [vault-path]`: Bidirectional sync between the local database and a local directory (**the actual vault**). + - If `vault-path` is provided, the CLI will synchronise the database with files in that directory. + - If `vault-path` is omitted, it defaults to `database-path` (compatibility mode). + - Use this command to keep your local `.md` files in sync with the database. +- `ls [prefix]`: List files currently stored in the local database. +- `push `: Push a local file `` into the database at path ``. +- `pull `: Pull a file `` from the database into local file ``. +- `cat `: Read a file from the database and write to stdout. +- `put `: Read from stdin and write to the database path ``. +- `init-settings [file]`: Create a default settings file. + +### Examples + +```bash +# Basic sync with remote +lsync ./my-db sync + +# Mirroring to your actual Obsidian vault +lsync ./my-db mirror /path/to/obsidian-vault + +# Manual file operations +lsync ./my-db push ./note.md folder/note.md +lsync ./my-db pull folder/note.md ./note.md +``` ## Docker @@ -61,16 +98,16 @@ Run: ```bash # Sync with CouchDB -docker run --rm -v /path/to/your/vault:/data livesync-cli sync +docker run --rm -v /path/to/your/db:/data livesync-cli sync + +# Mirror to a specific vault directory +docker run --rm -v /path/to/your/db:/data -v /path/to/your/vault:/vault livesync-cli mirror /vault # List files in the local database -docker run --rm -v /path/to/your/vault:/data livesync-cli ls - -# Generate a default settings file -docker run --rm -v /path/to/your/vault:/data livesync-cli init-settings +docker run --rm -v /path/to/your/db:/data livesync-cli ls ``` -The vault directory is mounted at `/data` by default. Override with `-e LIVESYNC_DB_PATH=/other/path`. +The database directory is mounted at `/data` by default. Override with `-e LIVESYNC_DB_PATH=/other/path`. ### P2P (WebRTC) and Docker networking @@ -78,11 +115,11 @@ The P2P replicator (`p2p-host`, `p2p-sync`, `p2p-peers`) uses WebRTC and generat three kinds of ICE candidates. The default Docker bridge network affects which candidates are usable: -| Candidate type | Description | Bridge network | -|---|---|---| -| `host` | Container bridge IP (`172.17.x.x`) | Unreachable from LAN peers | -| `srflx` | Host public IP via STUN reflection | Works over the internet | -| `relay` | Traffic relayed via TURN server | Always reachable | +| Candidate type | Description | Bridge network | +| -------------- | ---------------------------------- | -------------------------- | +| `host` | Container bridge IP (`172.17.x.x`) | Unreachable from LAN peers | +| `srflx` | Host public IP via STUN reflection | Works over the internet | +| `relay` | Traffic relayed via TURN server | Always reachable | **LAN P2P on Linux** — use `--network host` so that the real host IP is advertised as the `host` candidate: @@ -300,11 +337,11 @@ In other words, it performs the following actions: 5. **Categorisation and synchronisation** — The union of both file sets is split into three groups and processed concurrently (up to 10 files at a time): - | Group | Condition | Action | - |---|---|---| - | **UPDATE DATABASE** | File exists in storage only | Store the file into the local database. | - | **UPDATE STORAGE** | File exists in database only | If the entry is active (not deleted) and not conflicted, restore the file from the database to storage. Deleted entries and conflicted entries are skipped. | - | **SYNC DATABASE AND STORAGE** | File exists in both | Compare `mtime` freshness. If storage is newer → write to database (`STORAGE → DB`). If database is newer → restore to storage (`STORAGE ← DB`). If equal → do nothing. Conflicted documents and files exceeding the size limit are always skipped. | + | Group | Condition | Action | + | ----------------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | **UPDATE DATABASE** | File exists in storage only | Store the file into the local database. | + | **UPDATE STORAGE** | File exists in database only | If the entry is active (not deleted) and not conflicted, restore the file from the database to storage. Deleted entries and conflicted entries are skipped. | + | **SYNC DATABASE AND STORAGE** | File exists in both | Compare `mtime` freshness. If storage is newer → write to database (`STORAGE → DB`). If database is newer → restore to storage (`STORAGE ← DB`). If equal → do nothing. Conflicted documents and files exceeding the size limit are always skipped. | 6. **Initialisation flag** — On the very first successful run, writes `initialized = true` to the key-value database so that subsequent runs can restore state in step 2. diff --git a/src/apps/cli/commands/runCommand.ts b/src/apps/cli/commands/runCommand.ts index 12a315b..e188c23 100644 --- a/src/apps/cli/commands/runCommand.ts +++ b/src/apps/cli/commands/runCommand.ts @@ -5,13 +5,13 @@ import { configURIBase } from "@lib/common/models/shared.const"; import { DEFAULT_SETTINGS, type FilePathWithPrefix, type ObsidianLiveSyncSettings } from "@lib/common/types"; import { stripAllPrefixes } from "@lib/string_and_binary/path"; import type { CLICommandContext, CLIOptions } from "./types"; -import { promptForPassphrase, readStdinAsUtf8, toArrayBuffer, toVaultRelativePath } from "./utils"; +import { promptForPassphrase, readStdinAsUtf8, toArrayBuffer, toDatabaseRelativePath } from "./utils"; import { collectPeers, openP2PHost, parseTimeoutSeconds, syncWithPeer } from "./p2p"; import { performFullScan } from "@lib/serviceFeatures/offlineScanner"; import { UnresolvedErrorManager } from "@lib/services/base/UnresolvedErrorManager"; export async function runCommand(options: CLIOptions, context: CLICommandContext): Promise { - const { vaultPath, core, settingsPath } = context; + const { databasePath, core, settingsPath } = context; await core.services.control.activated; if (options.command === "daemon") { @@ -77,16 +77,16 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext throw new Error("push requires two arguments: "); } const sourcePath = path.resolve(options.commandArgs[0]); - const destinationVaultPath = toVaultRelativePath(options.commandArgs[1], vaultPath); + const destinationDatabasePath = toDatabaseRelativePath(options.commandArgs[1], databasePath); const sourceData = await fs.readFile(sourcePath); const sourceStat = await fs.stat(sourcePath); - console.log(`[Command] push ${sourcePath} -> ${destinationVaultPath}`); + console.log(`[Command] push ${sourcePath} -> ${destinationDatabasePath}`); - await core.serviceModules.storageAccess.writeFileAuto(destinationVaultPath, toArrayBuffer(sourceData), { + await core.serviceModules.storageAccess.writeFileAuto(destinationDatabasePath, toArrayBuffer(sourceData), { mtime: sourceStat.mtimeMs, ctime: sourceStat.ctimeMs, }); - const destinationPathWithPrefix = destinationVaultPath as FilePathWithPrefix; + const destinationPathWithPrefix = destinationDatabasePath as FilePathWithPrefix; const stored = await core.serviceModules.fileHandler.storeFileToDB(destinationPathWithPrefix, true); return stored; } @@ -95,16 +95,16 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext if (options.commandArgs.length < 2) { throw new Error("pull requires two arguments: "); } - const sourceVaultPath = toVaultRelativePath(options.commandArgs[0], vaultPath); + const sourceDatabasePath = toDatabaseRelativePath(options.commandArgs[0], databasePath); const destinationPath = path.resolve(options.commandArgs[1]); - console.log(`[Command] pull ${sourceVaultPath} -> ${destinationPath}`); + console.log(`[Command] pull ${sourceDatabasePath} -> ${destinationPath}`); - const sourcePathWithPrefix = sourceVaultPath as FilePathWithPrefix; + const sourcePathWithPrefix = sourceDatabasePath as FilePathWithPrefix; const restored = await core.serviceModules.fileHandler.dbToStorage(sourcePathWithPrefix, null, true); if (!restored) { return false; } - const data = await core.serviceModules.storageAccess.readFileAuto(sourceVaultPath); + const data = await core.serviceModules.storageAccess.readFileAuto(sourceDatabasePath); await fs.mkdir(path.dirname(destinationPath), { recursive: true }); if (typeof data === "string") { await fs.writeFile(destinationPath, data, "utf-8"); @@ -118,16 +118,16 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext if (options.commandArgs.length < 3) { throw new Error("pull-rev requires three arguments: "); } - const sourceVaultPath = toVaultRelativePath(options.commandArgs[0], vaultPath); + const sourceDatabasePath = toDatabaseRelativePath(options.commandArgs[0], databasePath); const destinationPath = path.resolve(options.commandArgs[1]); const rev = options.commandArgs[2].trim(); if (!rev) { throw new Error("pull-rev requires a non-empty revision"); } - console.log(`[Command] pull-rev ${sourceVaultPath}@${rev} -> ${destinationPath}`); + console.log(`[Command] pull-rev ${sourceDatabasePath}@${rev} -> ${destinationPath}`); const source = await core.serviceModules.databaseFileAccess.fetch( - sourceVaultPath as FilePathWithPrefix, + sourceDatabasePath as FilePathWithPrefix, rev, true ); @@ -175,11 +175,11 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext if (options.commandArgs.length < 1) { throw new Error("put requires one argument: "); } - const destinationVaultPath = toVaultRelativePath(options.commandArgs[0], vaultPath); + const destinationDatabasePath = toDatabaseRelativePath(options.commandArgs[0], databasePath); const content = await readStdinAsUtf8(); - console.log(`[Command] put stdin -> ${destinationVaultPath}`); + console.log(`[Command] put stdin -> ${destinationDatabasePath}`); return await core.serviceModules.databaseFileAccess.storeContent( - destinationVaultPath as FilePathWithPrefix, + destinationDatabasePath as FilePathWithPrefix, content ); } @@ -188,10 +188,10 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext if (options.commandArgs.length < 1) { throw new Error("cat requires one argument: "); } - const sourceVaultPath = toVaultRelativePath(options.commandArgs[0], vaultPath); - console.error(`[Command] cat ${sourceVaultPath}`); + const sourceDatabasePath = toDatabaseRelativePath(options.commandArgs[0], databasePath); + console.error(`[Command] cat ${sourceDatabasePath}`); const source = await core.serviceModules.databaseFileAccess.fetch( - sourceVaultPath as FilePathWithPrefix, + sourceDatabasePath as FilePathWithPrefix, undefined, true ); @@ -212,14 +212,14 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext if (options.commandArgs.length < 2) { throw new Error("cat-rev requires two arguments: "); } - const sourceVaultPath = toVaultRelativePath(options.commandArgs[0], vaultPath); + const sourceDatabasePath = toDatabaseRelativePath(options.commandArgs[0], databasePath); const rev = options.commandArgs[1].trim(); if (!rev) { throw new Error("cat-rev requires a non-empty revision"); } - console.error(`[Command] cat-rev ${sourceVaultPath} @ ${rev}`); + console.error(`[Command] cat-rev ${sourceDatabasePath} @ ${rev}`); const source = await core.serviceModules.databaseFileAccess.fetch( - sourceVaultPath as FilePathWithPrefix, + sourceDatabasePath as FilePathWithPrefix, rev, true ); @@ -239,7 +239,7 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext if (options.command === "ls") { const prefix = options.commandArgs.length > 0 && options.commandArgs[0].trim() !== "" - ? toVaultRelativePath(options.commandArgs[0], vaultPath) + ? toDatabaseRelativePath(options.commandArgs[0], databasePath) : ""; const rows: { path: string; line: string }[] = []; @@ -261,6 +261,8 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext rows.sort((a, b) => a.path.localeCompare(b.path)); if (rows.length > 0) { process.stdout.write(rows.map((e) => e.line).join("\n") + "\n"); + } else { + process.stderr.write("[Info] No documents found in the local database.\n"); } return true; } @@ -269,7 +271,7 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext if (options.commandArgs.length < 1) { throw new Error("info requires one argument: "); } - const targetPath = toVaultRelativePath(options.commandArgs[0], vaultPath); + const targetPath = toDatabaseRelativePath(options.commandArgs[0], databasePath); for await (const doc of core.services.database.localDatabase.findAllNormalDocs({ conflicts: true })) { if (doc._deleted || doc.deleted) continue; @@ -313,7 +315,7 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext if (options.commandArgs.length < 1) { throw new Error("rm requires one argument: "); } - const targetPath = toVaultRelativePath(options.commandArgs[0], vaultPath); + const targetPath = toDatabaseRelativePath(options.commandArgs[0], databasePath); console.error(`[Command] rm ${targetPath}`); return await core.serviceModules.databaseFileAccess.delete(targetPath as FilePathWithPrefix); } @@ -322,7 +324,7 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext if (options.commandArgs.length < 2) { throw new Error("resolve requires two arguments: "); } - const targetPath = toVaultRelativePath(options.commandArgs[0], vaultPath) as FilePathWithPrefix; + const targetPath = toDatabaseRelativePath(options.commandArgs[0], databasePath) as FilePathWithPrefix; const revisionToKeep = options.commandArgs[1].trim(); if (revisionToKeep === "") { throw new Error("resolve requires a non-empty revision-to-keep"); diff --git a/src/apps/cli/commands/runCommand.unit.spec.ts b/src/apps/cli/commands/runCommand.unit.spec.ts index 1a5b3da..85a91b8 100644 --- a/src/apps/cli/commands/runCommand.unit.spec.ts +++ b/src/apps/cli/commands/runCommand.unit.spec.ts @@ -58,7 +58,7 @@ async function createSetupURI(passphrase: string): Promise { describe("runCommand abnormal cases", () => { const context = { - vaultPath: "/tmp/vault", + databasePath: "/tmp/vault", settingsPath: "/tmp/vault/.livesync/settings.json", } as any; diff --git a/src/apps/cli/commands/types.ts b/src/apps/cli/commands/types.ts index 01ea118..f63f751 100644 --- a/src/apps/cli/commands/types.ts +++ b/src/apps/cli/commands/types.ts @@ -32,7 +32,7 @@ export interface CLIOptions { } export interface CLICommandContext { - vaultPath: string; + databasePath: string; core: LiveSyncBaseCore; settingsPath: string; } diff --git a/src/apps/cli/commands/utils.ts b/src/apps/cli/commands/utils.ts index f56940f..b596a71 100644 --- a/src/apps/cli/commands/utils.ts +++ b/src/apps/cli/commands/utils.ts @@ -5,19 +5,19 @@ export function toArrayBuffer(data: Buffer): ArrayBuffer { return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer; } -export function toVaultRelativePath(inputPath: string, vaultPath: string): string { +export function toDatabaseRelativePath(inputPath: string, databasePath: string): string { const stripped = inputPath.replace(/^[/\\]+/, ""); if (!path.isAbsolute(inputPath)) { const normalized = stripped.replace(/\\/g, "/"); - const resolved = path.resolve(vaultPath, normalized); - const rel = path.relative(vaultPath, resolved); + const resolved = path.resolve(databasePath, normalized); + const rel = path.relative(databasePath, resolved); if (rel.startsWith("..") || path.isAbsolute(rel)) { throw new Error(`Path ${inputPath} is outside of the local database directory`); } return rel.replace(/\\/g, "/"); } const resolved = path.resolve(inputPath); - const rel = path.relative(vaultPath, resolved); + const rel = path.relative(databasePath, resolved); if (rel.startsWith("..") || path.isAbsolute(rel)) { throw new Error(`Path ${inputPath} is outside of the local database directory`); } @@ -25,15 +25,15 @@ export function toVaultRelativePath(inputPath: string, vaultPath: string): strin } export async function readStdinAsUtf8(): Promise { - const chunks: Buffer[] = []; + const chunks = []; for await (const chunk of process.stdin) { if (typeof chunk === "string") { chunks.push(Buffer.from(chunk, "utf-8")); } else { - chunks.push(chunk); + chunks.push(chunk as Buffer); } } - return Buffer.concat(chunks).toString("utf-8"); + return Buffer.concat(chunks as Uint8Array[]).toString("utf-8"); } export async function promptForPassphrase(prompt = "Enter setup URI passphrase: "): Promise { diff --git a/src/apps/cli/commands/utils.unit.spec.ts b/src/apps/cli/commands/utils.unit.spec.ts index e209bf7..5d5f77a 100644 --- a/src/apps/cli/commands/utils.unit.spec.ts +++ b/src/apps/cli/commands/utils.unit.spec.ts @@ -1,29 +1,33 @@ import * as path from "path"; import { describe, expect, it } from "vitest"; -import { toVaultRelativePath } from "./utils"; +import { toDatabaseRelativePath } from "./utils"; -describe("toVaultRelativePath", () => { - const vaultPath = path.resolve("/tmp/livesync-vault"); +describe("toDatabaseRelativePath", () => { + const databasePath = path.resolve("/tmp/livesync-vault"); it("rejects absolute paths outside vault", () => { - expect(() => toVaultRelativePath("/etc/passwd", vaultPath)).toThrow("outside of the local database directory"); + expect(() => toDatabaseRelativePath("/etc/passwd", databasePath)).toThrow( + "outside of the local database directory" + ); }); it("normalizes leading slash for absolute path inside vault", () => { - const absoluteInsideVault = path.join(vaultPath, "notes", "foo.md"); - expect(toVaultRelativePath(absoluteInsideVault, vaultPath)).toBe("notes/foo.md"); + const absoluteInsideVault = path.join(databasePath, "notes", "foo.md"); + expect(toDatabaseRelativePath(absoluteInsideVault, databasePath)).toBe("notes/foo.md"); }); it("normalizes Windows-style separators", () => { - expect(toVaultRelativePath("notes\\daily\\2026-03-12.md", vaultPath)).toBe("notes/daily/2026-03-12.md"); + expect(toDatabaseRelativePath("notes\\daily\\2026-03-12.md", databasePath)).toBe("notes/daily/2026-03-12.md"); }); it("returns vault-relative path for another absolute path inside vault", () => { - const absoluteInsideVault = path.join(vaultPath, "docs", "inside.md"); - expect(toVaultRelativePath(absoluteInsideVault, vaultPath)).toBe("docs/inside.md"); + const absoluteInsideVault = path.join(databasePath, "docs", "inside.md"); + expect(toDatabaseRelativePath(absoluteInsideVault, databasePath)).toBe("docs/inside.md"); }); it("rejects relative path traversal that escapes vault", () => { - expect(() => toVaultRelativePath("../escape.md", vaultPath)).toThrow("outside of the local database directory"); + expect(() => toDatabaseRelativePath("../escape.md", databasePath)).toThrow( + "outside of the local database directory" + ); }); }); diff --git a/src/apps/cli/main.ts b/src/apps/cli/main.ts index ecd8afa..29a6ec4 100644 --- a/src/apps/cli/main.ts +++ b/src/apps/cli/main.ts @@ -58,6 +58,7 @@ Commands: info Show detailed metadata for a file (ID, revision, conflicts, chunks) 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) Examples: livesync-cli ./my-database sync livesync-cli ./my-database p2p-peers 5 @@ -112,6 +113,7 @@ export function parseArgs(): CLIOptions { case "-d": // debugging automatically enables verbose logging, as it is intended for debugging issues. debug = true; + // falls through case "--verbose": case "-v": verbose = true; @@ -220,34 +222,34 @@ export async function main() { return; } - // Resolve vault path - const vaultPath = path.resolve(options.databasePath!); - // Check if vault directory exists + // Resolve database path + const databasePath = path.resolve(options.databasePath!); + // Check if database directory exists try { - const stat = await fs.stat(vaultPath); + const stat = await fs.stat(databasePath); if (!stat.isDirectory()) { - console.error(`Error: ${vaultPath} is not a directory`); + console.error(`Error: ${databasePath} is not a directory`); process.exit(1); } } catch (error) { - console.error(`Error: Vault directory ${vaultPath} does not exist`); + console.error(`Error: Database directory ${databasePath} does not exist`); process.exit(1); } // Resolve settings path const settingsPath = options.settingsPath ? path.resolve(options.settingsPath) - : path.join(vaultPath, SETTINGS_FILE); - configureNodeLocalStorage(path.join(vaultPath, ".livesync", "runtime", "local-storage.json")); + : path.join(databasePath, SETTINGS_FILE); + configureNodeLocalStorage(path.join(databasePath, ".livesync", "runtime", "local-storage.json")); infoLog(`Self-hosted LiveSync CLI`); - infoLog(`Vault: ${vaultPath}`); + infoLog(`Database Path: ${databasePath}`); infoLog(`Settings: ${settingsPath}`); infoLog(""); // Create service context and hub - const context = new NodeServiceContext(vaultPath); - const serviceHubInstance = new NodeServiceHub(vaultPath, context); + const context = new NodeServiceContext(databasePath); + const serviceHubInstance = new NodeServiceHub(databasePath, context); serviceHubInstance.API.addLog.setHandler((message: string, level: LOG_LEVEL) => { let levelStr = ""; switch (level) { @@ -321,7 +323,11 @@ export async function main() { const core = new LiveSyncBaseCore( serviceHubInstance, (core: LiveSyncBaseCore, serviceHub: InjectableServiceHub) => { - return initialiseServiceModulesCLI(vaultPath, core, serviceHub); + const mirrorVaultPath = + options.command === "mirror" && options.commandArgs[0] + ? path.resolve(options.commandArgs[0]) + : databasePath; + return initialiseServiceModulesCLI(mirrorVaultPath, core, serviceHub); }, (core) => [ // No modules need to be registered for P2P replication in CLI. Directly using Replicators in p2p.ts @@ -331,8 +337,8 @@ export async function main() { (core) => { // Add target filter to prevent internal files are handled core.services.vault.isTargetFile.addHandler(async (target) => { - const vaultPath = stripAllPrefixes(getPathFromUXFileInfo(target)); - const parts = vaultPath.split(path.sep); + const targetPath = stripAllPrefixes(getPathFromUXFileInfo(target)); + const parts = targetPath.split(path.sep); // if some part of the path starts with dot, treat it as internal file and ignore. if (parts.some((part) => part.startsWith("."))) { return await Promise.resolve(false); @@ -393,7 +399,7 @@ export async function main() { infoLog(""); } - const result = await runCommand(options, { vaultPath, core, settingsPath }); + const result = await runCommand(options, { databasePath, core, settingsPath }); if (!result) { console.error(`[Error] Command '${options.command}' failed`); process.exitCode = 1; diff --git a/src/apps/cli/main.unit.spec.ts b/src/apps/cli/main.unit.spec.ts index 8206f03..83c3177 100644 --- a/src/apps/cli/main.unit.spec.ts +++ b/src/apps/cli/main.unit.spec.ts @@ -17,7 +17,7 @@ describe("CLI parseArgs", () => { }); it("exits 1 when --settings has no value", () => { - process.argv = ["node", "livesync-cli", "./vault", "--settings"]; + process.argv = ["node", "livesync-cli", "./databasePath", "--settings"]; const exitMock = mockProcessExit(); const stderr = vi.spyOn(console, "error").mockImplementation(() => {}); @@ -37,7 +37,7 @@ describe("CLI parseArgs", () => { }); it("exits 1 for unknown command after database-path", () => { - process.argv = ["node", "livesync-cli", "./vault", "unknown-cmd"]; + process.argv = ["node", "livesync-cli", "./databasePath", "unknown-cmd"]; const exitMock = mockProcessExit(); const stderr = vi.spyOn(console, "error").mockImplementation(() => {}); @@ -60,28 +60,28 @@ describe("CLI parseArgs", () => { }); it("parses p2p-peers command and timeout", () => { - process.argv = ["node", "livesync-cli", "./vault", "p2p-peers", "5"]; + process.argv = ["node", "livesync-cli", "./databasePath", "p2p-peers", "5"]; const parsed = parseArgs(); - expect(parsed.databasePath).toBe("./vault"); + expect(parsed.databasePath).toBe("./databasePath"); expect(parsed.command).toBe("p2p-peers"); expect(parsed.commandArgs).toEqual(["5"]); }); it("parses p2p-sync command with peer and timeout", () => { - process.argv = ["node", "livesync-cli", "./vault", "p2p-sync", "peer-1", "12"]; + process.argv = ["node", "livesync-cli", "./databasePath", "p2p-sync", "peer-1", "12"]; const parsed = parseArgs(); - expect(parsed.databasePath).toBe("./vault"); + expect(parsed.databasePath).toBe("./databasePath"); expect(parsed.command).toBe("p2p-sync"); expect(parsed.commandArgs).toEqual(["peer-1", "12"]); }); it("parses p2p-host command", () => { - process.argv = ["node", "livesync-cli", "./vault", "p2p-host"]; + process.argv = ["node", "livesync-cli", "./databasePath", "p2p-host"]; const parsed = parseArgs(); - expect(parsed.databasePath).toBe("./vault"); + expect(parsed.databasePath).toBe("./databasePath"); expect(parsed.command).toBe("p2p-host"); expect(parsed.commandArgs).toEqual([]); }); diff --git a/src/apps/cli/services/NodeServiceHub.ts b/src/apps/cli/services/NodeServiceHub.ts index 9815f42..eb2ad69 100644 --- a/src/apps/cli/services/NodeServiceHub.ts +++ b/src/apps/cli/services/NodeServiceHub.ts @@ -27,10 +27,10 @@ import { DatabaseService } from "@lib/services/base/DatabaseService"; import type { ObsidianLiveSyncSettings } from "@/lib/src/common/types"; export class NodeServiceContext extends ServiceContext { - vaultPath: string; - constructor(vaultPath: string) { + databasePath: string; + constructor(databasePath: string) { super(); - this.vaultPath = vaultPath; + this.databasePath = databasePath; } } @@ -64,7 +64,7 @@ class NodeDatabaseService extends DatabaseService< ): { name: string; options: PouchDB.Configuration.DatabaseConfiguration } { const optionPass = { ...options, - prefix: this.context.vaultPath + nodePath.sep, + prefix: this.context.databasePath + nodePath.sep, }; const passSettings = { ...settings, useIndexedDBAdapter: false }; return super.modifyDatabaseOptions(passSettings, name, optionPass); diff --git a/src/apps/cli/test/repro-issue-860.sh b/src/apps/cli/test/repro-issue-860.sh new file mode 100755 index 0000000..801e53c --- /dev/null +++ b/src/apps/cli/test/repro-issue-860.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +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 "Test for Issue #860: Empty output from ls and mirror" + +RUN_BUILD="${RUN_BUILD:-1}" +cli_test_init_cli_cmd + +WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-repro-860.XXXXXX")" +trap 'rm -rf "$WORK_DIR"' EXIT + +SETTINGS_FILE="$WORK_DIR/data.json" +VAULT_DIR="$WORK_DIR/vault" +mkdir -p "$VAULT_DIR" + +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" + +# 1. Test 'ls' on empty database +echo "[INFO] Testing 'ls' on empty database..." +LS_OUTPUT=$(run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" ls) +if [[ -z "$LS_OUTPUT" ]]; then + echo "[REPRODUCED] 'ls' returned empty output for empty database." +else + echo "[INFO] 'ls' output: $LS_OUTPUT" +fi + +# 2. Test 'mirror' on empty vault +echo "[INFO] Testing 'mirror' on empty vault..." +MIRROR_OUTPUT=$(run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror 2>&1) +if [[ "$MIRROR_OUTPUT" == *"[Command] mirror"* ]] && [[ ! "$MIRROR_OUTPUT" == *"[Mirror]"* ]]; then + # Note: currently it prints [Command] mirror to stderr. + # Let's see if it prints anything else. + echo "[REPRODUCED] 'mirror' produced no functional logs (only command header)." +else + echo "[INFO] 'mirror' output: $MIRROR_OUTPUT" +fi + +echo "[DONE] finished repro-860 test" diff --git a/src/apps/cli/test/test-mirror-linux.sh b/src/apps/cli/test/test-mirror-linux.sh old mode 100644 new mode 100755 index 389cf00..21a24d3 --- a/src/apps/cli/test/test-mirror-linux.sh +++ b/src/apps/cli/test/test-mirror-linux.sh @@ -28,7 +28,9 @@ trap 'rm -rf "$WORK_DIR"' EXIT SETTINGS_FILE="$WORK_DIR/data.json" VAULT_DIR="$WORK_DIR/vault" +DB_DIR="$WORK_DIR/db" mkdir -p "$VAULT_DIR/test" +mkdir -p "$DB_DIR" if [[ "$RUN_BUILD" == "1" ]]; then echo "[INFO] building CLI..." @@ -41,6 +43,20 @@ cli_test_init_settings_file "$SETTINGS_FILE" # isConfigured=true is required for mirror (canProceedScan checks this) cli_test_mark_settings_configured "$SETTINGS_FILE" +# Preparation: Sync settings and files logic +DB_SETTINGS="$DB_DIR/settings.json" +cp "$SETTINGS_FILE" "$DB_SETTINGS" + +# Helper for standard run (Separated paths) +run_mirror_test() { + run_cli "$DB_DIR" --settings "$DB_SETTINGS" mirror "$VAULT_DIR" +} + +# Helper for compatibility run (Same path) +run_mirror_compat() { + run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror +} + PASS=0 FAIL=0 @@ -78,19 +94,27 @@ portable_touch_timestamp() { # Case 1: File exists only in storage → should be synced into DB after mirror # ───────────────────────────────────────────────────────────────────────────── echo "" -echo "=== Case 1: storage-only → DB ===" +echo "=== Case 1: storage-only → DB (Separated Paths) ===" printf 'storage-only content\n' > "$VAULT_DIR/test/storage-only.md" -run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror +echo "[DEBUG] DB_DIR: $DB_DIR" +echo "[DEBUG] VAULT_DIR: $VAULT_DIR" + +run_mirror_test RESULT_FILE="$WORK_DIR/case1-cat.txt" -run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" pull test/storage-only.md "$RESULT_FILE" +# Try 'ls' first to see what's in the DB +echo "--- DB contents ---" +run_cli "$DB_DIR" --settings "$DB_SETTINGS" ls +echo "-------------------" + +run_cli "$DB_DIR" --settings "$DB_SETTINGS" pull test/storage-only.md "$RESULT_FILE" if cmp -s "$VAULT_DIR/test/storage-only.md" "$RESULT_FILE"; then - assert_pass "storage-only file was synced into DB" + assert_pass "storage-only file was synced into DB using separated paths" else - assert_fail "storage-only file NOT synced into DB" + assert_fail "storage-only file NOT synced into DB with separated paths" echo "--- storage ---" >&2; cat "$VAULT_DIR/test/storage-only.md" >&2 echo "--- cat ---" >&2; cat "$RESULT_FILE" >&2 fi @@ -99,9 +123,9 @@ fi # Case 2: File exists only in DB → should be restored to storage after mirror # ───────────────────────────────────────────────────────────────────────────── echo "" -echo "=== Case 2: DB-only → storage ===" +echo "=== Case 2: DB-only → storage (Separated Paths) ===" -printf 'db-only content\n' | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" put test/db-only.md +printf 'db-only content\n' | run_cli "$DB_DIR" --settings "$DB_SETTINGS" put test/db-only.md if [[ -f "$VAULT_DIR/test/db-only.md" ]]; then assert_fail "db-only.md unexpectedly exists in storage before mirror" @@ -109,7 +133,7 @@ else echo "[INFO] confirmed: test/db-only.md not in storage before mirror" fi -run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror +run_mirror_test if [[ -f "$VAULT_DIR/test/db-only.md" ]]; then STORAGE_CONTENT="$(cat "$VAULT_DIR/test/db-only.md")" @@ -119,19 +143,19 @@ if [[ -f "$VAULT_DIR/test/db-only.md" ]]; then assert_fail "DB-only file restored but content mismatch (got: '${STORAGE_CONTENT}')" fi else - assert_fail "DB-only file was NOT restored to storage" + assert_fail "DB-only file NOT restored to storage after mirror" fi # ───────────────────────────────────────────────────────────────────────────── # Case 3: File deleted in DB → should NOT be created in storage # ───────────────────────────────────────────────────────────────────────────── echo "" -echo "=== Case 3: DB-deleted → storage untouched ===" +echo "=== Case 3: DB-deleted → storage untouched (Separated Paths) ===" -printf 'to-be-deleted\n' | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" put test/deleted.md -run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" rm test/deleted.md +printf 'to-be-deleted\n' | run_cli "$DB_DIR" --settings "$DB_SETTINGS" put test/deleted.md +run_cli "$DB_DIR" --settings "$DB_SETTINGS" rm test/deleted.md -run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror +run_mirror_test if [[ ! -f "$VAULT_DIR/test/deleted.md" ]]; then assert_pass "deleted DB entry was not restored to storage" @@ -143,19 +167,19 @@ fi # Case 4: Both exist, storage is newer → DB should be updated # ───────────────────────────────────────────────────────────────────────────── echo "" -echo "=== Case 4: storage newer → DB updated ===" +echo "=== Case 4: storage newer → DB updated (Separated Paths) ===" # Seed DB with old content (mtime ≈ now) -printf 'old content\n' | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" put test/sync-storage-newer.md +printf 'old content\n' | run_cli "$DB_DIR" --settings "$DB_SETTINGS" put test/sync-storage-newer.md # Write new content to storage with a timestamp 1 hour in the future printf 'new content\n' > "$VAULT_DIR/test/sync-storage-newer.md" touch -t "$(portable_touch_timestamp '+1 hour')" "$VAULT_DIR/test/sync-storage-newer.md" -run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror +run_mirror_test DB_RESULT_FILE="$WORK_DIR/case4-pull.txt" -run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" pull test/sync-storage-newer.md "$DB_RESULT_FILE" +run_cli "$DB_DIR" --settings "$DB_SETTINGS" pull test/sync-storage-newer.md "$DB_RESULT_FILE" if cmp -s "$VAULT_DIR/test/sync-storage-newer.md" "$DB_RESULT_FILE"; then assert_pass "DB updated to match newer storage file" else @@ -168,16 +192,16 @@ fi # Case 5: Both exist, DB is newer → storage should be updated # ───────────────────────────────────────────────────────────────────────────── echo "" -echo "=== Case 5: DB newer → storage updated ===" +echo "=== Case 5: DB newer → storage updated (Separated Paths) ===" # Write old content to storage with a timestamp 1 hour in the past printf 'old storage content\n' > "$VAULT_DIR/test/sync-db-newer.md" touch -t "$(portable_touch_timestamp '-1 hour')" "$VAULT_DIR/test/sync-db-newer.md" # Write new content to DB only (mtime ≈ now, newer than the storage file) -printf 'new db content\n' | run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" put test/sync-db-newer.md +printf 'new db content\n' | run_cli "$DB_DIR" --settings "$DB_SETTINGS" put test/sync-db-newer.md -run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror +run_mirror_test STORAGE_CONTENT="$(cat "$VAULT_DIR/test/sync-db-newer.md")" if [[ "$STORAGE_CONTENT" == "new db content" ]]; then @@ -186,6 +210,25 @@ else assert_fail "storage NOT updated to match newer DB entry (got: '${STORAGE_CONTENT}')" fi +# ───────────────────────────────────────────────────────────────────────────── +# Case 6: Compatibility test - omitted vault-path +# ───────────────────────────────────────────────────────────────────────────── +echo "" +echo "=== Case 6: omitted vault-path (Compatibility Mode) ===" + +# We use VAULT_DIR as the "main" database path for this part. +printf 'compat-content\n' > "$VAULT_DIR/compat.md" +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror + +# In compat mode, it should find it in the DB at root +CAT_RESULT="$WORK_DIR/compat-cat.txt" +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" pull compat.md "$CAT_RESULT" +if [[ "$(cat "$CAT_RESULT")" == "compat-content" ]]; then + assert_pass "Compatibility mode works (omitted vault-path)" +else + assert_fail "Compatibility mode failed to sync file into DB" +fi + # ───────────────────────────────────────────────────────────────────────────── # Summary # ───────────────────────────────────────────────────────────────────────────── diff --git a/src/lib b/src/lib index e4380cc..57fb114 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit e4380ccd000278ed99c3de767f2e872087cca1db +Subproject commit 57fb114d27bd9edf477c173a301a9dbf87d5bfd4 diff --git a/updates.md b/updates.md index 576f123..c2ab3de 100644 --- a/updates.md +++ b/updates.md @@ -3,6 +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. +## Untagged (29th April, 2026) + +### Fixed (CLI) + +- `ls` and `mirror` commands now provide informative feedback when no documents are found or filters skip all files, resolving the issue where they would exit silently (#860). + - Improved the clarity of CLI command logs by including the total count of processed items. +- The command-line argument `vault` has been renamed to a more appropriate name, `databaseDir`. +- The `mirror` command now accepts a `vault` directory, which specifies the location where the actual files are stored. For compatibility reasons, the previous behaviour is still supported. + ## 0.25.59 ### Fixed From 3737eacffd4eee3b8e28d95ecbfade328dfeec19 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 29 Apr 2026 12:39:42 +0900 Subject: [PATCH 148/339] Fix readme Co-authored-by: Copilot --- src/apps/cli/README.md | 26 +++++++++++++------------ src/apps/cli/main.ts | 43 +++++++++++++++++++++--------------------- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/apps/cli/README.md b/src/apps/cli/README.md index 02dba18..ec4a772 100644 --- a/src/apps/cli/README.md +++ b/src/apps/cli/README.md @@ -61,8 +61,8 @@ lsync [database-path] [command] [args...] ### Commands - `sync`: Run one replication cycle with the remote CouchDB. -- `mirror [vault-path]`: Bidirectional sync between the local database and a local directory (**the actual vault**). - - If `vault-path` is provided, the CLI will synchronise the database with files in that directory. +- `mirror [database-path] [vault-path]`: Bidirectional sync between the local database and a local directory (**the actual vault**). + - If `vault-path` is provided, the CLI will synchronise the database with files in the vault directory. - If `vault-path` is omitted, it defaults to `database-path` (compatibility mode). - Use this command to keep your local `.md` files in sync with the database. - `ls [prefix]`: List files currently stored in the local database. @@ -229,7 +229,8 @@ The CLI uses the same settings format as the Obsidian plugin. Create a `.livesyn ``` Usage: - livesync-cli [database-path] [options] [command] [command-args] + livesync-cli [options] [command-args] + livesync-cli init-settings [path] Arguments: database-path Path to the local database directory (required except for init-settings) @@ -238,7 +239,8 @@ Options: --settings, -s Path to settings file (default: .livesync/settings.json in local database directory) --force, -f Overwrite existing file on init-settings --verbose, -v Enable verbose logging - --help, -h Show this help message + --debug, -d Enable debug logging (includes verbose) + --help, -h Show help message Commands: init-settings [path] Create settings JSON from DEFAULT_SETTINGS @@ -248,16 +250,16 @@ Commands: p2p-host Start P2P host mode and wait until interrupted (Ctrl+C) push Push local file into local database path pull Pull file from local database into local file - pull-rev Pull specific revision into local file + pull-rev Pull specific revision into local file setup Apply setup URI to settings file - put Read text from standard input and write to local database - cat Write latest file content from local database to standard output - cat-rev Write specific revision content from local database to standard output + put Read text from standard input and write to local database path + cat Write latest file content from local database to standard output + cat-rev Write specific revision content from local database to standard output ls [prefix] List files as pathsizemtimerevision[*] - info Show file metadata including current and past revisions, conflicts, and chunk list - rm Mark file as deleted in local database - resolve Resolve conflict by keeping the specified revision - mirror Mirror local file into local database. + info Show file metadata including current and past revisions, conflicts, and chunk list + rm Mark file as deleted in local database + resolve Resolve conflict by keeping the specified revision + mirror [vaultPath] Mirror database contents to the local file system (vaultPath defaults to database-path) ``` Run via npm script: diff --git a/src/apps/cli/main.ts b/src/apps/cli/main.ts index 29a6ec4..97483d5 100644 --- a/src/apps/cli/main.ts +++ b/src/apps/cli/main.ts @@ -36,14 +36,15 @@ function printHelp(): void { Self-hosted LiveSync CLI Usage: - livesync-cli [database-path] [options] [command] [command-args] + livesync-cli [options] [command-args] + livesync-cli init-settings [path] Arguments: - database-path Path to the local database directory (required) + database-path Path to the local database directory Commands: sync Run one replication cycle and exit - p2p-peers Show discovered peers as [peer] + p2p-peers Show discovered peers as [peer]\t\t p2p-sync Sync with the specified peer-id or peer-name p2p-host Start P2P host mode and wait until interrupted @@ -54,29 +55,29 @@ Commands: put Read UTF-8 content from stdin and write to local database path cat Read file from local database and write to stdout cat-rev Read file at specific revision and write to stdout - ls [prefix] List DB files as pathsizemtimerevision[*] + ls [prefix] List DB files as path\tsize\tmtime\trevision[*] info Show detailed metadata for a file (ID, revision, conflicts, chunks) 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) 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 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 `); } From faefa80cbd5576d94b237fc0d618544622b72025 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 29 Apr 2026 12:40:40 +0900 Subject: [PATCH 149/339] Fix again --- src/apps/cli/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/cli/README.md b/src/apps/cli/README.md index ec4a772..228dbb7 100644 --- a/src/apps/cli/README.md +++ b/src/apps/cli/README.md @@ -61,7 +61,7 @@ lsync [database-path] [command] [args...] ### Commands - `sync`: Run one replication cycle with the remote CouchDB. -- `mirror [database-path] [vault-path]`: Bidirectional sync between the local database and a local directory (**the actual vault**). +- `mirror [vault-path]`: Bidirectional sync between the local database and a local directory (**the actual vault**). - If `vault-path` is provided, the CLI will synchronise the database with files in the vault directory. - If `vault-path` is omitted, it defaults to `database-path` (compatibility mode). - Use this command to keep your local `.md` files in sync with the database. From 1aa7c45794896f53f41b4e04151108d4041e6c21 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 29 Apr 2026 12:55:34 +0900 Subject: [PATCH 150/339] Fix the readme Co-authored-by: Copilot --- src/apps/cli/README.md | 124 ++++++++++++++++++++++++++++------------- 1 file changed, 85 insertions(+), 39 deletions(-) diff --git a/src/apps/cli/README.md b/src/apps/cli/README.md index 228dbb7..b3272c6 100644 --- a/src/apps/cli/README.md +++ b/src/apps/cli/README.md @@ -49,10 +49,14 @@ CLI Main The CLI operates on a **database directory** which contains PouchDB data and settings. +> [!NOTE] +> `livesync-cli` is the alias for the CLI executable. Please replace with the actual command of your installation (e.g. `npm run --silent cli --` or `docker run ...`). + ```bash -lsync [database-path] [command] [args...] +livesync-cli [database-path] [command] [args...] ``` + ### Arguments - `database-path`: Path to the directory where `.livesync` folder and `settings.json` are (or will be) located. @@ -76,17 +80,38 @@ lsync [database-path] [command] [args...] ```bash # Basic sync with remote -lsync ./my-db sync +livesync-cli ./my-db sync # Mirroring to your actual Obsidian vault -lsync ./my-db mirror /path/to/obsidian-vault +livesync-cli ./my-db mirror /path/to/obsidian-vault # Manual file operations -lsync ./my-db push ./note.md folder/note.md -lsync ./my-db pull folder/note.md ./note.md +livesync-cli ./my-db push ./note.md folder/note.md +livesync-cli ./my-db pull folder/note.md ./note.md ``` -## Docker +## Installation + +### Build from source + +```bash +# Install dependencies (ensure you are in repository root directory, not src/apps/cli) +# due to shared dependencies with webapp and main library +npm install +# Build the project (ensure you are in `src/apps/cli` directory) +npm run build +``` + +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: @@ -109,7 +134,7 @@ docker run --rm -v /path/to/your/db:/data livesync-cli ls The database directory is mounted at `/data` by default. Override with `-e LIVESYNC_DB_PATH=/other/path`. -### P2P (WebRTC) and Docker networking +#### P2P (WebRTC) and Docker networking The P2P replicator (`p2p-host`, `p2p-sync`, `p2p-peers`) uses WebRTC and generates three kinds of ICE candidates. The default Docker bridge network affects which @@ -128,6 +153,8 @@ advertised as the `host` candidate: docker run --rm --network host -v /path/to/your/vault:/data livesync-cli p2p-host ``` +Note: also fix the alias to include `--network host` if you want to use `livesync-cli` for P2P commands. + > `--network host` is not available on Docker Desktop for macOS or Windows. **LAN P2P on macOS / Windows Docker Desktop** — configure a TURN server in the @@ -140,16 +167,35 @@ candidate carries the host's public IP and peers can connect normally. **CouchDB sync only (no P2P)** — no special network configuration is required. -## Installation + +### Adding `livesync-cli` alias + +To use the `livesync-cli` command globally, you can add an alias to your shell configuration file (e.g., `.zshrc` or `.bashrc`). + +If you are using `npm run`, add the following line: ```bash -# Install dependencies (ensure you are in repository root directory, not src/apps/cli) -# due to shared dependencies with webapp and main library -npm install -# Build the project (ensure you are in `src/apps/cli` directory) -npm run build +alias livesync-cli='npm run --silent --prefix /path/to/repository/src/apps/cli cli --' +# or +alias livesync-cli="npm run --silent --prefix $PWD cli --" ``` +Alternatively, if you want to use the built executable directly: + +```bash +alias livesync-cli='node /path/to/repository/src/apps/cli/dist/index.cjs' +or +alias livesync-cli="node $PWD/dist/index.cjs" +``` + +If you prefer using Docker: + +```bash +alias livesync-cli='docker run --rm -v /path/to/your/db:/data livesync-cli' +``` + +After adding the alias, restart your shell or run `source ~/.zshrc` (or `.bashrc`). + ## Usage ### Basic Usage @@ -158,43 +204,43 @@ As you know, the CLI is designed to be used in a headless environment. Hence all ```bash # Sync local database with CouchDB (no files will be changed). -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json sync +livesync-cli /path/to/your-local-database --settings /path/to/settings.json sync # Push files to local database -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json push /your/storage/file.md /vault/path/file.md +livesync-cli /path/to/your-local-database --settings /path/to/settings.json push /your/storage/file.md /vault/path/file.md # Pull files from local database -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json pull /vault/path/file.md /your/storage/file.md +livesync-cli /path/to/your-local-database --settings /path/to/settings.json pull /vault/path/file.md /your/storage/file.md # Verbose logging -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json --verbose +livesync-cli /path/to/your-local-database --settings /path/to/settings.json --verbose # Apply setup URI to settings file (settings only; does not run synchronisation) -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json setup "obsidian://setuplivesync?settings=..." +livesync-cli /path/to/your-local-database --settings /path/to/settings.json setup "obsidian://setuplivesync?settings=..." # Put text from stdin into local database -echo "Hello from stdin" | npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json put /vault/path/file.md +echo "Hello from stdin" | livesync-cli /path/to/your-local-database --settings /path/to/settings.json put /vault/path/file.md # Output a file from local database to stdout -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json cat /vault/path/file.md +livesync-cli /path/to/your-local-database --settings /path/to/settings.json cat /vault/path/file.md # Output a specific revision of a file from local database -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json cat-rev /vault/path/file.md 3-abcdef +livesync-cli /path/to/your-local-database --settings /path/to/settings.json cat-rev /vault/path/file.md 3-abcdef # Pull a specific revision of a file from local database to local storage -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json pull-rev /vault/path/file.md /your/storage/file.old.md 3-abcdef +livesync-cli /path/to/your-local-database --settings /path/to/settings.json pull-rev /vault/path/file.md /your/storage/file.old.md 3-abcdef # List files in local database -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json ls /vault/path/ +livesync-cli /path/to/your-local-database --settings /path/to/settings.json ls /vault/path/ # Show metadata for a file in local database -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json info /vault/path/file.md +livesync-cli /path/to/your-local-database --settings /path/to/settings.json info /vault/path/file.md # Mark a file as deleted in local database -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json rm /vault/path/file.md +livesync-cli /path/to/your-local-database --settings /path/to/settings.json rm /vault/path/file.md # Resolve conflict by keeping a specific revision -npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json resolve /vault/path/file.md 3-abcdef +livesync-cli /path/to/your-local-database --settings /path/to/settings.json resolve /vault/path/file.md 3-abcdef ``` ### Configuration @@ -362,9 +408,9 @@ Note: `mirror` does not respect file deletions. If a file is deleted in storage, Create default settings, apply a setup URI, then run one sync cycle. ```bash -npm run --silent cli -- init-settings /data/livesync-settings.json -printf '%s\n' "$SETUP_PASSPHRASE" | npm run --silent cli -- /data/vault --settings /data/livesync-settings.json setup "$SETUP_URI" -npm run --silent cli -- /data/vault --settings /data/livesync-settings.json sync +livesync-cli -- init-settings /data/livesync-settings.json +printf '%s\n' "$SETUP_PASSPHRASE" | livesync-cli -- /data/vault --settings /data/livesync-settings.json setup "$SETUP_URI" +livesync-cli -- /data/vault --settings /data/livesync-settings.json sync ``` ### 2. Scripted import and export @@ -372,8 +418,8 @@ npm run --silent cli -- /data/vault --settings /data/livesync-settings.json sync Push local files into the database from automation, and pull them back for export or backup. ```bash -npm run --silent cli -- /data/vault --settings /data/livesync-settings.json push ./note.md notes/note.md -npm run --silent cli -- /data/vault --settings /data/livesync-settings.json pull notes/note.md ./exports/note.md +livesync-cli -- /data/vault --settings /data/livesync-settings.json push ./note.md notes/note.md +livesync-cli -- /data/vault --settings /data/livesync-settings.json pull notes/note.md ./exports/note.md ``` ### 3. Revision inspection and restore @@ -381,9 +427,9 @@ npm run --silent cli -- /data/vault --settings /data/livesync-settings.json pull List metadata, find an older revision, then restore it by content (`cat-rev`) or file output (`pull-rev`). ```bash -npm run --silent cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md -npm run --silent cli -- /data/vault --settings /data/livesync-settings.json cat-rev notes/note.md 3-abcdef -npm run --silent cli -- /data/vault --settings /data/livesync-settings.json pull-rev notes/note.md ./restore/note.old.md 3-abcdef +livesync-cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md +livesync-cli -- /data/vault --settings /data/livesync-settings.json cat-rev notes/note.md 3-abcdef +livesync-cli -- /data/vault --settings /data/livesync-settings.json pull-rev notes/note.md ./restore/note.old.md 3-abcdef ``` ### 4. Conflict and cleanup workflow @@ -391,9 +437,9 @@ npm run --silent cli -- /data/vault --settings /data/livesync-settings.json pull Inspect conflicted revisions, resolve by keeping one revision, then delete obsolete files. ```bash -npm run --silent cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md -npm run --silent cli -- /data/vault --settings /data/livesync-settings.json resolve notes/note.md 3-abcdef -npm run --silent cli -- /data/vault --settings /data/livesync-settings.json rm notes/obsolete.md +livesync-cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md +livesync-cli -- /data/vault --settings /data/livesync-settings.json resolve notes/note.md 3-abcdef +livesync-cli -- /data/vault --settings /data/livesync-settings.json rm notes/obsolete.md ``` ### 5. CI smoke test for content round-trip @@ -401,8 +447,8 @@ npm run --silent cli -- /data/vault --settings /data/livesync-settings.json rm n Validate that `put`/`cat` is behaving as expected in a pipeline. ```bash -echo "hello-ci" | npm run --silent cli -- /data/vault --settings /data/livesync-settings.json put ci/test.md -npm run --silent cli -- /data/vault --settings /data/livesync-settings.json cat ci/test.md +echo "hello-ci" | livesync-cli -- /data/vault --settings /data/livesync-settings.json put ci/test.md +livesync-cli -- /data/vault --settings /data/livesync-settings.json cat ci/test.md ``` ## Development From c2f696d0a4a3456218e19ecc5939fe62fb8666cf Mon Sep 17 00:00:00 2001 From: Fabio Date: Sun, 26 Apr 2026 14:20:39 +0000 Subject: [PATCH 151/339] chore: attribute `version` is obsolete --- docker-compose.traefik.yml | 1 - docs/setup_own_server.md | 1 - docs/setup_own_server_cn.md | 1 - 3 files changed, 3 deletions(-) diff --git a/docker-compose.traefik.yml b/docker-compose.traefik.yml index 8b7ab90..453a49b 100644 --- a/docker-compose.traefik.yml +++ b/docker-compose.traefik.yml @@ -1,7 +1,6 @@ # For details and other explanations about this file refer to: # https://github.com/vrtmrz/obsidian-livesync/blob/main/docs/setup_own_server.md#traefik -version: "2.1" services: couchdb: image: couchdb:latest diff --git a/docs/setup_own_server.md b/docs/setup_own_server.md index b444125..40dd20d 100644 --- a/docs/setup_own_server.md +++ b/docs/setup_own_server.md @@ -230,7 +230,6 @@ And, be sure to check the server log and be careful of malicious access. If you are using Traefik, this [docker-compose.yml](https://github.com/vrtmrz/obsidian-livesync/blob/main/docker-compose.traefik.yml) file (also pasted below) has all the right CORS parameters set. It assumes you have an external network called `proxy`. ```yaml -version: "2.1" services: couchdb: image: couchdb:latest diff --git a/docs/setup_own_server_cn.md b/docs/setup_own_server_cn.md index 37c1d0c..1c88ed6 100644 --- a/docs/setup_own_server_cn.md +++ b/docs/setup_own_server_cn.md @@ -71,7 +71,6 @@ obsidian-livesync 可以参照以下内容编辑 `docker-compose.yml`: ```yaml -version: "2.1" services: couchdb: image: couchdb From cc466a4b3c4af6421cd8091ef490301ad94ec87b Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 29 Apr 2026 18:37:44 +0900 Subject: [PATCH 152/339] ### Fixed - Now larger settings can be exported and imported via QR code without issues. (#595) - Fixed some errors during serialisation and deserialisation of the settings, which caused issues in some cases when importing/exporting settings via QR code. Co-authored-by: Copilot --- aggregator.html | 92 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lib | 2 +- 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 aggregator.html diff --git a/aggregator.html b/aggregator.html new file mode 100644 index 0000000..c3c422b --- /dev/null +++ b/aggregator.html @@ -0,0 +1,92 @@ + + + + + + Self-hosted LiveSync Setup QR Aggregator + + + +
+

LiveSync Setup

+
+

Checking hash data...

+
+
+ + + + diff --git a/src/lib b/src/lib index 57fb114..91b5981 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 57fb114d27bd9edf477c173a301a9dbf87d5bfd4 +Subproject commit 91b59812191dc8e190658b4110eedd4dca5e1803 From 81d82243300ce9b520717d5f54fbe86d115f5fdc Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 29 Apr 2026 18:39:48 +0900 Subject: [PATCH 153/339] bump Co-authored-by: Copilot --- manifest.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- updates.md | 13 ++++++++++++- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/manifest.json b/manifest.json index 1c1e14c..cb9b9f1 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-livesync", "name": "Self-hosted LiveSync", - "version": "0.25.59", + "version": "0.25.60", "minAppVersion": "0.9.12", "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 62114d8..0f1f682 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "obsidian-livesync", - "version": "0.25.59", + "version": "0.25.60", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "obsidian-livesync", - "version": "0.25.59", + "version": "0.25.60", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.808.0", diff --git a/package.json b/package.json index b4d2478..479780a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-livesync", - "version": "0.25.59", + "version": "0.25.60", "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 c2ab3de..0d65ee5 100644 --- a/updates.md +++ b/updates.md @@ -3,7 +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. -## Untagged (29th April, 2026) +## 0.25.60 + +29th April, 2026 + +### Fixed + +- Now larger settings can be exported and imported via QR code without issues. (#595) + - When the settings data exceeds the QR code capacity, it is now split into multiple QR codes. + - These QR codes are reassembled by the aggregator page, which collects the split data and reconstructs the original settings. + - Aggregator page is available at `https://vrtmrz.github.io/obsidian-livesync/aggregator.html`, and this file is also included in the repository. + - We will not send the settings data to any server. The QR code data is generated and processed entirely on the client side, ensuring that your settings remain private and secure. HOWEVER, please be careful your network environment. +- Fixed some errors during serialisation and deserialisation of the settings, which caused issues in some cases when importing/exporting settings via QR code. ### Fixed (CLI) From fa7ef623020afd7d83382d92bad0e8a1d232f714 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 29 Apr 2026 18:42:54 +0900 Subject: [PATCH 154/339] Fix: adjusting help Co-authored-by: Copilot --- src/apps/cli/main.unit.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/cli/main.unit.spec.ts b/src/apps/cli/main.unit.spec.ts index 83c3177..4c35ae9 100644 --- a/src/apps/cli/main.unit.spec.ts +++ b/src/apps/cli/main.unit.spec.ts @@ -56,7 +56,7 @@ describe("CLI parseArgs", () => { expect(stdout).toHaveBeenCalled(); const combined = stdout.mock.calls.flat().join("\n"); expect(combined).toContain("Usage:"); - expect(combined).toContain("livesync-cli [database-path]"); + expect(combined).toContain("livesync-cli [options] [command-args]"); }); it("parses p2p-peers command and timeout", () => { From f9294446ba5ae21a099ed5a820eaab0f9a021ae1 Mon Sep 17 00:00:00 2001 From: SeleiXi Date: Sat, 2 May 2026 22:18:43 +0800 Subject: [PATCH 155/339] feat: add diff block navigation to Document History modal Add prev/next buttons to jump between diff blocks in the Document History view. Includes position indicator and auto-scroll with visual focus highlighting. --- .../DocumentHistory/DocumentHistoryModal.ts | 120 +++++++++++++++--- styles.css | 41 ++++++ 2 files changed, 142 insertions(+), 19 deletions(-) diff --git a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts index f28f263..0056832 100644 --- a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts +++ b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts @@ -66,6 +66,11 @@ export class DocumentHistoryModal extends Modal { currentDeleted = false; initialRev?: string; + // Diff navigation state + currentDiffIndex = -1; + diffNavContainer!: HTMLDivElement; + diffNavIndicator!: HTMLSpanElement; + constructor( app: App, core: LiveSyncBaseCore, @@ -216,6 +221,61 @@ export class DocumentHistoryModal extends Modal { this.contentView.innerHTML = (this.currentDeleted ? "(At this revision, the file has been deleted)\n" : "") + result; } + // Reset diff navigation after content changes + this.resetDiffNavigation(); + } + + /** + * Navigate to the previous or next diff block in the content view. + * Only effective when diff highlighting is enabled. + */ + navigateDiff(direction: "prev" | "next") { + const diffElements = this.contentView.querySelectorAll(".history-added, .history-deleted"); + if (diffElements.length === 0) return; + + // Remove previous focus highlight + const prevFocused = this.contentView.querySelector(".diff-focused"); + if (prevFocused) { + prevFocused.classList.remove("diff-focused"); + } + + if (direction === "next") { + this.currentDiffIndex = (this.currentDiffIndex + 1) % diffElements.length; + } else { + this.currentDiffIndex = + this.currentDiffIndex <= 0 ? diffElements.length - 1 : this.currentDiffIndex - 1; + } + + const target = diffElements[this.currentDiffIndex]; + target.classList.add("diff-focused"); + target.scrollIntoView({ behavior: "smooth", block: "center" }); + + this.diffNavIndicator.textContent = `${this.currentDiffIndex + 1}/${diffElements.length}`; + } + + /** + * Reset the diff navigation index and update the indicator. + */ + resetDiffNavigation() { + this.currentDiffIndex = -1; + if (this.diffNavIndicator) { + if (this.showDiff) { + const diffElements = this.contentView.querySelectorAll(".history-added, .history-deleted"); + this.diffNavIndicator.textContent = diffElements.length > 0 ? `0/${diffElements.length}` : "\u2014"; + } else { + this.diffNavIndicator.textContent = "\u2014"; + } + } + this.updateDiffNavVisibility(); + } + + /** + * Show or hide the diff navigation buttons based on the showDiff state. + */ + updateDiffNavVisibility() { + if (this.diffNavContainer) { + this.diffNavContainer.style.display = this.showDiff ? "flex" : "none"; + } } override onOpen() { @@ -236,25 +296,47 @@ export class DocumentHistoryModal extends Modal { void scheduleOnceIfDuplicated("loadRevs", () => this.loadRevs()); }); }); - contentEl - .createDiv("", (e) => { - e.createEl("label", {}, (label) => { - label.appendChild( - createEl("input", { type: "checkbox" }, (checkbox) => { - if (this.showDiff) { - checkbox.checked = true; - } - checkbox.addEventListener("input", (evt: any) => { - this.showDiff = checkbox.checked; - localStorage.setItem("ols-history-highlightdiff", this.showDiff == true ? "1" : ""); - void scheduleOnceIfDuplicated("loadRevs", () => this.loadRevs()); - }); - }) - ); - label.appendText("Highlight diff"); - }); - }) - .addClass("op-info"); + const diffOptionsRow = contentEl.createDiv(""); + diffOptionsRow.addClass("op-info"); + diffOptionsRow.addClass("diff-options-row"); + + diffOptionsRow.createEl("label", {}, (label) => { + label.appendChild( + createEl("input", { type: "checkbox" }, (checkbox) => { + if (this.showDiff) { + checkbox.checked = true; + } + checkbox.addEventListener("input", (evt: any) => { + this.showDiff = checkbox.checked; + localStorage.setItem("ols-history-highlightdiff", this.showDiff == true ? "1" : ""); + this.updateDiffNavVisibility(); + void scheduleOnceIfDuplicated("loadRevs", () => this.loadRevs()); + }); + }) + ); + label.appendText("Highlight diff"); + }); + + // Diff navigation buttons + this.diffNavContainer = diffOptionsRow.createDiv(""); + this.diffNavContainer.addClass("diff-nav"); + this.diffNavContainer.style.display = this.showDiff ? "flex" : "none"; + + this.diffNavContainer.createEl("button", { text: "\u25B2 Prev" }, (e) => { + e.addClass("diff-nav-btn"); + e.addEventListener("click", () => { + this.navigateDiff("prev"); + }); + }); + this.diffNavContainer.createEl("button", { text: "\u25BC Next" }, (e) => { + e.addClass("diff-nav-btn"); + e.addEventListener("click", () => { + this.navigateDiff("next"); + }); + }); + this.diffNavIndicator = this.diffNavContainer.createEl("span", { text: "\u2014" }); + this.diffNavIndicator.addClass("diff-nav-indicator"); + this.info = contentEl.createDiv(""); this.info.addClass("op-info"); fireAndForget(async () => await this.loadFile(this.initialRev)); diff --git a/styles.css b/styles.css index a3b1792..59ad096 100644 --- a/styles.css +++ b/styles.css @@ -484,4 +484,45 @@ div.workspace-leaf-content[data-type=bases] .livesync-status { white-space: pre-wrap; word-break: break-all; +} + +/* Diff navigation */ +.diff-options-row { + display: flex; + align-items: center; + gap: 8px; +} + +.diff-nav { + display: flex; + align-items: center; + gap: 4px; + margin-left: auto; +} + +.diff-nav-btn { + padding: 2px 8px; + font-size: 0.85em; + cursor: pointer; + border: 1px solid var(--background-modifier-border); + border-radius: 4px; + background-color: var(--background-secondary); + color: var(--text-normal); +} + +.diff-nav-btn:hover { + background-color: var(--background-modifier-hover); +} + +.diff-nav-indicator { + font-size: 0.85em; + color: var(--text-muted); + min-width: 3em; + text-align: center; +} + +.diff-focused { + outline: 2px solid var(--interactive-accent); + outline-offset: 1px; + border-radius: 2px; } \ No newline at end of file From 7a4b76a550ab48f8492e0c5ca7bcced897e87d90 Mon Sep 17 00:00:00 2001 From: bori Date: Sat, 2 May 2026 18:51:07 +0300 Subject: [PATCH 156/339] added documentaion and a hook build script to make onbaording easier --- devs.md | 3 ++ src/apps/cli/README.md | 45 +++++++++++++++--------- src/apps/cli/package.json | 1 + src/apps/cli/scripts/check-submodule.mjs | 36 +++++++++++++++++++ 4 files changed, 68 insertions(+), 17 deletions(-) create mode 100644 src/apps/cli/scripts/check-submodule.mjs diff --git a/devs.md b/devs.md index d44a68b..d9d9ef1 100644 --- a/devs.md +++ b/devs.md @@ -63,6 +63,9 @@ npm test # Run vitest tests (requires Docker services) ### Environment Setup +- Clone with submodules: `git clone --recurse-submodules ` +- If you already cloned without them, run: `git submodule update --init --recursive` +- The shared common library is provided by the `src/lib` submodule, and builds will fail if it is missing - Create `.env` file with `PATHS_TEST_INSTALL` pointing to test vault plug-in directories (`:` separated on Unix, `;` on Windows) - Development builds auto-copy to these paths on build diff --git a/src/apps/cli/README.md b/src/apps/cli/README.md index b3272c6..6bd0082 100644 --- a/src/apps/cli/README.md +++ b/src/apps/cli/README.md @@ -92,28 +92,39 @@ livesync-cli ./my-db pull folder/note.md ./note.md ## Installation -### Build from source +### 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. -```bash -# Install dependencies (ensure you are in repository root directory, not src/apps/cli) -# due to shared dependencies with webapp and main library -npm install -# Build the project (ensure you are in `src/apps/cli` directory) -npm run build -``` - -Run the CLI: - -```bash -# Run with npm script (from repository root) -npm run --silent cli -- [database-path] [command] [args...] +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 . diff --git a/src/apps/cli/package.json b/src/apps/cli/package.json index 4deaade..18768a9 100644 --- a/src/apps/cli/package.json +++ b/src/apps/cli/package.json @@ -6,6 +6,7 @@ "type": "module", "scripts": { "dev": "vite", + "prebuild": "node scripts/check-submodule.mjs", "build": "vite build", "preview": "vite preview", "cli": "node dist/index.cjs", diff --git a/src/apps/cli/scripts/check-submodule.mjs b/src/apps/cli/scripts/check-submodule.mjs new file mode 100644 index 0000000..6235507 --- /dev/null +++ b/src/apps/cli/scripts/check-submodule.mjs @@ -0,0 +1,36 @@ +import fs from "node:fs"; +import path from "node:path"; +import process from "node:process"; + +const cliDir = process.cwd(); +const repoRoot = path.resolve(cliDir, "../../.."); +const requiredFiles = [ + path.join(repoRoot, "src/lib/src/common/types.ts"), +]; + +const missingFiles = requiredFiles.filter((filePath) => !fs.existsSync(filePath)); + +if (missingFiles.length === 0) { + process.exit(0); +} + +console.error("[CLI Build Error] Required shared sources were not found."); +console.error("This repository uses Git submodules, and the CLI depends on src/lib."); +console.error(""); +console.error("Missing file(s):"); +for (const filePath of missingFiles) { + console.error(` - ${path.relative(repoRoot, filePath)}`); +} +console.error(""); +console.error("Initialize submodules, then retry the CLI build:"); +console.error(" git submodule update --init --recursive"); +console.error(""); +console.error("For a fresh clone, prefer:"); +console.error(" git clone --recurse-submodules "); +console.error(""); +console.error("Then run:"); +console.error(" npm install"); +console.error(" cd src/apps/cli"); +console.error(" npm run build"); + +process.exit(1); From 39e82cc8a1e17fccdf1b729fd1ad397f6b90a015 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 6 May 2026 21:56:13 +0900 Subject: [PATCH 157/339] Fixed: Fix timing issue during test --- src/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib b/src/lib index 91b5981..9753055 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 91b59812191dc8e190658b4110eedd4dca5e1803 +Subproject commit 97530553a63dc206ea3fb7ef60721cabda6c74cc From cc3d30dbcf2b92b580ffbdfa6603a98dec0ba3df Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 7 May 2026 11:06:12 +0100 Subject: [PATCH 158/339] feat(tests): add Deno-based tests for checking CLI functionality in the same-codebase between platforms. --- src/apps/cli/testdeno/CONTRIBUTING_TESTS.md | 150 +++++ src/apps/cli/testdeno/deno.json | 22 + src/apps/cli/testdeno/deno.lock | 31 + .../cli/testdeno/helpers/backgroundCli.ts | 112 ++++ src/apps/cli/testdeno/helpers/cli.ts | 231 ++++++++ src/apps/cli/testdeno/helpers/docker.ts | 530 ++++++++++++++++++ src/apps/cli/testdeno/helpers/env.ts | 26 + src/apps/cli/testdeno/helpers/p2p.ts | 52 ++ src/apps/cli/testdeno/helpers/settings.ts | 205 +++++++ src/apps/cli/testdeno/helpers/temp.ts | 33 ++ .../testdeno/test-e2e-two-vaults-couchdb.ts | 279 +++++++++ .../testdeno/test-e2e-two-vaults-matrix.ts | 20 + src/apps/cli/testdeno/test-mirror.ts | 196 +++++++ src/apps/cli/testdeno/test-p2p-host.ts | 40 ++ .../testdeno/test-p2p-peers-local-relay.ts | 42 ++ src/apps/cli/testdeno/test-p2p-sync.ts | 59 ++ .../testdeno/test-p2p-three-nodes-conflict.ts | 118 ++++ .../test-p2p-upload-download-repro.ts | 111 ++++ src/apps/cli/testdeno/test-push-pull.ts | 64 +++ src/apps/cli/testdeno/test-setup-put-cat.ts | 214 +++++++ .../cli/testdeno/test-sync-locked-remote.ts | 97 ++++ .../testdeno/test-sync-two-local-databases.ts | 287 ++++++++++ src/apps/cli/testdeno/test_dev_deno.md | 292 ++++++++++ 23 files changed, 3211 insertions(+) create mode 100644 src/apps/cli/testdeno/CONTRIBUTING_TESTS.md create mode 100644 src/apps/cli/testdeno/deno.json create mode 100644 src/apps/cli/testdeno/deno.lock create mode 100644 src/apps/cli/testdeno/helpers/backgroundCli.ts create mode 100644 src/apps/cli/testdeno/helpers/cli.ts create mode 100644 src/apps/cli/testdeno/helpers/docker.ts create mode 100644 src/apps/cli/testdeno/helpers/env.ts create mode 100644 src/apps/cli/testdeno/helpers/p2p.ts create mode 100644 src/apps/cli/testdeno/helpers/settings.ts create mode 100644 src/apps/cli/testdeno/helpers/temp.ts create mode 100644 src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts create mode 100644 src/apps/cli/testdeno/test-e2e-two-vaults-matrix.ts create mode 100644 src/apps/cli/testdeno/test-mirror.ts create mode 100644 src/apps/cli/testdeno/test-p2p-host.ts create mode 100644 src/apps/cli/testdeno/test-p2p-peers-local-relay.ts create mode 100644 src/apps/cli/testdeno/test-p2p-sync.ts create mode 100644 src/apps/cli/testdeno/test-p2p-three-nodes-conflict.ts create mode 100644 src/apps/cli/testdeno/test-p2p-upload-download-repro.ts create mode 100644 src/apps/cli/testdeno/test-push-pull.ts create mode 100644 src/apps/cli/testdeno/test-setup-put-cat.ts create mode 100644 src/apps/cli/testdeno/test-sync-locked-remote.ts create mode 100644 src/apps/cli/testdeno/test-sync-two-local-databases.ts create mode 100644 src/apps/cli/testdeno/test_dev_deno.md diff --git a/src/apps/cli/testdeno/CONTRIBUTING_TESTS.md b/src/apps/cli/testdeno/CONTRIBUTING_TESTS.md new file mode 100644 index 0000000..b244807 --- /dev/null +++ b/src/apps/cli/testdeno/CONTRIBUTING_TESTS.md @@ -0,0 +1,150 @@ +# Writing CLI Tests on Deno + +This guide explains how to add or update tests under `src/apps/cli/testdeno/`. +Note that new tests should be added to the Deno suite rather than the existing bash suite due to the cross-platform execution and TypeScript benefits. + +## Scope + +The Deno suite is designed for cross-platform execution, with a strong focus on Windows compatibility while keeping behaviour equivalent to existing bash tests. + +## Principles + +- Keep one scenario per file when practical. +- Reuse helpers from `helpers/` rather than duplicating process, Docker, or settings logic. +- Prefer deterministic data over random inputs unless randomness is explicitly required. +- Ensure every test can clean up automatically. +- Keep assertions actionable with clear failure messages. + +## Directory structure + +``` +src/apps/cli/testdeno/ + helpers/ + backgroundCli.ts + cli.ts + docker.ts + env.ts + p2p.ts + settings.ts + temp.ts + test-*.ts + deno.json +``` + +## Test file naming + +- Use `test-.ts`. +- Use names aligned with existing bash tests when porting, for example: + - `test-sync-locked-remote.ts` + - `test-p2p-sync.ts` + +## Core helper usage + +### Temporary workspace + +Use `TempDir` and `await using` so cleanup is automatic: + +```ts +await using workDir = await TempDir.create("livesync-cli-my-test"); +``` + +### CLI execution + +- `runCli(...)`: returns code and combined output. +- `runCliOrFail(...)`: throws on non-zero exit. +- `runCliWithInputOrFail(input, ...)`: for `put` and stdin-driven commands. + +### Settings + +- `initSettingsFile(...)`: creates a baseline settings file. +- `applyCouchdbSettings(...)`: applies CouchDB fields. +- `applyRemoteSyncSettings(...)`: applies remote and encryption fields. +- `applyP2pSettings(...)`: applies P2P fields. +- `applyP2pTestTweaks(...)`: enables P2P-only test profile. + +### Docker services + +- `startCouchdb(...)`, `stopCouchdb()` +- `startP2pRelay()`, `stopP2pRelay()` + +### P2P discovery + +- `discoverPeer(...)` +- `maybeStartLocalRelay(...)` +- `stopLocalRelayIfStarted(...)` + +### Background host process + +Use `startCliInBackground(...)` for long-running host mode such as `p2p-host`. + +## Recommended test structure + +1. Arrange +2. Act +3. Assert +4. Cleanup in `finally` + +Example skeleton: + +```ts +Deno.test("feature: behaviour", async () => { + await using workDir = await TempDir.create("example"); + // Arrange + + try { + // Act + + // Assert + } finally { + // Optional explicit cleanup + } +}); +``` + +## Reliability guidelines + +- Use explicit waits only when needed for eventual consistency. +- Re-run sync operations where the protocol is eventually consistent. +- For network-sensitive commands, use `LIVESYNC_CLI_RETRY` during debugging. +- Keep Docker container reuse disabled by default unless debugging. + +## Environment variables + +Common variables: + +- `LIVESYNC_DOCKER_MODE` +- `LIVESYNC_DOCKER_COMMAND` +- `LIVESYNC_TEST_TEE` +- `LIVESYNC_DOCKER_TEE` +- `LIVESYNC_CLI_DEBUG` +- `LIVESYNC_CLI_VERBOSE` +- `LIVESYNC_CLI_RETRY` +- `LIVESYNC_DEBUG_KEEP_DOCKER` + +P2P variables: + +- `RELAY` +- `ROOM_ID` +- `PASSPHRASE` +- `APP_ID` +- `PEERS_TIMEOUT` +- `SYNC_TIMEOUT` +- `USE_INTERNAL_RELAY` + +## Adding a new test task + +1. Add the test file under `src/apps/cli/testdeno/`. +2. Add a task in `src/apps/cli/testdeno/deno.json`. +3. Update `src/apps/cli/testdeno/test_dev_deno.md`. +4. Run the new task locally. + +## Validation checklist + +- The test passes on a clean workspace. +- The test does not leave persistent artefacts unless explicitly requested. +- Failure messages identify both expected and actual behaviour. +- The corresponding task is documented. + +## Out of scope for this suite + +- One-off reproduction scripts that are not intended as stable regression tests. diff --git a/src/apps/cli/testdeno/deno.json b/src/apps/cli/testdeno/deno.json new file mode 100644 index 0000000..18797ae --- /dev/null +++ b/src/apps/cli/testdeno/deno.json @@ -0,0 +1,22 @@ +{ + "tasks": { + "test": "deno test -A --no-check .", + "test:local": "deno test -A --no-check test-setup-put-cat.ts test-mirror.ts", + "test:push-pull": "deno test -A --no-check test-push-pull.ts", + "test:setup-put-cat": "deno test -A --no-check test-setup-put-cat.ts", + "test:mirror": "deno test -A --no-check test-mirror.ts", + "test:sync-two-local": "deno test -A --no-check test-sync-two-local-databases.ts", + "test:sync-locked-remote": "deno test -A --no-check test-sync-locked-remote.ts", + "test:p2p-host": "deno test -A --no-check test-p2p-host.ts", + "test:p2p-peers": "deno test -A --no-check test-p2p-peers-local-relay.ts", + "test:p2p-sync": "deno test -A --no-check test-p2p-sync.ts", + "test:p2p-three-nodes": "deno test -A --no-check test-p2p-three-nodes-conflict.ts", + "test:p2p-upload-download": "deno test -A --no-check test-p2p-upload-download-repro.ts", + "test:e2e-couchdb": "deno test -A --no-check test-e2e-two-vaults-couchdb.ts", + "test:e2e-matrix": "deno test -A --no-check test-e2e-two-vaults-matrix.ts" + }, + "imports": { + "@std/assert": "jsr:@std/assert@^1.0.13", + "@std/path": "jsr:@std/path@^1.0.9" + } +} diff --git a/src/apps/cli/testdeno/deno.lock b/src/apps/cli/testdeno/deno.lock new file mode 100644 index 0000000..a65fb54 --- /dev/null +++ b/src/apps/cli/testdeno/deno.lock @@ -0,0 +1,31 @@ +{ + "version": "5", + "specifiers": { + "jsr:@std/assert@^1.0.13": "1.0.19", + "jsr:@std/internal@^1.0.12": "1.0.12", + "jsr:@std/path@^1.0.9": "1.1.4" + }, + "jsr": { + "@std/assert@1.0.19": { + "integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e", + "dependencies": [ + "jsr:@std/internal" + ] + }, + "@std/internal@1.0.12": { + "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" + }, + "@std/path@1.1.4": { + "integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5", + "dependencies": [ + "jsr:@std/internal" + ] + } + }, + "workspace": { + "dependencies": [ + "jsr:@std/assert@^1.0.13", + "jsr:@std/path@^1.0.9" + ] + } +} diff --git a/src/apps/cli/testdeno/helpers/backgroundCli.ts b/src/apps/cli/testdeno/helpers/backgroundCli.ts new file mode 100644 index 0000000..1269d0f --- /dev/null +++ b/src/apps/cli/testdeno/helpers/backgroundCli.ts @@ -0,0 +1,112 @@ +import { CLI_DIR } from "./cli.ts"; +import { join } from "@std/path"; + +const CLI_DIST = join(CLI_DIR, "dist", "index.cjs"); +const VERBOSE_ENABLED = Deno.env.get("LIVESYNC_CLI_VERBOSE") === "1"; +const DEBUG_ENABLED = Deno.env.get("LIVESYNC_CLI_DEBUG") === "1"; + +function decorateArgs(args: string[]): string[] { + return DEBUG_ENABLED ? ["-d", ...args] : VERBOSE_ENABLED ? ["-v", ...args] : args; +} + +async function pump( + stream: ReadableStream, + sink: (text: string) => void, + teeTarget: WritableStream | null +): Promise { + const reader = stream.getReader(); + const writer = teeTarget?.getWriter(); + const dec = new TextDecoder(); + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + if (!value) continue; + sink(dec.decode(value, { stream: true })); + if (writer) { + await writer.write(value); + } + } + } finally { + if (writer) writer.releaseLock(); + reader.releaseLock(); + } +} + +export class BackgroundCliProcess { + #stdout = ""; + #stderr = ""; + #stdoutDone: Promise; + #stderrDone: Promise; + + constructor( + readonly child: Deno.ChildProcess, + readonly args: string[] + ) { + this.#stdoutDone = pump( + child.stdout, + (text) => { + this.#stdout += text; + }, + null + ); + this.#stderrDone = pump( + child.stderr, + (text) => { + this.#stderr += text; + }, + null + ); + } + + get stdout(): string { + return this.#stdout; + } + + get stderr(): string { + return this.#stderr; + } + + get combined(): string { + return this.#stdout + this.#stderr; + } + + async waitUntilContains(needle: string, timeoutMs = 15000): Promise { + const started = Date.now(); + while (Date.now() - started < timeoutMs) { + if (this.combined.includes(needle)) return; + const status = await Promise.race([ + this.child.status.then((s) => ({ type: "status" as const, status: s })), + new Promise<{ type: "tick" }>((resolve) => setTimeout(() => resolve({ type: "tick" }), 100)), + ]); + if (status.type === "status") { + throw new Error( + `Background CLI exited before '${needle}' appeared (code ${status.status.code})\n${this.combined}` + ); + } + } + throw new Error(`Timed out waiting for '${needle}'\n${this.combined}`); + } + + async stop(): Promise { + try { + this.child.kill("SIGTERM"); + } catch { + // ignore already-exited processes + } + const status = await this.child.status; + await Promise.all([this.#stdoutDone, this.#stderrDone]); + return status.code; + } +} + +export function startCliInBackground(...args: string[]): BackgroundCliProcess { + const child = new Deno.Command("node", { + args: [CLI_DIST, ...decorateArgs(args)], + cwd: CLI_DIR, + stdin: "null", + stdout: "piped", + stderr: "piped", + }).spawn(); + return new BackgroundCliProcess(child, args); +} diff --git a/src/apps/cli/testdeno/helpers/cli.ts b/src/apps/cli/testdeno/helpers/cli.ts new file mode 100644 index 0000000..8c78b52 --- /dev/null +++ b/src/apps/cli/testdeno/helpers/cli.ts @@ -0,0 +1,231 @@ +import { join } from "@std/path"; + +// --------------------------------------------------------------------------- +// Path resolution +// --------------------------------------------------------------------------- +// This file lives at: src/apps/cli/testdeno/helpers/cli.ts +// CLI root (src/apps/cli/) is two levels up. +// import.meta.dirname is available in Deno 1.40+ as an OS-native path string. +export const CLI_DIR: string = join(import.meta.dirname!, "..", ".."); +const CLI_DIST = join(CLI_DIR, "dist", "index.cjs"); + +// --------------------------------------------------------------------------- +// Result type +// --------------------------------------------------------------------------- +export interface CliResult { + stdout: string; + stderr: string; + /** stdout + stderr concatenated — useful for assertion messages. */ + combined: string; + code: number; +} + +const TEE_ENABLED = Deno.env.get("LIVESYNC_TEST_TEE") === "1"; +const VERBOSE_ENABLED = Deno.env.get("LIVESYNC_CLI_VERBOSE") === "1"; +const DEBUG_ENABLED = Deno.env.get("LIVESYNC_CLI_DEBUG") === "1"; + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function concatChunks(chunks: Uint8Array[]): Uint8Array { + const total = chunks.reduce((n, c) => n + c.length, 0); + const out = new Uint8Array(total); + let offset = 0; + for (const c of chunks) { + out.set(c, offset); + offset += c.length; + } + return out; +} + +async function collectStream( + stream: ReadableStream, + teeTarget: WritableStream | null +): Promise { + const reader = stream.getReader(); + const chunks: Uint8Array[] = []; + const writer = teeTarget?.getWriter(); + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + if (value) { + chunks.push(value); + if (writer) { + await writer.write(value); + } + } + } + } finally { + if (writer) { + writer.releaseLock(); + } + reader.releaseLock(); + } + return concatChunks(chunks); +} + +async function runNodeCommand(args: string[], stdinData?: Uint8Array): Promise { + const cliArgs = DEBUG_ENABLED ? ["-d", ...args] : VERBOSE_ENABLED ? ["-v", ...args] : args; + const child = new Deno.Command("node", { + args: [CLI_DIST, ...cliArgs], + cwd: CLI_DIR, + stdin: stdinData ? "piped" : "null", + stdout: "piped", + stderr: "piped", + }).spawn(); + + const stdoutPromise = collectStream(child.stdout, TEE_ENABLED ? Deno.stdout.writable : null); + const stderrPromise = collectStream(child.stderr, TEE_ENABLED ? Deno.stderr.writable : null); + + if (stdinData) { + const w = child.stdin.getWriter(); + await w.write(stdinData); + await w.close(); + } + + const [status, stdout, stderr] = await Promise.all([child.status, stdoutPromise, stderrPromise]); + + const dec = new TextDecoder(); + const out = dec.decode(stdout); + const err = dec.decode(stderr); + return { stdout: out, stderr: err, combined: out + err, code: status.code }; +} + +function isTransientNetworkError(message: string): boolean { + const m = message.toLowerCase(); + return ( + m.includes("fetch failed") || + m.includes("econnreset") || + m.includes("econnrefused") || + m.includes("und_err_socket") || + m.includes("other side closed") + ); +} + +// --------------------------------------------------------------------------- +// Core runners +// --------------------------------------------------------------------------- + +/** + * Run the CLI (node dist/index.cjs) with the supplied arguments. + * Pass the vault / DB path as the first argument, exactly as the bash helpers + * do. Does NOT throw on non-zero exit — check `.code` yourself. + */ +export async function runCli(...args: string[]): Promise { + const retries = Number(Deno.env.get("LIVESYNC_CLI_RETRY") ?? "0"); + for (let attempt = 0; ; attempt++) { + const result = await runNodeCommand(args); + if (result.code === 0) return result; + + if (attempt >= retries || !isTransientNetworkError(result.combined)) { + return result; + } + const waitMs = 400 * (attempt + 1); + console.warn(`[WARN] transient CLI failure, retrying (${attempt + 1}/${retries}) in ${waitMs}ms`); + await sleep(waitMs); + } +} + +/** + * Run the CLI and throw if it exits non-zero. Returns stdout. + */ +export async function runCliOrFail(...args: string[]): Promise { + const r = await runCli(...args); + if (r.code !== 0) { + throw new Error(`CLI exited with code ${r.code}\nstdout: ${r.stdout}\nstderr: ${r.stderr}`); + } + return r.stdout; +} + +/** + * Run the CLI with data piped to stdin (equivalent to `echo … | run_cli …` + * or `cat file | run_cli …`). + */ +export async function runCliWithInput(input: string | Uint8Array, ...args: string[]): Promise { + const data = typeof input === "string" ? new TextEncoder().encode(input) : input; + + const retries = Number(Deno.env.get("LIVESYNC_CLI_RETRY") ?? "0"); + for (let attempt = 0; ; attempt++) { + const result = await runNodeCommand(args, data); + if (result.code === 0) return result; + + if (attempt >= retries || !isTransientNetworkError(result.combined)) { + return result; + } + const waitMs = 400 * (attempt + 1); + console.warn(`[WARN] transient CLI(stdin) failure, retrying (${attempt + 1}/${retries}) in ${waitMs}ms`); + await sleep(waitMs); + } +} + +/** + * runCliWithInput — throws on non-zero exit, returns stdout. + */ +export async function runCliWithInputOrFail(input: string | Uint8Array, ...args: string[]): Promise { + const r = await runCliWithInput(input, ...args); + if (r.code !== 0) { + throw new Error(`CLI (with stdin) exited with code ${r.code}\nstdout: ${r.stdout}\nstderr: ${r.stderr}`); + } + return r.stdout; +} + +// --------------------------------------------------------------------------- +// Output helpers +// --------------------------------------------------------------------------- + +/** Strip the CLIWatchAdapter banner line that `cat` emits. */ +export function sanitiseCatStdout(raw: string): string { + return raw + .split("\n") + .filter((l) => l !== "[CLIWatchAdapter] File watching is not enabled in CLI version") + .join("\n"); +} + +// --------------------------------------------------------------------------- +// Assertions (parity with test-helpers.sh) +// --------------------------------------------------------------------------- + +export function assertContains(haystack: string, needle: string, message: string): void { + if (!haystack.includes(needle)) { + throw new Error(`[FAIL] ${message}\nExpected to find: ${JSON.stringify(needle)}\nActual output:\n${haystack}`); + } +} + +export function assertNotContains(haystack: string, needle: string, message: string): void { + if (haystack.includes(needle)) { + throw new Error(`[FAIL] ${message}\nDid NOT expect: ${JSON.stringify(needle)}\nActual output:\n${haystack}`); + } +} + +export async function assertFilesEqual(expectedPath: string, actualPath: string, message: string): Promise { + const [expected, actual] = await Promise.all([Deno.readFile(expectedPath), Deno.readFile(actualPath)]); + if (expected.length !== actual.length || expected.some((b, i) => b !== actual[i])) { + const hex = async (d: Uint8Array) => { + const h = await crypto.subtle.digest("SHA-256", d); + return [...new Uint8Array(h)].map((b) => b.toString(16).padStart(2, "0")).join(""); + }; + throw new Error( + `[FAIL] ${message}\nexpected SHA-256: ${await hex(expected)}\nactual SHA-256: ${await hex(actual)}` + ); + } +} + +// --------------------------------------------------------------------------- +// JSON helpers +// --------------------------------------------------------------------------- + +export async function readJsonFile>(filePath: string): Promise { + return JSON.parse(await Deno.readTextFile(filePath)) as T; +} + +export function jsonStringField(jsonText: string, field: string): string { + const data = JSON.parse(jsonText) as Record; + const value = data[field]; + return typeof value === "string" ? value : ""; +} + +export function jsonFieldIsNa(data: Record, field: string): boolean { + return data[field] === "N/A"; +} diff --git a/src/apps/cli/testdeno/helpers/docker.ts b/src/apps/cli/testdeno/helpers/docker.ts new file mode 100644 index 0000000..5ecea1f --- /dev/null +++ b/src/apps/cli/testdeno/helpers/docker.ts @@ -0,0 +1,530 @@ +/** + * Docker service management for tests. + * + * CouchDB start/stop/init is implemented directly using `docker` CLI commands + * and the Fetch API, so it works on any platform where Docker (Desktop) is + * available — including Windows — without needing bash. + */ + +type DockerInvoker = { + bin: string; + prefix: string[]; + label: string; +}; + +let dockerInvokerPromise: Promise | null = null; +const DOCKER_TEE = Deno.env.get("LIVESYNC_DOCKER_TEE") === "1" || Deno.env.get("LIVESYNC_TEST_TEE") === "1"; + +// --------------------------------------------------------------------------- +// Low-level docker wrapper +// --------------------------------------------------------------------------- + +function parseCommand(command: string): { bin: string; prefix: string[] } { + const parts = command.trim().split(/\s+/).filter(Boolean); + if (parts.length === 0) { + throw new Error("LIVESYNC_DOCKER_COMMAND is empty"); + } + return { bin: parts[0], prefix: parts.slice(1) }; +} + +async function runCommand(bin: string, args: string[]): Promise<{ code: number; stdout: string; stderr: string }> { + const cmd = new Deno.Command(bin, { + args, + stdin: "null", + stdout: "piped", + stderr: "piped", + }); + try { + const { code, stdout, stderr } = await cmd.output(); + const dec = new TextDecoder(); + const result = { + code, + stdout: dec.decode(stdout), + stderr: dec.decode(stderr), + }; + if (DOCKER_TEE) { + if (result.stdout.trim().length > 0) { + console.log(`[docker:${bin}] ${result.stdout.trimEnd()}`); + } + if (result.stderr.trim().length > 0) { + console.error(`[docker:${bin}] ${result.stderr.trimEnd()}`); + } + } + return result; + } catch (err) { + if (err instanceof Deno.errors.NotFound) { + return { + code: 127, + stdout: "", + stderr: `Command not found: ${bin}`, + }; + } + throw err; + } +} + +async function resolveDockerInvoker(): Promise { + const custom = Deno.env.get("LIVESYNC_DOCKER_COMMAND")?.trim(); + if (custom) { + const parsed = parseCommand(custom); + const runner: DockerInvoker = { + ...parsed, + label: `custom(${custom})`, + }; + + // Validate custom command eagerly so misconfiguration fails fast. + const checkArgs = runner.prefix.length === 0 ? ["--version"] : [...runner.prefix, "docker", "--version"]; + const check = await runCommand(runner.bin, checkArgs); + if (check.code !== 0) { + throw new Error(`LIVESYNC_DOCKER_COMMAND is not usable: ${custom}\n${check.stderr || check.stdout}`); + } + return runner; + } + + const mode = (Deno.env.get("LIVESYNC_DOCKER_MODE") ?? "auto").toLowerCase(); + const onWindows = Deno.build.os === "windows"; + + const native: DockerInvoker = { bin: "docker", prefix: [], label: "docker" }; + const wsl: DockerInvoker = { bin: "wsl", prefix: [], label: "wsl docker" }; + + if (mode === "native") { + return native; + } + if (mode === "wsl") { + return wsl; + } + if (mode !== "auto") { + throw new Error(`Unsupported LIVESYNC_DOCKER_MODE='${mode}'. Use auto, native, or wsl.`); + } + + // On Windows we prefer `wsl docker` first, then native docker. + // This typically works better in setups where Docker is installed only in + // WSL and not exposed as docker.exe on PATH. + const candidates = onWindows ? [wsl, native] : [native, wsl]; + for (const c of candidates) { + if (c.bin === "docker") { + const r = await runCommand("docker", ["--version"]); + if (r.code === 0) return c; + continue; + } + const r = await runCommand("wsl", ["docker", "--version"]); + if (r.code === 0) return c; + } + + throw new Error( + [ + "Docker command is not available.", + "Set one of:", + "- LIVESYNC_DOCKER_MODE=native", + "- LIVESYNC_DOCKER_MODE=wsl", + "- LIVESYNC_DOCKER_COMMAND='docker'", + "- LIVESYNC_DOCKER_COMMAND='wsl docker'", + ].join("\n") + ); +} + +async function getDockerInvoker(): Promise { + if (!dockerInvokerPromise) { + dockerInvokerPromise = resolveDockerInvoker().then((r) => { + console.log(`[INFO] docker runner: ${r.label}`); + return r; + }); + } + return await dockerInvokerPromise; +} + +async function docker(...args: string[]): Promise<{ code: number; stdout: string; stderr: string }> { + const invoker = await getDockerInvoker(); + + // Either: + // docker + // Or: + // wsl docker + const finalArgs = + invoker.prefix.length === 0 + ? invoker.bin === "wsl" + ? ["docker", ...args] + : args + : [...invoker.prefix, ...args]; + + const r = await runCommand(invoker.bin, finalArgs); + return { code: r.code, stdout: r.stdout, stderr: r.stderr }; +} + +async function dockerOrFail(...args: string[]): Promise { + const r = await docker(...args); + if (r.code !== 0) { + throw new Error(`docker ${args[0]} failed (code ${r.code}): ${r.stderr.trim()}`); + } + return r.stdout; +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +async function waitForCouchdbStable(hostname: string, user: string, password: string): Promise { + const h = hostname.replace(/\/$/, "").replace("localhost", "127.0.0.1"); + const auth = btoa(`${user}:${password}`); + const headers = { Authorization: `Basic ${auth}` }; + let consecutive = 0; + for (let i = 0; i < 30; i++) { + try { + const r = await fetch(`${h}/_up`, { + headers, + signal: AbortSignal.timeout(3000), + }); + if (r.ok) { + consecutive++; + if (consecutive >= 3) return; + } else { + consecutive = 0; + } + } catch { + consecutive = 0; + } + await sleep(500); + } + throw new Error("CouchDB did not become stable in time"); +} + +// --------------------------------------------------------------------------- +// Fetch with retry (mirrors cli_test_curl_json() retry loop) +// --------------------------------------------------------------------------- + +async function fetchRetry( + url: string, + init: RequestInit, + retries = 30, + delayMs = 2000, + allowStatus: number[] = [] +): Promise { + let lastError: unknown; + let lastStatus: number | undefined; + for (let i = 0; i < retries; i++) { + try { + const r = await fetch(url, { + signal: AbortSignal.timeout(5000), + ...init, + }); + lastStatus = r.status; + await r.body?.cancel().catch(() => {}); + if (r.ok || allowStatus.includes(r.status)) return; + lastError = `HTTP ${r.status}`; + } catch (e) { + lastError = e; + } + await sleep(delayMs); + } + throw new Error( + `Could not reach ${url} after ${retries} retries: ${lastError} (last status: ${lastStatus ?? "N/A"})` + ); +} + +// --------------------------------------------------------------------------- +// CouchDB +// --------------------------------------------------------------------------- +// +// TODO: these values could be configurable via environment variables. +// +const COUCHDB_CONTAINER = "couchdb-test"; +const COUCHDB_IMAGE = "couchdb:3.5.0"; + +const MINIO_CONTAINER = "minio-test"; +const MINIO_IMAGE = "minio/minio"; +const MINIO_MC_IMAGE = "minio/mc"; + +export async function stopCouchdb(): Promise { + await docker("stop", COUCHDB_CONTAINER); + await docker("rm", COUCHDB_CONTAINER); +} + +/** + * Start a CouchDB test container, initialise it, and create the test DB. + * Mirrors cli_test_start_couchdb() from test-helpers.sh, using direct + * docker / fetch calls instead of the bash util scripts. + */ +export async function startCouchdb(couchdbUri: string, user: string, password: string, dbname: string): Promise { + console.log("[INFO] stopping leftover CouchDB container if present"); + await stopCouchdb().catch(() => {}); + + console.log("[INFO] starting CouchDB test container"); + await dockerOrFail( + "run", + "-d", + "--name", + COUCHDB_CONTAINER, + "-p", + // TODO: port mapping should be configurable. + "5989:5984", + "-e", + `COUCHDB_USER=${user}`, + "-e", + `COUCHDB_PASSWORD=${password}`, + "-e", + "COUCHDB_SINGLE_NODE=y", + COUCHDB_IMAGE + ); + + console.log("[INFO] initialising CouchDB"); + await initCouchdb(couchdbUri, user, password); + + console.log("[INFO] waiting for CouchDB to become stable"); + await waitForCouchdbStable(couchdbUri, user, password); + + console.log(`[INFO] creating test database: ${dbname}`); + await createCouchdbDatabase(couchdbUri, user, password, dbname); +} + +/** + * Mirror couchdb-init.sh: configure single-node CouchDB via its REST API. + */ +async function initCouchdb(hostname: string, user: string, password: string, node = "_local"): Promise { + // Podman environments often resolve localhost to ::1; use 127.0.0.1 like + // the bash script does. + const h = hostname.replace(/\/$/, "").replace("localhost", "127.0.0.1"); + const auth = btoa(`${user}:${password}`); + const headers = { + "Content-Type": "application/json", + Authorization: `Basic ${auth}`, + }; + + const calls: Array<[string, string, string]> = [ + [ + "POST", + `${h}/_cluster_setup`, + JSON.stringify({ + action: "enable_single_node", + username: user, + password, + bind_address: "0.0.0.0", + port: 5984, + singlenode: true, + }), + ], + ["PUT", `${h}/_node/${node}/_config/chttpd/require_valid_user`, '"true"'], + ["PUT", `${h}/_node/${node}/_config/chttpd_auth/require_valid_user`, '"true"'], + ["PUT", `${h}/_node/${node}/_config/httpd/WWW-Authenticate`, '"Basic realm=\\"couchdb\\""'], + ["PUT", `${h}/_node/${node}/_config/httpd/enable_cors`, '"true"'], + ["PUT", `${h}/_node/${node}/_config/chttpd/enable_cors`, '"true"'], + ["PUT", `${h}/_node/${node}/_config/chttpd/max_http_request_size`, '"4294967296"'], + ["PUT", `${h}/_node/${node}/_config/couchdb/max_document_size`, '"50000000"'], + ["PUT", `${h}/_node/${node}/_config/cors/credentials`, '"true"'], + ["PUT", `${h}/_node/${node}/_config/cors/origins`, '"*"'], + ]; + + for (const [method, url, body] of calls) { + await fetchRetry(url, { method, headers, body }); + } +} + +export async function createCouchdbDatabase( + hostname: string, + user: string, + password: string, + dbname: string +): Promise { + const h = hostname.replace(/\/$/, "").replace("localhost", "127.0.0.1"); + const auth = btoa(`${user}:${password}`); + await fetchRetry(`${h}/${dbname}`, { + method: "PUT", + headers: { Authorization: `Basic ${auth}` }, + }); +} + +/** Update a CouchDB document via PUT. Returns the updated document. */ +export async function updateCouchdbDoc( + hostname: string, + user: string, + password: string, + docUrl: string, + updater: (doc: Record) => Record +): Promise { + const h = hostname.replace(/\/$/, "").replace("localhost", "127.0.0.1"); + const auth = btoa(`${user}:${password}`); + const headers = { + "Content-Type": "application/json", + Authorization: `Basic ${auth}`, + }; + const getRes = await fetch(`${h}/${docUrl}`, { headers }); + const current = (await getRes.json()) as Record; + const updated = updater(current); + await fetchRetry(`${h}/${docUrl}`, { + method: "PUT", + headers, + body: JSON.stringify(updated), + }); +} + +// --------------------------------------------------------------------------- +// MinIO +// --------------------------------------------------------------------------- + +function shQuote(value: string): string { + return `'${value.split("'").join(`'"'"'`)}'`; +} + +export async function stopMinio(): Promise { + await docker("stop", MINIO_CONTAINER); + await docker("rm", MINIO_CONTAINER); +} + +async function initMinioBucket( + minioEndpoint: string, + accessKey: string, + secretKey: string, + bucket: string +): Promise { + const cmd = + `mc alias set myminio ${shQuote(minioEndpoint)} ${shQuote(accessKey)} ${shQuote(secretKey)} >/dev/null 2>&1 && ` + + `mc mb --ignore-existing myminio/${shQuote(bucket)} >/dev/null 2>&1`; + const r = await docker("run", "--rm", "--network", "host", "--entrypoint", "/bin/sh", MINIO_MC_IMAGE, "-c", cmd); + return r.code === 0; +} + +async function waitForMinioBucket( + minioEndpoint: string, + accessKey: string, + secretKey: string, + bucket: string +): Promise { + for (let i = 0; i < 30; i++) { + const checkCmd = + `mc alias set myminio ${shQuote(minioEndpoint)} ${shQuote(accessKey)} ${shQuote(secretKey)} >/dev/null 2>&1 && ` + + `mc ls myminio/${shQuote(bucket)} >/dev/null 2>&1`; + const check = await docker( + "run", + "--rm", + "--network", + // Now I used host networking to access the container via localhost for some environments (Docker Desktop on Windows). + // We need something good idea to work across all environments. + "host", + "--entrypoint", + "/bin/sh", + MINIO_MC_IMAGE, + "-c", + checkCmd + ); + if (check.code === 0) { + return; + } + await initMinioBucket(minioEndpoint, accessKey, secretKey, bucket); + await sleep(2000); + } + throw new Error(`MinIO bucket not ready: ${bucket}`); +} + +export async function startMinio( + minioEndpoint: string, + accessKey: string, + secretKey: string, + bucket: string +): Promise { + console.log("[INFO] stopping leftover MinIO container if present"); + await stopMinio().catch(() => {}); + + console.log("[INFO] starting MinIO test container"); + await dockerOrFail( + "run", + "-d", + "--name", + MINIO_CONTAINER, + // TODO: Ports should be configurable. + "-p", + "9000:9000", + "-p", + "9001:9001", + "-e", + `MINIO_ROOT_USER=${accessKey}`, + "-e", + `MINIO_ROOT_PASSWORD=${secretKey}`, + "-e", + `MINIO_SERVER_URL=${minioEndpoint}`, + MINIO_IMAGE, + "server", + "/data", + "--console-address", + ":9001" + ); + + console.log(`[INFO] initialising MinIO test bucket: ${bucket}`); + let initialised = false; + for (let i = 0; i < 5; i++) { + if (await initMinioBucket(minioEndpoint, accessKey, secretKey, bucket)) { + initialised = true; + break; + } + await sleep(2000); + } + if (!initialised) { + throw new Error(`Could not initialise MinIO bucket after retries: ${bucket}`); + } + + await waitForMinioBucket(minioEndpoint, accessKey, secretKey, bucket); +} + +// --------------------------------------------------------------------------- +// P2P relay (strfry) +// --------------------------------------------------------------------------- +// TODO: these values could be configurable via environment variables. +const P2P_RELAY_CONTAINER = "relay-test"; +const P2P_RELAY_IMAGE = "ghcr.io/hoytech/strfry:latest"; +const STRFRY_BOOTSTRAP_SH = String.raw`cat > /tmp/strfry.conf <<"EOF" +db = "./strfry-db/" + +relay { + bind = "0.0.0.0" + port = 7777 + nofiles = 100000 + + info { + name = "livesync test relay" + description = "local relay for livesync p2p tests" + } + + maxWebsocketPayloadSize = 131072 + autoPingSeconds = 55 + + writePolicy { + plugin = "" + } +} +EOF +exec /app/strfry --config /tmp/strfry.conf relay`; + +export async function stopP2pRelay(): Promise { + await docker("stop", P2P_RELAY_CONTAINER); + await docker("rm", P2P_RELAY_CONTAINER); +} + +/** + * Start the local P2P relay container through the same docker runner used + * by CouchDB helpers. This keeps process ownership consistent across + * start/stop on Windows, WSL, and native Linux/macOS. + */ +export async function startP2pRelay(): Promise { + console.log("[INFO] stopping leftover P2P relay container if present"); + await stopP2pRelay().catch(() => {}); + + console.log("[INFO] starting local P2P relay container"); + await dockerOrFail( + "run", + "-d", + "--name", + P2P_RELAY_CONTAINER, + "-p", + //TODO: port mapping should be configurable. + "4000:7777", + "--tmpfs", + "/app/strfry-db:rw,size=256m", + "--entrypoint", + "sh", + P2P_RELAY_IMAGE, + "-lc", + STRFRY_BOOTSTRAP_SH + ); +} + +export function isLocalP2pRelay(relayUrl: string): boolean { + return relayUrl === "ws://localhost:4000" || relayUrl === "ws://localhost:4000/"; +} diff --git a/src/apps/cli/testdeno/helpers/env.ts b/src/apps/cli/testdeno/helpers/env.ts new file mode 100644 index 0000000..656a2f4 --- /dev/null +++ b/src/apps/cli/testdeno/helpers/env.ts @@ -0,0 +1,26 @@ +/** + * Load a .env-style file (KEY=value per line) into a plain object. + * Equivalent to `source $TEST_ENV_FILE; set -a` in bash. + * Maybe we should use some library... now it is just the minimal implementation that covers our use cases. + * + * Supported value formats: + * KEY=value + * KEY='single quoted' + * KEY="double quoted" + * # comment lines are ignored + */ +export async function loadEnvFile(filePath: string): Promise> { + const text = await Deno.readTextFile(filePath); + const result: Record = {}; + for (const line of text.split("\n")) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("#")) continue; + const idx = trimmed.indexOf("="); + if (idx < 0) continue; + const key = trimmed.slice(0, idx).trim(); + const raw = trimmed.slice(idx + 1).trim(); + // Strip surrounding single or double quotes + result[key] = raw.replace(/^(['"])(.*)\1$/, "$2"); + } + return result; +} diff --git a/src/apps/cli/testdeno/helpers/p2p.ts b/src/apps/cli/testdeno/helpers/p2p.ts new file mode 100644 index 0000000..28741c4 --- /dev/null +++ b/src/apps/cli/testdeno/helpers/p2p.ts @@ -0,0 +1,52 @@ +import { runCli } from "./cli.ts"; +import { isLocalP2pRelay, startP2pRelay, stopP2pRelay } from "./docker.ts"; + +export type PeerEntry = { + id: string; + name: string; +}; + +export function parsePeerLines(output: string): PeerEntry[] { + return output + .split(/\r?\n/) + .map((line) => line.split("\t")) + .filter((parts) => parts.length >= 3 && parts[0] === "[peer]") + .map((parts) => ({ id: parts[1], name: parts[2] })); +} + +export async function discoverPeer( + vaultDir: string, + settingsFile: string, + timeoutSeconds: number, + targetPeer?: string +): Promise { + const result = await runCli(vaultDir, "--settings", settingsFile, "p2p-peers", String(timeoutSeconds)); + if (result.code !== 0) { + throw new Error(`p2p-peers failed\n${result.combined}`); + } + const peers = parsePeerLines(result.stdout); + if (targetPeer) { + const matched = peers.find((peer) => peer.id === targetPeer || peer.name === targetPeer); + if (matched) return matched; + } + if (peers.length === 0) { + const fallback = result.combined.match(/Advertisement from\s+([^\s]+)/); + if (fallback?.[1]) { + return { id: fallback[1], name: fallback[1] }; + } + throw new Error(`No peers discovered\n${result.combined}`); + } + return peers[0]; +} + +export async function maybeStartLocalRelay(relay: string): Promise { + if (!isLocalP2pRelay(relay)) return false; + await startP2pRelay(); + return true; +} + +export async function stopLocalRelayIfStarted(started: boolean): Promise { + if (started) { + await stopP2pRelay().catch(() => {}); + } +} diff --git a/src/apps/cli/testdeno/helpers/settings.ts b/src/apps/cli/testdeno/helpers/settings.ts new file mode 100644 index 0000000..6f88a03 --- /dev/null +++ b/src/apps/cli/testdeno/helpers/settings.ts @@ -0,0 +1,205 @@ +import { join } from "@std/path"; +import { CLI_DIR, runCliOrFail } from "./cli.ts"; + +// --------------------------------------------------------------------------- +// Settings file initialisation +// --------------------------------------------------------------------------- + +/** Generate a default settings file using the CLI's init-settings command. */ +export async function initSettingsFile(settingsFile: string): Promise { + await runCliOrFail("init-settings", "--force", settingsFile); +} + +/** + * Generate a full setup URI from a settings file via src/lib API. + * Mirrors the bash flow in test-setup-put-cat-linux.sh. + */ +export async function generateSetupUriFromSettings(settingsFile: string, setupPassphrase: string): Promise { + const repoRoot = join(CLI_DIR, "..", "..", ".."); + const script = [ + "import fs from 'node:fs';", + "import { pathToFileURL } from 'node:url';", + "(async () => {", + " const modulePath = process.env.REPO_ROOT + '/src/lib/src/API/processSetting.ts';", + " const moduleUrl = pathToFileURL(modulePath).href;", + " const { encodeSettingsToSetupURI } = await import(moduleUrl);", + " const settingsPath = process.env.SETTINGS_FILE;", + " const passphrase = process.env.SETUP_PASSPHRASE;", + " const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));", + " settings.couchDB_DBNAME = 'setup-put-cat-db';", + " settings.couchDB_URI = 'http://127.0.0.1:5999';", + " settings.couchDB_USER = 'dummy';", + " settings.couchDB_PASSWORD = 'dummy';", + " settings.liveSync = false;", + " settings.syncOnStart = false;", + " settings.syncOnSave = false;", + " const uri = await encodeSettingsToSetupURI(settings, passphrase);", + " process.stdout.write(uri.trim());", + "})();", + ].join("\n"); + + const scriptPath = await Deno.makeTempFile({ + prefix: "livesync-setup-uri-", + suffix: ".mts", + }); + await Deno.writeTextFile(scriptPath, script); + + try { + const cmd = new Deno.Command("npx", { + args: ["tsx", scriptPath], + cwd: CLI_DIR, + env: { + REPO_ROOT: repoRoot, + SETTINGS_FILE: settingsFile, + SETUP_PASSPHRASE: setupPassphrase, + }, + stdin: "null", + stdout: "piped", + stderr: "piped", + }); + + const { code, stdout, stderr } = await cmd.output(); + const dec = new TextDecoder(); + if (code !== 0) { + throw new Error( + `Failed to generate setup URI (code ${code})\nstdout: ${dec.decode(stdout)}\nstderr: ${dec.decode(stderr)}` + ); + } + + const uri = dec.decode(stdout).trim(); + if (!uri) { + throw new Error("Failed to generate setup URI: output is empty"); + } + return uri; + } finally { + await Deno.remove(scriptPath).catch(() => {}); + } +} + +/** Set isConfigured=true in a settings file (required for mirror / scan). */ +export async function markSettingsConfigured(settingsFile: string): Promise { + const data = JSON.parse(await Deno.readTextFile(settingsFile)); + data.isConfigured = true; + await Deno.writeTextFile(settingsFile, JSON.stringify(data, null, 2)); +} + +// --------------------------------------------------------------------------- +// CouchDB remote settings +// --------------------------------------------------------------------------- + +/** + * Apply CouchDB connection details to a settings file. + * Mirrors cli_test_apply_couchdb_settings() from test-helpers.sh. + */ +export async function applyCouchdbSettings( + settingsFile: string, + couchdbUri: string, + couchdbUser: string, + couchdbPassword: string, + couchdbDbname: string, + liveSync = false +): Promise { + const data = JSON.parse(await Deno.readTextFile(settingsFile)); + data.couchDB_URI = couchdbUri; + data.couchDB_USER = couchdbUser; + data.couchDB_PASSWORD = couchdbPassword; + data.couchDB_DBNAME = couchdbDbname; + if (liveSync) { + data.liveSync = true; + data.syncOnStart = false; + data.syncOnSave = false; + data.usePluginSync = false; + } + data.isConfigured = true; + await Deno.writeTextFile(settingsFile, JSON.stringify(data, null, 2)); +} + +export async function applyRemoteSyncSettings( + settingsFile: string, + options: { + remoteType: "COUCHDB" | "MINIO"; + couchdbUri?: string; + couchdbUser?: string; + couchdbPassword?: string; + couchdbDbname?: string; + minioBucket?: string; + minioEndpoint?: string; + minioAccessKey?: string; + minioSecretKey?: string; + encrypt?: boolean; + passphrase?: string; + } +): Promise { + const data = JSON.parse(await Deno.readTextFile(settingsFile)); + + if (options.remoteType === "COUCHDB") { + data.remoteType = ""; + data.couchDB_URI = options.couchdbUri; + data.couchDB_USER = options.couchdbUser; + data.couchDB_PASSWORD = options.couchdbPassword; + data.couchDB_DBNAME = options.couchdbDbname; + } else { + data.remoteType = "MINIO"; + data.bucket = options.minioBucket; + data.endpoint = options.minioEndpoint; + data.accessKey = options.minioAccessKey; + data.secretKey = options.minioSecretKey; + data.region = "auto"; + data.forcePathStyle = true; + } + + data.liveSync = true; + data.syncOnStart = false; + data.syncOnSave = false; + data.usePluginSync = false; + data.encrypt = options.encrypt === true; + data.passphrase = options.encrypt ? (options.passphrase ?? "") : ""; + data.isConfigured = true; + await Deno.writeTextFile(settingsFile, JSON.stringify(data, null, 2)); +} + +// --------------------------------------------------------------------------- +// P2P settings +// --------------------------------------------------------------------------- + +/** + * Apply P2P connection details to a settings file. + * Mirrors cli_test_apply_p2p_settings() from test-helpers.sh. + */ +export async function applyP2pSettings( + settingsFile: string, + roomId: string, + passphrase: string, + appId = "self-hosted-livesync-cli-tests", + relays = "ws://localhost:4000/", + autoAccept = "~.*" +): Promise { + const data = JSON.parse(await Deno.readTextFile(settingsFile)); + data.P2P_Enabled = true; + data.P2P_AutoStart = false; + data.P2P_AutoBroadcast = false; + data.P2P_AppID = appId; + data.P2P_roomID = roomId; + data.P2P_passphrase = passphrase; + data.P2P_relays = relays; + data.P2P_AutoAcceptingPeers = autoAccept; + data.P2P_AutoDenyingPeers = ""; + data.P2P_IsHeadless = true; + data.isConfigured = true; + await Deno.writeTextFile(settingsFile, JSON.stringify(data, null, 2)); +} + +export async function applyP2pTestTweaks(settingsFile: string, deviceName: string, passphrase: string): Promise { + const data = JSON.parse(await Deno.readTextFile(settingsFile)); + data.remoteType = "ONLY_P2P"; + data.encrypt = true; + data.passphrase = passphrase; + data.usePathObfuscation = true; + data.handleFilenameCaseSensitive = false; + data.customChunkSize = 50; + data.usePluginSyncV2 = true; + data.doNotUseFixedRevisionForChunks = false; + data.P2P_DevicePeerName = deviceName; + data.isConfigured = true; + await Deno.writeTextFile(settingsFile, JSON.stringify(data, null, 2)); +} diff --git a/src/apps/cli/testdeno/helpers/temp.ts b/src/apps/cli/testdeno/helpers/temp.ts new file mode 100644 index 0000000..655d18d --- /dev/null +++ b/src/apps/cli/testdeno/helpers/temp.ts @@ -0,0 +1,33 @@ +import { join } from "@std/path"; + +/** + * A temporary directory that cleans itself up via `await using`. + * Requires TypeScript 5.2+ / Deno 1.40+ for the AsyncDisposable protocol. + * + * @example + * ```ts + * await using tmp = await TempDir.create(); + * const file = tmp.join("data.json"); + * ``` + */ +export class TempDir implements AsyncDisposable { + readonly path: string; + + private constructor(path: string) { + this.path = path; + } + + static async create(prefix = "livesync-deno-test"): Promise { + const path = await Deno.makeTempDir({ prefix: `${prefix}.` }); + return new TempDir(path); + } + + /** Return an OS path joined to the temp directory root. */ + join(...parts: string[]): string { + return join(this.path, ...parts); + } + + async [Symbol.asyncDispose](): Promise { + await Deno.remove(this.path, { recursive: true }).catch(() => {}); + } +} diff --git a/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts b/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts new file mode 100644 index 0000000..f1b60f1 --- /dev/null +++ b/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts @@ -0,0 +1,279 @@ +import { assert } from "@std/assert"; +import { TempDir } from "./helpers/temp.ts"; +import { loadEnvFile } from "./helpers/env.ts"; +import { + runCli, + runCliOrFail, + runCliWithInputOrFail, + sanitiseCatStdout, + assertFilesEqual, + jsonStringField, +} from "./helpers/cli.ts"; +import { applyRemoteSyncSettings, initSettingsFile } from "./helpers/settings.ts"; +import { startCouchdb, startMinio, stopCouchdb, stopMinio } from "./helpers/docker.ts"; +import { join } from "@std/path"; + +const TEST_ENV = join(import.meta.dirname!, "..", ".test.env"); +type RemoteType = "COUCHDB" | "MINIO"; + +function requireEnv(env: Record, key: string): string { + const value = env[key]?.trim(); + if (!value) throw new Error(`Required env var is missing: ${key}`); + return value; +} + +export async function runScenario(remoteType: RemoteType, encrypt: boolean): Promise { + const env = await loadEnvFile(TEST_ENV); + const dbSuffix = `${Date.now()}-${Math.floor(Math.random() * 100000)}`; + + const couchdbUri = remoteType === "COUCHDB" ? requireEnv(env, "hostname").replace(/\/$/, "") : ""; + const couchdbUser = remoteType === "COUCHDB" ? requireEnv(env, "username") : ""; + const couchdbPassword = remoteType === "COUCHDB" ? requireEnv(env, "password") : ""; + const dbPrefix = remoteType === "COUCHDB" ? requireEnv(env, "dbname") : ""; + const dbname = remoteType === "COUCHDB" ? `${dbPrefix}-${dbSuffix}` : ""; + + const minioEndpoint = remoteType === "MINIO" ? requireEnv(env, "minioEndpoint").replace(/\/$/, "") : ""; + const minioAccessKey = remoteType === "MINIO" ? requireEnv(env, "accessKey") : ""; + const minioSecretKey = remoteType === "MINIO" ? requireEnv(env, "secretKey") : ""; + const minioBucketBase = remoteType === "MINIO" ? requireEnv(env, "bucketName") : ""; + const minioBucket = remoteType === "MINIO" ? `${minioBucketBase}-${dbSuffix}` : ""; + + const passphrase = "e2e-passphrase"; + + await using workDir = await TempDir.create( + `livesync-cli-e2e-${remoteType.toLowerCase()}-${encrypt ? "enc1" : "enc0"}` + ); + const vaultA = workDir.join("testvault_a"); + const vaultB = workDir.join("testvault_b"); + const settingsA = workDir.join("test-settings-a.json"); + const settingsB = workDir.join("test-settings-b.json"); + const pushSrc = workDir.join("push-source.txt"); + const pullDst = workDir.join("pull-destination.txt"); + const pushBinarySrc = workDir.join("push-source.bin"); + const pullBinaryDst = workDir.join("pull-destination.bin"); + await Deno.mkdir(vaultA, { recursive: true }); + await Deno.mkdir(vaultB, { recursive: true }); + + const keepDocker = Deno.env.get("LIVESYNC_DEBUG_KEEP_DOCKER") === "1"; + if (remoteType === "COUCHDB") { + await startCouchdb(couchdbUri, couchdbUser, couchdbPassword, dbname); + } else { + await startMinio(minioEndpoint, minioAccessKey, minioSecretKey, minioBucket); + } + + try { + await initSettingsFile(settingsA); + await initSettingsFile(settingsB); + await applyRemoteSyncSettings(settingsA, { + remoteType, + couchdbUri, + couchdbUser, + couchdbPassword, + couchdbDbname: dbname, + minioBucket, + minioEndpoint, + minioAccessKey, + minioSecretKey, + encrypt, + passphrase, + }); + await applyRemoteSyncSettings(settingsB, { + remoteType, + couchdbUri, + couchdbUser, + couchdbPassword, + couchdbDbname: dbname, + minioBucket, + minioEndpoint, + minioAccessKey, + minioSecretKey, + encrypt, + passphrase, + }); + + const syncBoth = async () => { + await runCliOrFail(vaultA, "--settings", settingsA, "sync"); + await runCliOrFail(vaultB, "--settings", settingsB, "sync"); + }; + + const targetAOnly = "e2e/a-only-info.md"; + const targetSync = "e2e/sync-info.md"; + const targetSyncTwiceFirst = "e2e/sync-twice-first.md"; + const targetSyncTwiceSecond = "e2e/sync-twice-second.md"; + const targetPush = "e2e/pushed-from-a.md"; + const targetPut = "e2e/put-from-a.md"; + const targetPushBinary = "e2e/pushed-from-a.bin"; + const targetConflict = "e2e/conflict.md"; + + await runCliWithInputOrFail("alpha-from-a\n", vaultA, "--settings", settingsA, "put", targetAOnly); + const infoAOnly = await runCliOrFail(vaultA, "--settings", settingsA, "info", targetAOnly); + assert(infoAOnly.includes(`"path": "${targetAOnly}"`)); + + await runCliWithInputOrFail("visible-after-sync\n", vaultA, "--settings", settingsA, "put", targetSync); + await syncBoth(); + const infoBSync = await runCliOrFail(vaultB, "--settings", settingsB, "info", targetSync); + assert(infoBSync.includes(`"path": "${targetSync}"`)); + + await runCliWithInputOrFail( + `first-sync-round-${dbSuffix}\n`, + vaultA, + "--settings", + settingsA, + "put", + targetSyncTwiceFirst + ); + await runCliOrFail(vaultA, "--settings", settingsA, "sync"); + await runCliOrFail(vaultB, "--settings", settingsB, "sync"); + const firstVisible = sanitiseCatStdout( + await runCliOrFail(vaultB, "--settings", settingsB, "cat", targetSyncTwiceFirst) + ).trimEnd(); + assert(firstVisible === `first-sync-round-${dbSuffix}`); + + await runCliWithInputOrFail( + `second-sync-round-${dbSuffix}\n`, + vaultA, + "--settings", + settingsA, + "put", + targetSyncTwiceSecond + ); + await runCliOrFail(vaultA, "--settings", settingsA, "sync"); + await runCliOrFail(vaultB, "--settings", settingsB, "sync"); + const secondVisible = sanitiseCatStdout( + await runCliOrFail(vaultB, "--settings", settingsB, "cat", targetSyncTwiceSecond) + ).trimEnd(); + assert(secondVisible === `second-sync-round-${dbSuffix}`); + + await Deno.writeTextFile(pushSrc, `pushed-content-${dbSuffix}\n`); + await runCliOrFail(vaultA, "--settings", settingsA, "push", pushSrc, targetPush); + await runCliWithInputOrFail(`put-content-${dbSuffix}\n`, vaultA, "--settings", settingsA, "put", targetPut); + await syncBoth(); + await runCliOrFail(vaultB, "--settings", settingsB, "pull", targetPush, pullDst); + await assertFilesEqual(pushSrc, pullDst, "B pull result does not match pushed source"); + const catBPut = sanitiseCatStdout( + await runCliOrFail(vaultB, "--settings", settingsB, "cat", targetPut) + ).trimEnd(); + assert(catBPut === `put-content-${dbSuffix}`); + + const binary = new Uint8Array(4096); + binary.fill(0x61); + await Deno.writeFile(pushBinarySrc, binary); + await runCliOrFail(vaultA, "--settings", settingsA, "push", pushBinarySrc, targetPushBinary); + await syncBoth(); + await runCliOrFail(vaultB, "--settings", settingsB, "pull", targetPushBinary, pullBinaryDst); + await assertFilesEqual(pushBinarySrc, pullBinaryDst, "B pull result does not match pushed binary source"); + + await runCliOrFail(vaultA, "--settings", settingsA, "rm", targetPut); + await syncBoth(); + const removed = await runCli(vaultB, "--settings", settingsB, "cat", targetPut); + assert(removed.code !== 0, `B cat should fail after A removed the file\n${removed.combined}`); + + await runCliWithInputOrFail("conflict-base\n", vaultA, "--settings", settingsA, "put", targetConflict); + await syncBoth(); + await runCliWithInputOrFail( + `conflict-from-a-${dbSuffix}\n`, + vaultA, + "--settings", + settingsA, + "put", + targetConflict + ); + await runCliWithInputOrFail( + `conflict-from-b-${dbSuffix}\n`, + vaultB, + "--settings", + settingsB, + "put", + targetConflict + ); + + let infoAConflict = ""; + let infoBConflict = ""; + let conflictDetected = false; + for (const side of ["a", "b", "a"] as const) { + await runCliOrFail( + side === "a" ? vaultA : vaultB, + "--settings", + side === "a" ? settingsA : settingsB, + "sync" + ); + infoAConflict = await runCliOrFail(vaultA, "--settings", settingsA, "info", targetConflict); + infoBConflict = await runCliOrFail(vaultB, "--settings", settingsB, "info", targetConflict); + if ( + jsonStringField(infoAConflict, "conflicts") !== "N/A" || + jsonStringField(infoBConflict, "conflicts") !== "N/A" + ) { + conflictDetected = true; + break; + } + } + assert(conflictDetected, `conflict was expected\nA: ${infoAConflict}\nB: ${infoBConflict}`); + + const lsAConflict = + (await runCliOrFail(vaultA, "--settings", settingsA, "ls", targetConflict)).trim().split(/\r?\n/)[0] ?? ""; + const lsBConflict = + (await runCliOrFail(vaultB, "--settings", settingsB, "ls", targetConflict)).trim().split(/\r?\n/)[0] ?? ""; + const revA = lsAConflict.split("\t")[3] ?? ""; + const revB = lsBConflict.split("\t")[3] ?? ""; + assert( + revA.includes("*") || revB.includes("*"), + `conflicted entry should be marked with '*'\nA: ${lsAConflict}\nB: ${lsBConflict}` + ); + + const keepRevision = jsonStringField(infoAConflict, "revision"); + assert(keepRevision.length > 0, `could not extract revision\n${infoAConflict}`); + await runCliOrFail(vaultA, "--settings", settingsA, "resolve", targetConflict, keepRevision); + + let resolved = false; + let infoAResolved = ""; + let infoBResolved = ""; + for (let i = 0; i < 6; i++) { + await syncBoth(); + infoAResolved = await runCliOrFail(vaultA, "--settings", settingsA, "info", targetConflict); + infoBResolved = await runCliOrFail(vaultB, "--settings", settingsB, "info", targetConflict); + if ( + jsonStringField(infoAResolved, "conflicts") === "N/A" && + jsonStringField(infoBResolved, "conflicts") === "N/A" + ) { + resolved = true; + break; + } + const retryRevision = jsonStringField(infoAResolved, "revision"); + if (retryRevision) { + await runCli(vaultA, "--settings", settingsA, "resolve", targetConflict, retryRevision); + } + } + assert(resolved, `conflicts should be resolved\nA: ${infoAResolved}\nB: ${infoBResolved}`); + + const lsAResolved = + (await runCliOrFail(vaultA, "--settings", settingsA, "ls", targetConflict)).trim().split(/\r?\n/)[0] ?? ""; + const lsBResolved = + (await runCliOrFail(vaultB, "--settings", settingsB, "ls", targetConflict)).trim().split(/\r?\n/)[0] ?? ""; + assert(!(lsAResolved.split("\t")[3] ?? "").includes("*")); + assert(!(lsBResolved.split("\t")[3] ?? "").includes("*")); + + const catAResolved = sanitiseCatStdout( + await runCliOrFail(vaultA, "--settings", settingsA, "cat", targetConflict) + ).trimEnd(); + const catBResolved = sanitiseCatStdout( + await runCliOrFail(vaultB, "--settings", settingsB, "cat", targetConflict) + ).trimEnd(); + assert(catAResolved === catBResolved, `resolved content should match\nA: ${catAResolved}\nB: ${catBResolved}`); + } finally { + if (!keepDocker) { + if (remoteType === "COUCHDB") { + await stopCouchdb().catch(() => {}); + } else { + await stopMinio().catch(() => {}); + } + } + } +} + +Deno.test("e2e: two vaults over CouchDB without encryption", async () => { + await runScenario("COUCHDB", false); +}); + +Deno.test("e2e: two vaults over CouchDB with encryption", async () => { + await runScenario("COUCHDB", true); +}); diff --git a/src/apps/cli/testdeno/test-e2e-two-vaults-matrix.ts b/src/apps/cli/testdeno/test-e2e-two-vaults-matrix.ts new file mode 100644 index 0000000..757d6b5 --- /dev/null +++ b/src/apps/cli/testdeno/test-e2e-two-vaults-matrix.ts @@ -0,0 +1,20 @@ +import { runScenario } from "./test-e2e-two-vaults-couchdb.ts"; + +type MatrixCase = { + remoteType: "COUCHDB" | "MINIO"; + encrypt: boolean; + label: string; +}; + +const matrixCases: MatrixCase[] = [ + { remoteType: "COUCHDB", encrypt: false, label: "COUCHDB-enc0" }, + { remoteType: "COUCHDB", encrypt: true, label: "COUCHDB-enc1" }, + { remoteType: "MINIO", encrypt: false, label: "MINIO-enc0" }, + { remoteType: "MINIO", encrypt: true, label: "MINIO-enc1" }, +]; + +for (const tc of matrixCases) { + Deno.test(`e2e matrix: ${tc.label}`, async () => { + await runScenario(tc.remoteType, tc.encrypt); + }); +} diff --git a/src/apps/cli/testdeno/test-mirror.ts b/src/apps/cli/testdeno/test-mirror.ts new file mode 100644 index 0000000..b6eae4d --- /dev/null +++ b/src/apps/cli/testdeno/test-mirror.ts @@ -0,0 +1,196 @@ +/** + * Deno port of test-mirror-linux.sh + * + * Tests the `mirror` command — bidirectional synchronisation between a local + * storage directory (vault) and an in-process database. + * + * Covered cases (identical to the bash test): + * 1. Storage-only file -> synced into DB (UPDATE DATABASE) + * 2. DB-only file -> restored to storage (UPDATE STORAGE) + * 3. DB-deleted file -> NOT restored to storage (UPDATE STORAGE skip) + * 4. Both, storage newer -> DB updated (SYNC: STORAGE -> DB) + * 5. Both, DB newer -> storage updated (SYNC: DB -> STORAGE) + * 6. Compatibility mode -> omitted vault-path works (same DB + vault path) + * + * No external services are required. + * + * Run: + * deno test -A test-mirror.ts + */ + +import { assert } from "@std/assert"; +import { TempDir } from "./helpers/temp.ts"; +import { runCliOrFail } from "./helpers/cli.ts"; +import { initSettingsFile, markSettingsConfigured } from "./helpers/settings.ts"; + +Deno.test("mirror: storage <-> DB synchronisation", async (t) => { + await using workDir = await TempDir.create("livesync-cli-mirror"); + + // ------------------------------------------------------------------- + // Shared setup + // ------------------------------------------------------------------- + const settingsFile = workDir.join("data.json"); + const vaultDir = workDir.join("vault"); + const dbDir = workDir.join("db"); + await Deno.mkdir(workDir.join("vault", "test"), { recursive: true }); + await Deno.mkdir(dbDir, { recursive: true }); + + await initSettingsFile(settingsFile); + // isConfigured=true is required for canProceedScan in the mirror command. + await markSettingsConfigured(settingsFile); + + // Copy settings to the DB directory (separated-path mode) + const dbSettings = workDir.join("db", "settings.json"); + await Deno.copyFile(settingsFile, dbSettings); + + /** Run mirror in separated-path mode: DB dir ≠ vault dir. */ + const runMirror = () => runCliOrFail(dbDir, "--settings", dbSettings, "mirror", vaultDir); + + /** Run mirror in compatibility mode: DB path = vault path. */ + const runMirrorCompat = () => runCliOrFail(vaultDir, "--settings", settingsFile, "mirror"); + + // Helper wrappers + const dbRun = (...args: string[]) => runCliOrFail(dbDir, "--settings", dbSettings, ...args); + const compatRun = (...args: string[]) => runCliOrFail(vaultDir, "--settings", settingsFile, ...args); + + // ------------------------------------------------------------------- + // Case 1: storage-only -> DB (UPDATE DATABASE) + // ------------------------------------------------------------------- + await t.step("case 1: storage-only file is synced into DB", async () => { + const storageFile = workDir.join("vault", "test", "storage-only.md"); + await Deno.writeTextFile(storageFile, "storage-only content\n"); + + await runMirror(); + + const resultFile = workDir.join("case1-pull.txt"); + await dbRun("pull", "test/storage-only.md", resultFile); + + const storageContent = await Deno.readTextFile(storageFile); + const pulledContent = await Deno.readTextFile(resultFile); + assert( + storageContent === pulledContent, + `storage-only file NOT synced into DB\nexpected: ${storageContent}\ngot: ${pulledContent}` + ); + console.log("[PASS] case 1: storage-only file was synced into DB"); + }); + + // ------------------------------------------------------------------- + // Case 2: DB-only -> storage (UPDATE STORAGE) + // ------------------------------------------------------------------- + await t.step("case 2: DB-only file is restored to storage", async () => { + await dbRun( + "push", + // write inline via push (pipe not needed — push takes a file path) + // create a temp file with content and push it + await (async () => { + const tmp = workDir.join("db-only-src.txt"); + await Deno.writeTextFile(tmp, "db-only content\n"); + return tmp; + })(), + "test/db-only.md" + ); + + const storagePath = workDir.join("vault", "test", "db-only.md"); + assert(!(await exists(storagePath)), "db-only.md unexpectedly exists in storage before mirror"); + + await runMirror(); + + assert(await exists(storagePath), "DB-only file NOT restored to storage after mirror"); + const content = await Deno.readTextFile(storagePath); + assert(content === "db-only content\n", `DB-only file restored but content mismatch: '${content}'`); + console.log("[PASS] case 2: DB-only file was restored to storage"); + }); + + // ------------------------------------------------------------------- + // Case 3: DB-deleted -> storage untouched + // ------------------------------------------------------------------- + await t.step("case 3: DB-deleted entry is NOT restored to storage", async () => { + const deletedSrc = workDir.join("deleted-src.txt"); + await Deno.writeTextFile(deletedSrc, "to-be-deleted\n"); + await dbRun("push", deletedSrc, "test/deleted.md"); + await dbRun("rm", "test/deleted.md"); + + await runMirror(); + + const storagePath = workDir.join("vault", "test", "deleted.md"); + assert(!(await exists(storagePath)), "deleted DB entry was incorrectly restored to storage"); + console.log("[PASS] case 3: deleted DB entry was NOT restored to storage"); + }); + + // ------------------------------------------------------------------- + // Case 4: storage newer -> DB updated (SYNC: STORAGE -> DB) + // ------------------------------------------------------------------- + await t.step("case 4: storage newer than DB -> DB is updated", async () => { + // Seed DB with old content (mtime ~ now) + const seedFile = workDir.join("case4-seed.txt"); + await Deno.writeTextFile(seedFile, "old content\n"); + await dbRun("push", seedFile, "test/sync-storage-newer.md"); + + // Write new content to storage with a timestamp 1 hour in the future + const storageFile = workDir.join("vault", "test", "sync-storage-newer.md"); + await Deno.writeTextFile(storageFile, "new content\n"); + await Deno.utime(storageFile, new Date(), new Date(Date.now() + 3600_000)); + + await runMirror(); + + const resultFile = workDir.join("case4-pull.txt"); + await dbRun("pull", "test/sync-storage-newer.md", resultFile); + const storageContent = await Deno.readTextFile(storageFile); + const pulledContent = await Deno.readTextFile(resultFile); + assert( + storageContent === pulledContent, + `DB NOT updated to match newer storage file\nexpected: ${storageContent}\ngot: ${pulledContent}` + ); + console.log("[PASS] case 4: DB updated to match newer storage file"); + }); + + // ------------------------------------------------------------------- + // Case 5: DB newer -> storage updated (SYNC: DB -> STORAGE) + // ------------------------------------------------------------------- + await t.step("case 5: DB newer than storage -> storage is updated", async () => { + // Write old content to storage with a timestamp 1 hour in the past + const storageFile = workDir.join("vault", "test", "sync-db-newer.md"); + await Deno.writeTextFile(storageFile, "old storage content\n"); + await Deno.utime(storageFile, new Date(), new Date(Date.now() - 3600_000)); + + // Write new content to DB only (mtime ~ now, newer than the storage file) + const dbNewFile = workDir.join("case5-db-new.txt"); + await Deno.writeTextFile(dbNewFile, "new db content\n"); + await dbRun("push", dbNewFile, "test/sync-db-newer.md"); + + await runMirror(); + + const content = await Deno.readTextFile(storageFile); + assert(content === "new db content\n", `storage NOT updated to match newer DB entry (got: '${content}')`); + console.log("[PASS] case 5: storage updated to match newer DB entry"); + }); + + // ------------------------------------------------------------------- + // Case 6: compatibility mode (vault path = DB path) + // ------------------------------------------------------------------- + await t.step("case 6: compatibility mode (omitted vault-path)", async () => { + const compatFile = workDir.join("vault", "compat.md"); + await Deno.writeTextFile(compatFile, "compat-content\n"); + + await runMirrorCompat(); + + const resultFile = workDir.join("case6-pull.txt"); + await compatRun("pull", "compat.md", resultFile); + const pulled = await Deno.readTextFile(resultFile); + assert(pulled === "compat-content\n", `Compatibility mode failed to sync file into DB (got: '${pulled}')`); + console.log("[PASS] case 6: compatibility mode works"); + }); +}); + +// --------------------------------------------------------------------------- +// Utility +// --------------------------------------------------------------------------- + +async function exists(path: string): Promise { + try { + await Deno.stat(path); + return true; + } catch { + return false; + } +} diff --git a/src/apps/cli/testdeno/test-p2p-host.ts b/src/apps/cli/testdeno/test-p2p-host.ts new file mode 100644 index 0000000..7c67c5f --- /dev/null +++ b/src/apps/cli/testdeno/test-p2p-host.ts @@ -0,0 +1,40 @@ +import { assert } from "@std/assert"; +import { TempDir } from "./helpers/temp.ts"; +import { initSettingsFile, applyP2pSettings } from "./helpers/settings.ts"; +import { startP2pRelay, stopP2pRelay, isLocalP2pRelay } from "./helpers/docker.ts"; +import { startCliInBackground } from "./helpers/backgroundCli.ts"; + +Deno.test("p2p-host: starts and becomes ready", async () => { + const relay = Deno.env.get("RELAY") ?? "ws://localhost:4000/"; + const roomId = Deno.env.get("ROOM_ID") ?? `room-${Date.now()}`; + const passphrase = Deno.env.get("PASSPHRASE") ?? "test"; + const appId = Deno.env.get("APP_ID") ?? "self-hosted-livesync-cli-tests"; + const useInternalRelay = Deno.env.get("USE_INTERNAL_RELAY") !== "0"; + + await using workDir = await TempDir.create("livesync-cli-p2p-host"); + const vaultDir = workDir.join("vault-host"); + const settingsFile = workDir.join("settings-host.json"); + await Deno.mkdir(vaultDir, { recursive: true }); + + let relayStarted = false; + if (useInternalRelay && isLocalP2pRelay(relay)) { + await startP2pRelay(); + relayStarted = true; + } + + try { + await initSettingsFile(settingsFile); + await applyP2pSettings(settingsFile, roomId, passphrase, appId, relay); + const host = startCliInBackground(vaultDir, "--settings", settingsFile, "p2p-host"); + try { + await host.waitUntilContains("P2P host is running", 20000); + assert(host.combined.includes("P2P host is running")); + } finally { + await host.stop(); + } + } finally { + if (relayStarted) { + await stopP2pRelay().catch(() => {}); + } + } +}); diff --git a/src/apps/cli/testdeno/test-p2p-peers-local-relay.ts b/src/apps/cli/testdeno/test-p2p-peers-local-relay.ts new file mode 100644 index 0000000..f73fe35 --- /dev/null +++ b/src/apps/cli/testdeno/test-p2p-peers-local-relay.ts @@ -0,0 +1,42 @@ +import { assert } from "@std/assert"; +import { TempDir } from "./helpers/temp.ts"; +import { initSettingsFile, applyP2pSettings, applyP2pTestTweaks } from "./helpers/settings.ts"; +import { startCliInBackground } from "./helpers/backgroundCli.ts"; +import { discoverPeer, maybeStartLocalRelay, stopLocalRelayIfStarted } from "./helpers/p2p.ts"; + +Deno.test("p2p-peers: discovers host through local relay", async () => { + const relay = Deno.env.get("RELAY") ?? "ws://localhost:4000/"; + const roomId = Deno.env.get("ROOM_ID") ?? `room-${Date.now()}`; + const passphrase = Deno.env.get("PASSPHRASE") ?? "test"; + const timeoutSeconds = Number(Deno.env.get("TIMEOUT_SECONDS") ?? "8"); + + await using workDir = await TempDir.create("livesync-cli-p2p-peers-local-relay"); + const hostVault = workDir.join("vault-host"); + const hostSettings = workDir.join("settings-host.json"); + const clientVault = workDir.join("vault"); + const clientSettings = workDir.join("settings.json"); + await Deno.mkdir(hostVault, { recursive: true }); + await Deno.mkdir(clientVault, { recursive: true }); + + const relayStarted = await maybeStartLocalRelay(relay); + try { + await initSettingsFile(hostSettings); + await initSettingsFile(clientSettings); + await applyP2pSettings(hostSettings, roomId, passphrase, "self-hosted-livesync-cli-tests", relay); + await applyP2pSettings(clientSettings, roomId, passphrase, "self-hosted-livesync-cli-tests", relay); + await applyP2pTestTweaks(hostSettings, "p2p-host", passphrase); + await applyP2pTestTweaks(clientSettings, "p2p-client", passphrase); + + const host = startCliInBackground(hostVault, "--settings", hostSettings, "p2p-host"); + try { + await host.waitUntilContains("P2P host is running", 20000); + const peer = await discoverPeer(clientVault, clientSettings, timeoutSeconds); + assert(peer.id.length > 0); + assert(peer.name.length > 0); + } finally { + await host.stop(); + } + } finally { + await stopLocalRelayIfStarted(relayStarted); + } +}); diff --git a/src/apps/cli/testdeno/test-p2p-sync.ts b/src/apps/cli/testdeno/test-p2p-sync.ts new file mode 100644 index 0000000..970b4e9 --- /dev/null +++ b/src/apps/cli/testdeno/test-p2p-sync.ts @@ -0,0 +1,59 @@ +import { assert } from "@std/assert"; +import { TempDir } from "./helpers/temp.ts"; +import { initSettingsFile, applyP2pSettings, applyP2pTestTweaks } from "./helpers/settings.ts"; +import { startCliInBackground } from "./helpers/backgroundCli.ts"; +import { discoverPeer, maybeStartLocalRelay, stopLocalRelayIfStarted } from "./helpers/p2p.ts"; +import { runCli } from "./helpers/cli.ts"; + +Deno.test("p2p-sync: discovers peer and completes sync", async () => { + const relay = Deno.env.get("RELAY") ?? "ws://localhost:4000/"; + const roomId = Deno.env.get("ROOM_ID") ?? `room-${Date.now()}`; + const passphrase = Deno.env.get("PASSPHRASE") ?? "test"; + const peersTimeout = Number(Deno.env.get("PEERS_TIMEOUT") ?? "12"); + const syncTimeout = Number(Deno.env.get("SYNC_TIMEOUT") ?? "15"); + + await using workDir = await TempDir.create("livesync-cli-p2p-sync"); + const hostVault = workDir.join("vault-host"); + const hostSettings = workDir.join("settings-host.json"); + const clientVault = workDir.join("vault-sync"); + const clientSettings = workDir.join("settings-sync.json"); + await Deno.mkdir(hostVault, { recursive: true }); + await Deno.mkdir(clientVault, { recursive: true }); + + const relayStarted = await maybeStartLocalRelay(relay); + try { + await initSettingsFile(hostSettings); + await initSettingsFile(clientSettings); + await applyP2pSettings(hostSettings, roomId, passphrase, "self-hosted-livesync-cli-tests", relay); + await applyP2pSettings(clientSettings, roomId, passphrase, "self-hosted-livesync-cli-tests", relay); + await applyP2pTestTweaks(hostSettings, "p2p-host", passphrase); + await applyP2pTestTweaks(clientSettings, "p2p-client", passphrase); + + const host = startCliInBackground(hostVault, "--settings", hostSettings, "p2p-host"); + try { + await host.waitUntilContains("P2P host is running", 20000); + const peer = await discoverPeer( + clientVault, + clientSettings, + peersTimeout, + Deno.env.get("TARGET_PEER") ?? undefined + ); + const syncResult = await runCli( + clientVault, + "--settings", + clientSettings, + "p2p-sync", + peer.id, + String(syncTimeout) + ); + assert( + syncResult.code === 0, + `p2p-sync failed\nstdout: ${syncResult.stdout}\nstderr: ${syncResult.stderr}` + ); + } finally { + await host.stop(); + } + } finally { + await stopLocalRelayIfStarted(relayStarted); + } +}); diff --git a/src/apps/cli/testdeno/test-p2p-three-nodes-conflict.ts b/src/apps/cli/testdeno/test-p2p-three-nodes-conflict.ts new file mode 100644 index 0000000..744692e --- /dev/null +++ b/src/apps/cli/testdeno/test-p2p-three-nodes-conflict.ts @@ -0,0 +1,118 @@ +import { assert } from "@std/assert"; +import { TempDir } from "./helpers/temp.ts"; +import { applyP2pSettings, initSettingsFile } from "./helpers/settings.ts"; +import { startCliInBackground } from "./helpers/backgroundCli.ts"; +import { discoverPeer, maybeStartLocalRelay, stopLocalRelayIfStarted } from "./helpers/p2p.ts"; +import { jsonStringField, runCliOrFail, runCliWithInputOrFail, sanitiseCatStdout } from "./helpers/cli.ts"; + +Deno.test("p2p: three nodes detect and resolve conflicts", async () => { + const relay = Deno.env.get("RELAY") ?? "ws://localhost:4000/"; + const roomId = `${Deno.env.get("ROOM_ID_PREFIX") ?? "p2p-room"}-${Date.now()}`; + const passphrase = `${Deno.env.get("PASSPHRASE_PREFIX") ?? "p2p-pass"}-${Date.now()}`; + const appId = Deno.env.get("APP_ID") ?? "self-hosted-livesync-cli-tests"; + const peersTimeout = Number(Deno.env.get("PEERS_TIMEOUT") ?? "10"); + const syncTimeout = Number(Deno.env.get("SYNC_TIMEOUT") ?? "15"); + + await using workDir = await TempDir.create("livesync-cli-p2p-3nodes"); + const vaultA = workDir.join("vault-a"); + const vaultB = workDir.join("vault-b"); + const vaultC = workDir.join("vault-c"); + const settingsA = workDir.join("settings-a.json"); + const settingsB = workDir.join("settings-b.json"); + const settingsC = workDir.join("settings-c.json"); + await Deno.mkdir(vaultA, { recursive: true }); + await Deno.mkdir(vaultB, { recursive: true }); + await Deno.mkdir(vaultC, { recursive: true }); + + const relayStarted = await maybeStartLocalRelay(relay); + try { + for (const settings of [settingsA, settingsB, settingsC]) { + await initSettingsFile(settings); + await applyP2pSettings(settings, roomId, passphrase, appId, relay); + } + + const host = startCliInBackground(vaultA, "--settings", settingsA, "p2p-host"); + try { + await host.waitUntilContains("P2P host is running", 20000); + const peerFromB = await discoverPeer(vaultB, settingsB, peersTimeout); + const peerFromC = await discoverPeer(vaultC, settingsC, peersTimeout); + const targetPath = "p2p/conflicted-from-two-clients.txt"; + + await runCliWithInputOrFail("from-client-b-v1\n", vaultB, "--settings", settingsB, "put", targetPath); + await runCliOrFail(vaultB, "--settings", settingsB, "p2p-sync", peerFromB.id, String(syncTimeout)); + await runCliOrFail(vaultC, "--settings", settingsC, "p2p-sync", peerFromC.id, String(syncTimeout)); + + let visibleOnC = ""; + for (let i = 0; i < 5; i++) { + try { + visibleOnC = sanitiseCatStdout( + await runCliOrFail(vaultC, "--settings", settingsC, "cat", targetPath) + ).trimEnd(); + if (visibleOnC === "from-client-b-v1") break; + } catch { + // retry below + } + await runCliOrFail(vaultC, "--settings", settingsC, "p2p-sync", peerFromC.id, String(syncTimeout)); + } + assert(visibleOnC === "from-client-b-v1", `C should see file created by B, got: ${visibleOnC}`); + + await runCliWithInputOrFail("from-client-b-v2\n", vaultB, "--settings", settingsB, "put", targetPath); + await runCliWithInputOrFail("from-client-c-v2\n", vaultC, "--settings", settingsC, "put", targetPath); + + const [syncB, syncC] = await Promise.all([ + runCliOrFail(vaultB, "--settings", settingsB, "p2p-sync", peerFromB.id, String(syncTimeout)), + runCliOrFail(vaultC, "--settings", settingsC, "p2p-sync", peerFromC.id, String(syncTimeout)), + ]); + void syncB; + void syncC; + + await runCliOrFail(vaultB, "--settings", settingsB, "p2p-sync", peerFromB.id, String(syncTimeout)); + await runCliOrFail(vaultC, "--settings", settingsC, "p2p-sync", peerFromC.id, String(syncTimeout)); + + const infoBBefore = await runCliOrFail(vaultB, "--settings", settingsB, "info", targetPath); + const conflictsBBefore = jsonStringField(infoBBefore, "conflicts"); + const keepRevB = jsonStringField(infoBBefore, "revision"); + assert( + conflictsBBefore !== "N/A" && conflictsBBefore.length > 0, + `expected conflicts on B\n${infoBBefore}` + ); + assert(keepRevB.length > 0, `could not read revision on B\n${infoBBefore}`); + + const infoCBefore = await runCliOrFail(vaultC, "--settings", settingsC, "info", targetPath); + const conflictsCBefore = jsonStringField(infoCBefore, "conflicts"); + const keepRevC = jsonStringField(infoCBefore, "revision"); + assert( + conflictsCBefore !== "N/A" && conflictsCBefore.length > 0, + `expected conflicts on C\n${infoCBefore}` + ); + assert(keepRevC.length > 0, `could not read revision on C\n${infoCBefore}`); + + await runCliOrFail(vaultB, "--settings", settingsB, "resolve", targetPath, keepRevB); + await runCliOrFail(vaultC, "--settings", settingsC, "resolve", targetPath, keepRevC); + + const infoBAfter = await runCliOrFail(vaultB, "--settings", settingsB, "info", targetPath); + const infoCAfter = await runCliOrFail(vaultC, "--settings", settingsC, "info", targetPath); + assert(jsonStringField(infoBAfter, "conflicts") === "N/A", `conflict still remains on B\n${infoBAfter}`); + assert(jsonStringField(infoCAfter, "conflicts") === "N/A", `conflict still remains on C\n${infoCAfter}`); + + const finalContentB = sanitiseCatStdout( + await runCliOrFail(vaultB, "--settings", settingsB, "cat", targetPath) + ).trimEnd(); + const finalContentC = sanitiseCatStdout( + await runCliOrFail(vaultC, "--settings", settingsC, "cat", targetPath) + ).trimEnd(); + assert( + finalContentB === "from-client-b-v2" || finalContentB === "from-client-c-v2", + `unexpected final content on B: ${finalContentB}` + ); + assert( + finalContentC === "from-client-b-v2" || finalContentC === "from-client-c-v2", + `unexpected final content on C: ${finalContentC}` + ); + } finally { + await host.stop(); + } + } finally { + await stopLocalRelayIfStarted(relayStarted); + } +}); diff --git a/src/apps/cli/testdeno/test-p2p-upload-download-repro.ts b/src/apps/cli/testdeno/test-p2p-upload-download-repro.ts new file mode 100644 index 0000000..ba07fd5 --- /dev/null +++ b/src/apps/cli/testdeno/test-p2p-upload-download-repro.ts @@ -0,0 +1,111 @@ +import { TempDir } from "./helpers/temp.ts"; +import { applyP2pSettings, applyP2pTestTweaks, initSettingsFile } from "./helpers/settings.ts"; +import { startCliInBackground } from "./helpers/backgroundCli.ts"; +import { discoverPeer, maybeStartLocalRelay, stopLocalRelayIfStarted } from "./helpers/p2p.ts"; +import { assertFilesEqual, runCliOrFail } from "./helpers/cli.ts"; + +async function writeFilledFile(path: string, size: number, byte: number): Promise { + const data = new Uint8Array(size); + data.fill(byte); + await Deno.writeFile(path, data); +} + +Deno.test("p2p: upload/download reproduction scenario", async () => { + const relay = Deno.env.get("RELAY") ?? "ws://localhost:4000/"; + const appId = Deno.env.get("APP_ID") ?? "self-hosted-livesync-cli-tests"; + const peersTimeout = Number(Deno.env.get("PEERS_TIMEOUT") ?? "20"); + const syncTimeout = Number(Deno.env.get("SYNC_TIMEOUT") ?? "240"); + const roomId = `p2p-room-${Date.now()}`; + const passphrase = `p2p-pass-${Date.now()}`; + + await using workDir = await TempDir.create("livesync-cli-p2p-upload-download"); + const vaultHost = workDir.join("vault-host"); + const vaultUp = workDir.join("vault-up"); + const vaultDown = workDir.join("vault-down"); + const settingsHost = workDir.join("settings-host.json"); + const settingsUp = workDir.join("settings-up.json"); + const settingsDown = workDir.join("settings-down.json"); + for (const dir of [vaultHost, vaultUp, vaultDown]) { + await Deno.mkdir(dir, { recursive: true }); + } + + const relayStarted = await maybeStartLocalRelay(relay); + try { + for (const settings of [settingsHost, settingsUp, settingsDown]) { + await initSettingsFile(settings); + await applyP2pSettings(settings, roomId, passphrase, appId, relay, "~.*"); + } + await applyP2pTestTweaks(settingsHost, "p2p-cli-host", passphrase); + await applyP2pTestTweaks(settingsUp, `p2p-cli-upload-${Date.now()}`, passphrase); + await applyP2pTestTweaks(settingsDown, `p2p-cli-download-${Date.now()}`, passphrase); + + const host = startCliInBackground(vaultHost, "--settings", settingsHost, "p2p-host"); + try { + await host.waitUntilContains("P2P host is running", 20000); + const uploadPeer = await discoverPeer(vaultUp, settingsUp, peersTimeout); + + const storeText = workDir.join("store-file.md"); + const diffA = workDir.join("test-diff-1.md"); + const diffB = workDir.join("test-diff-2.md"); + const diffC = workDir.join("test-diff-3.md"); + await Deno.writeTextFile(storeText, "Hello, World!\n"); + await Deno.writeTextFile(diffA, "Content A\n"); + await Deno.writeTextFile(diffB, "Content B\n"); + await Deno.writeTextFile(diffC, "Content C\n"); + await runCliOrFail(vaultUp, "--settings", settingsUp, "push", storeText, "p2p/store-file.md"); + await runCliOrFail(vaultUp, "--settings", settingsUp, "push", diffA, "p2p/test-diff-1.md"); + await runCliOrFail(vaultUp, "--settings", settingsUp, "push", diffB, "p2p/test-diff-2.md"); + await runCliOrFail(vaultUp, "--settings", settingsUp, "push", diffC, "p2p/test-diff-3.md"); + + const large100k = workDir.join("large-100k.txt"); + const large1m = workDir.join("large-1m.txt"); + const binary100k = workDir.join("binary-100k.bin"); + const binary5m = workDir.join("binary-5m.bin"); + await Deno.writeTextFile(large100k, "a".repeat(100000)); + await Deno.writeTextFile(large1m, "b".repeat(1000000)); + await writeFilledFile(binary100k, 100000, 0x5a); + await writeFilledFile(binary5m, 5000000, 0x7c); + await runCliOrFail(vaultUp, "--settings", settingsUp, "push", large100k, "p2p/large-100000.md"); + await runCliOrFail(vaultUp, "--settings", settingsUp, "push", large1m, "p2p/large-1000000.md"); + await runCliOrFail(vaultUp, "--settings", settingsUp, "push", binary100k, "p2p/binary-100000.bin"); + await runCliOrFail(vaultUp, "--settings", settingsUp, "push", binary5m, "p2p/binary-5000000.bin"); + + await runCliOrFail(vaultUp, "--settings", settingsUp, "p2p-sync", uploadPeer.id, String(syncTimeout)); + await runCliOrFail(vaultUp, "--settings", settingsUp, "p2p-sync", uploadPeer.id, String(syncTimeout)); + + const downloadPeer = await discoverPeer(vaultDown, settingsDown, peersTimeout); + await runCliOrFail(vaultDown, "--settings", settingsDown, "p2p-sync", downloadPeer.id, String(syncTimeout)); + await runCliOrFail(vaultDown, "--settings", settingsDown, "p2p-sync", downloadPeer.id, String(syncTimeout)); + + const downStoreText = workDir.join("down-store-file.md"); + const downDiffA = workDir.join("down-test-diff-1.md"); + const downDiffB = workDir.join("down-test-diff-2.md"); + const downDiffC = workDir.join("down-test-diff-3.md"); + const downLarge100k = workDir.join("down-large-100k.txt"); + const downLarge1m = workDir.join("down-large-1m.txt"); + const downBinary100k = workDir.join("down-binary-100k.bin"); + const downBinary5m = workDir.join("down-binary-5m.bin"); + await runCliOrFail(vaultDown, "--settings", settingsDown, "pull", "p2p/store-file.md", downStoreText); + await runCliOrFail(vaultDown, "--settings", settingsDown, "pull", "p2p/test-diff-1.md", downDiffA); + await runCliOrFail(vaultDown, "--settings", settingsDown, "pull", "p2p/test-diff-2.md", downDiffB); + await runCliOrFail(vaultDown, "--settings", settingsDown, "pull", "p2p/test-diff-3.md", downDiffC); + await runCliOrFail(vaultDown, "--settings", settingsDown, "pull", "p2p/large-100000.md", downLarge100k); + await runCliOrFail(vaultDown, "--settings", settingsDown, "pull", "p2p/large-1000000.md", downLarge1m); + await runCliOrFail(vaultDown, "--settings", settingsDown, "pull", "p2p/binary-100000.bin", downBinary100k); + await runCliOrFail(vaultDown, "--settings", settingsDown, "pull", "p2p/binary-5000000.bin", downBinary5m); + + await assertFilesEqual(storeText, downStoreText, "store-file mismatch"); + await assertFilesEqual(diffA, downDiffA, "test-diff-1 mismatch"); + await assertFilesEqual(diffB, downDiffB, "test-diff-2 mismatch"); + await assertFilesEqual(diffC, downDiffC, "test-diff-3 mismatch"); + await assertFilesEqual(large100k, downLarge100k, "large-100000 mismatch"); + await assertFilesEqual(large1m, downLarge1m, "large-1000000 mismatch"); + await assertFilesEqual(binary100k, downBinary100k, "binary-100000 mismatch"); + await assertFilesEqual(binary5m, downBinary5m, "binary-5000000 mismatch"); + } finally { + await host.stop(); + } + } finally { + await stopLocalRelayIfStarted(relayStarted); + } +}); diff --git a/src/apps/cli/testdeno/test-push-pull.ts b/src/apps/cli/testdeno/test-push-pull.ts new file mode 100644 index 0000000..3b04a1e --- /dev/null +++ b/src/apps/cli/testdeno/test-push-pull.ts @@ -0,0 +1,64 @@ +/** + * Deno port of test-push-pull-linux.sh + * + * Requires CouchDB connection details either via environment variables or a + * .test.env file. If neither is present the test logs a warning and the + * CLI will likely fail at the push step. + * + * Run: + * deno test -A test-push-pull.ts + * + * With explicit CouchDB: + * COUCHDB_URI=http://127.0.0.1:5984 \ + * COUCHDB_USER=admin \ + * COUCHDB_PASSWORD=password \ + * COUCHDB_DBNAME=livesync-test \ + * deno test -A test-push-pull.ts + */ + +import { join } from "@std/path"; +import { assertEquals } from "@std/assert"; +import { TempDir } from "./helpers/temp.ts"; +import { runCliOrFail } from "./helpers/cli.ts"; +import { applyCouchdbSettings, initSettingsFile } from "./helpers/settings.ts"; + +const REMOTE_PATH = Deno.env.get("REMOTE_PATH") ?? "test/push-pull.txt"; + +Deno.test("push/pull roundtrip", async () => { + await using workDir = await TempDir.create("livesync-cli-push-pull"); + + const settingsFile = workDir.join("data.json"); + const vaultDir = workDir.join("vault"); + await Deno.mkdir(join(vaultDir, "test"), { recursive: true }); + + await initSettingsFile(settingsFile); + + const uri = Deno.env.get("COUCHDB_URI") ?? ""; + const user = Deno.env.get("COUCHDB_USER") ?? ""; + const password = Deno.env.get("COUCHDB_PASSWORD") ?? ""; + const dbname = Deno.env.get("COUCHDB_DBNAME") ?? ""; + + if (uri && user && password && dbname) { + console.log("[INFO] applying CouchDB env vars to settings"); + await applyCouchdbSettings(settingsFile, uri, user, password, dbname); + } else { + console.warn( + "[WARN] CouchDB env vars not fully set — push/pull may fail unless the generated settings already contain connection details" + ); + } + + const srcFile = workDir.join("push-source.txt"); + const pulledFile = workDir.join("pull-result.txt"); + const content = `push-pull-test ${new Date().toISOString()}\n`; + await Deno.writeTextFile(srcFile, content); + + console.log(`[INFO] push -> ${REMOTE_PATH}`); + await runCliOrFail(vaultDir, "--settings", settingsFile, "push", srcFile, REMOTE_PATH); + + console.log(`[INFO] pull <- ${REMOTE_PATH}`); + await runCliOrFail(vaultDir, "--settings", settingsFile, "pull", REMOTE_PATH, pulledFile); + + const pulled = await Deno.readTextFile(pulledFile); + assertEquals(content, pulled, "push/pull roundtrip content mismatch"); + console.log("[PASS] push/pull roundtrip matched"); +}); diff --git a/src/apps/cli/testdeno/test-setup-put-cat.ts b/src/apps/cli/testdeno/test-setup-put-cat.ts new file mode 100644 index 0000000..f2a7696 --- /dev/null +++ b/src/apps/cli/testdeno/test-setup-put-cat.ts @@ -0,0 +1,214 @@ +/** + * Deno port of test-setup-put-cat-linux.sh + * + * Tests all local-DB file operations that require no external remote: + * setup / + * push / cat / ls / info / rm / resolve / cat-rev / pull-rev + * + * Run (no external services needed): + * deno test -A test-setup-put-cat.ts + */ + +import { join } from "@std/path"; +import { assertEquals, assert } from "@std/assert"; +import { TempDir } from "./helpers/temp.ts"; +import { runCli, runCliOrFail, runCliWithInput, sanitiseCatStdout } from "./helpers/cli.ts"; +import { generateSetupUriFromSettings, initSettingsFile } from "./helpers/settings.ts"; + +const REMOTE_PATH = Deno.env.get("REMOTE_PATH") ?? "test/setup-put-cat.txt"; +const SETUP_PASSPHRASE = Deno.env.get("SETUP_PASSPHRASE") ?? "setup-passphrase"; + +Deno.test("CLI file operations: push / cat / ls / info / rm / resolve / cat-rev / pull-rev", async (t) => { + await using workDir = await TempDir.create("livesync-cli-setup-put-cat"); + + const settingsFile = workDir.join("data.json"); + const vaultDir = workDir.join("vault"); + await Deno.mkdir(join(vaultDir, "test"), { recursive: true }); + + await initSettingsFile(settingsFile); + + const setupUri = await generateSetupUriFromSettings(settingsFile, SETUP_PASSPHRASE); + const setupResult = await runCliWithInput( + `${SETUP_PASSPHRASE}\n`, + vaultDir, + "--settings", + settingsFile, + "setup", + setupUri + ); + assert(setupResult.code === 0, `setup command exited with ${setupResult.code}\n${setupResult.combined}`); + assert( + setupResult.combined.includes("[Command] setup ->"), + `setup command did not execute expected code path\n${setupResult.combined}` + ); + + const run = (...args: string[]) => runCliOrFail(vaultDir, "--settings", settingsFile, ...args); + + // ------------------------------------------------------------------ + // push / cat roundtrip + // ------------------------------------------------------------------ + await t.step("push/cat roundtrip", async () => { + const srcFile = workDir.join("put-source.txt"); + const content = `setup-put-cat-test ${new Date().toISOString()}\nline-2\n`; + await Deno.writeTextFile(srcFile, content); + + console.log(`[INFO] push -> ${REMOTE_PATH}`); + await runCliWithInput(content, vaultDir, "--settings", settingsFile, "put", REMOTE_PATH); + + console.log(`[INFO] cat <- ${REMOTE_PATH}`); + const rawOutput = await run("cat", REMOTE_PATH); + const catOutput = sanitiseCatStdout(rawOutput); + + assertEquals(content, catOutput, "push/cat roundtrip content mismatch"); + console.log("[PASS] push/cat roundtrip matched"); + }); + + // ------------------------------------------------------------------ + // ls: single file + // ------------------------------------------------------------------ + await t.step("ls output format (single file)", async () => { + const lsOutput = await run("ls", REMOTE_PATH); + const line = lsOutput + .trim() + .split("\n") + .find((l) => l.startsWith(REMOTE_PATH + "\t")); + assert(line, `ls output did not include ${REMOTE_PATH}`); + + const [lsPath, lsSize, lsMtime, lsRev] = line.split("\t"); + assertEquals(lsPath, REMOTE_PATH, "ls path column mismatch"); + assert(/^\d+$/.test(lsSize), `ls size not numeric: ${lsSize}`); + assert(/^\d+$/.test(lsMtime), `ls mtime not numeric: ${lsMtime}`); + assert(lsRev?.length > 0, "ls revision column is empty"); + console.log("[PASS] ls output format matched"); + }); + + // ------------------------------------------------------------------ + // ls: prefix filter and sort order + // ------------------------------------------------------------------ + await t.step("ls prefix filter and sort order", async () => { + await runCliWithInput("file-a\n", vaultDir, "--settings", settingsFile, "put", "test/a-first.txt"); + await runCliWithInput("file-z\n", vaultDir, "--settings", settingsFile, "put", "test/z-last.txt"); + + const lsOut = await run("ls", "test/"); + const lines = lsOut.trim().split("\n").filter(Boolean); + assert(lines.length >= 3, "ls prefix output expected at least 3 rows"); + + // Verify sorted ascending by path + const paths = lines.map((l) => l.split("\t")[0]); + for (let i = 1; i < paths.length; i++) { + assert(paths[i - 1] <= paths[i], `ls output not sorted: ${paths[i - 1]} > ${paths[i]}`); + } + assert( + lines.some((l) => l.startsWith("test/a-first.txt\t")), + "ls prefix output missing test/a-first.txt" + ); + assert( + lines.some((l) => l.startsWith("test/z-last.txt\t")), + "ls prefix output missing test/z-last.txt" + ); + console.log("[PASS] ls prefix and sorting matched"); + }); + + // ------------------------------------------------------------------ + // ls: no-match prefix returns empty output + // ------------------------------------------------------------------ + await t.step("ls no-match prefix returns empty", async () => { + const lsOut = await run("ls", "no-such-prefix/"); + assertEquals(lsOut.trim(), "", "ls no-match prefix should produce empty output"); + console.log("[PASS] ls no-match prefix matched"); + }); + + // ------------------------------------------------------------------ + // info: JSON output format + // ------------------------------------------------------------------ + await t.step("info output JSON format", async () => { + const infoOut = await run("info", REMOTE_PATH); + let data: Record; + try { + data = JSON.parse(infoOut); + } catch { + throw new Error(`info output is not valid JSON:\n${infoOut}`); + } + assertEquals(data.path, REMOTE_PATH, "info .path mismatch"); + assertEquals(data.filename, REMOTE_PATH.split("/").at(-1), "info .filename mismatch"); + assert(typeof data.size === "number" && data.size >= 0, `info .size invalid: ${data.size}`); + assert(typeof data.chunks === "number" && (data.chunks as number) >= 1, `info .chunks invalid: ${data.chunks}`); + assertEquals(data.conflicts, "N/A", "info .conflicts should be N/A"); + console.log("[PASS] info output format matched"); + }); + + // ------------------------------------------------------------------ + // info: non-existent path exits non-zero + // ------------------------------------------------------------------ + await t.step("info non-existent path returns non-zero", async () => { + const r = await runCli(vaultDir, "--settings", settingsFile, "info", "no-such-file.md"); + assert(r.code !== 0, "info on non-existent file should exit non-zero"); + console.log("[PASS] info non-existent path returns non-zero"); + }); + + // ------------------------------------------------------------------ + // rm: removes file from ls and makes cat fail + // ------------------------------------------------------------------ + await t.step("rm removes target from ls and cat", async () => { + await run("rm", "test/z-last.txt"); + + const catResult = await runCli(vaultDir, "--settings", settingsFile, "cat", "test/z-last.txt"); + assert(catResult.code !== 0, "rm target should not be readable by cat"); + + const lsOut = await run("ls", "test/"); + assert(!lsOut.includes("test/z-last.txt\t"), "rm target should not appear in ls output"); + console.log("[PASS] rm removed target from visible entries"); + }); + + // ------------------------------------------------------------------ + // resolve: accepts current revision, rejects invalid revision + // ------------------------------------------------------------------ + await t.step("resolve: valid and invalid revisions", async () => { + const lsLine = (await run("ls", "test/a-first.txt")).trim().split("\n")[0]; + assert(lsLine, "could not fetch revision for resolve test"); + const rev = lsLine.split("\t")[3]; + assert(rev?.length > 0, "revision was empty for resolve test"); + + await run("resolve", "test/a-first.txt", rev); + console.log("[PASS] resolve accepted current revision"); + + const badR = await runCli(vaultDir, "--settings", settingsFile, "resolve", "test/a-first.txt", "9-no-such-rev"); + assert(badR.code !== 0, "resolve with non-existent revision should exit non-zero"); + console.log("[PASS] resolve non-existent revision returns non-zero"); + }); + + // ------------------------------------------------------------------ + // cat-rev / pull-rev: retrieve a past revision + // ------------------------------------------------------------------ + await t.step("cat-rev / pull-rev: retrieve past revision", async () => { + const revPath = "test/revision-history.txt"; + await runCliWithInput("revision-v1\n", vaultDir, "--settings", settingsFile, "put", revPath); + await runCliWithInput("revision-v2\n", vaultDir, "--settings", settingsFile, "put", revPath); + await runCliWithInput("revision-v3\n", vaultDir, "--settings", settingsFile, "put", revPath); + + const infoOut = await run("info", revPath); + const infoData = JSON.parse(infoOut) as { + revisions?: string[]; + }; + const revisions = Array.isArray(infoData.revisions) ? infoData.revisions : []; + const pastRev = revisions.find((r): r is string => typeof r === "string" && r !== "N/A"); + assert(pastRev, "info output did not include any past revision"); + + const catRevOut = await run("cat-rev", revPath, pastRev); + const catRevClean = sanitiseCatStdout(catRevOut); + assert( + catRevClean === "revision-v1\n" || catRevClean === "revision-v2\n", + `cat-rev output did not match expected past revision:\n${catRevClean}` + ); + console.log("[PASS] cat-rev matched one of the past revisions from info"); + + const pullRevFile = workDir.join("rev-pull-output.txt"); + await run("pull-rev", revPath, pullRevFile, pastRev); + const pullRevContent = await Deno.readTextFile(pullRevFile); + assert( + pullRevContent === "revision-v1\n" || pullRevContent === "revision-v2\n", + `pull-rev output did not match expected past revision:\n${pullRevContent}` + ); + console.log("[PASS] pull-rev matched one of the past revisions from info"); + }); +}); diff --git a/src/apps/cli/testdeno/test-sync-locked-remote.ts b/src/apps/cli/testdeno/test-sync-locked-remote.ts new file mode 100644 index 0000000..1dfc568 --- /dev/null +++ b/src/apps/cli/testdeno/test-sync-locked-remote.ts @@ -0,0 +1,97 @@ +/** + * Deno port of test-sync-locked-remote-linux.sh + * + * Verifies CLI sync behaviour when the remote milestone document is unlocked + * versus locked. + */ + +import { assert, assertStringIncludes } from "@std/assert"; +import { join } from "@std/path"; +import { loadEnvFile } from "./helpers/env.ts"; +import { TempDir } from "./helpers/temp.ts"; +import { runCli } from "./helpers/cli.ts"; +import { applyCouchdbSettings, initSettingsFile } from "./helpers/settings.ts"; +import { createCouchdbDatabase, startCouchdb, stopCouchdb, updateCouchdbDoc } from "./helpers/docker.ts"; + +const TEST_ENV = join(import.meta.dirname!, "..", ".test.env"); +const MILESTONE_DOC = "_local/obsydian_livesync_milestone"; + +function requireEnv(env: Record, key: string): string { + const value = env[key]?.trim(); + if (!value) { + throw new Error(`Required env var is missing: ${key}`); + } + return value; +} + +Deno.test("sync: actionable error against locked remote DB", async () => { + const env = await loadEnvFile(TEST_ENV); + const couchdbUri = requireEnv(env, "hostname").replace(/\/$/, ""); + const couchdbUser = requireEnv(env, "username"); + const couchdbPassword = requireEnv(env, "password"); + const dbPrefix = requireEnv(env, "dbname"); + const dbname = `${dbPrefix}-locked-${Date.now()}-${Math.floor(Math.random() * 100000)}`; + + await using workDir = await TempDir.create("livesync-cli-locked-test"); + const vaultDir = workDir.join("vault"); + const settingsFile = workDir.join("settings.json"); + await Deno.mkdir(vaultDir, { recursive: true }); + + const shouldStartDocker = Deno.env.get("LIVESYNC_START_DOCKER") !== "0"; + const keepDocker = Deno.env.get("LIVESYNC_DEBUG_KEEP_DOCKER") === "1"; + + if (shouldStartDocker) { + console.log(`[INFO] starting CouchDB and creating test database: ${dbname}`); + await startCouchdb(couchdbUri, couchdbUser, couchdbPassword, dbname); + } else { + console.log(`[INFO] using existing CouchDB and creating test database: ${dbname}`); + await createCouchdbDatabase(couchdbUri, couchdbUser, couchdbPassword, dbname); + } + + try { + await initSettingsFile(settingsFile); + await applyCouchdbSettings(settingsFile, couchdbUri, couchdbUser, couchdbPassword, dbname, true); + + console.log("[CASE] initial sync to create milestone document"); + const initialSync = await runCli(vaultDir, "--settings", settingsFile, "sync"); + assert( + initialSync.code === 0, + `initial sync failed\nstdout: ${initialSync.stdout}\nstderr: ${initialSync.stderr}` + ); + + const updateMilestone = async (locked: boolean) => { + await updateCouchdbDoc(couchdbUri, couchdbUser, couchdbPassword, `${dbname}/${MILESTONE_DOC}`, (doc) => ({ + ...doc, + locked, + accepted_nodes: [], + })); + }; + + console.log("[CASE] sync should succeed when remote is not locked"); + await updateMilestone(false); + const unlockedSync = await runCli(vaultDir, "--settings", settingsFile, "sync"); + assert( + unlockedSync.code === 0, + `sync should succeed when remote is not locked\nstdout: ${unlockedSync.stdout}\nstderr: ${unlockedSync.stderr}` + ); + assert( + !unlockedSync.combined.includes("The remote database is locked"), + `locked error should not appear when remote is not locked\n${unlockedSync.combined}` + ); + console.log("[PASS] unlocked remote DB syncs successfully"); + + console.log("[CASE] sync should fail with actionable error when remote is locked"); + await updateMilestone(true); + const lockedSync = await runCli(vaultDir, "--settings", settingsFile, "sync"); + assert( + lockedSync.code !== 0, + `sync should fail when remote is locked\nstdout: ${lockedSync.stdout}\nstderr: ${lockedSync.stderr}` + ); + assertStringIncludes(lockedSync.combined, "The remote database is locked and this device is not yet accepted"); + console.log("[PASS] locked remote DB produces actionable CLI error"); + } finally { + if (shouldStartDocker && !keepDocker) { + await stopCouchdb().catch(() => {}); + } + } +}); diff --git a/src/apps/cli/testdeno/test-sync-two-local-databases.ts b/src/apps/cli/testdeno/test-sync-two-local-databases.ts new file mode 100644 index 0000000..c14ee08 --- /dev/null +++ b/src/apps/cli/testdeno/test-sync-two-local-databases.ts @@ -0,0 +1,287 @@ +/** + * Deno port of test-sync-two-local-databases-linux.sh + * + * Tests two-vault synchronisation via CouchDB including conflict detection + * and resolution. + * + * Requires CouchDB connection details. Provide them via environment variables + * OR place a .test.env file at src/apps/cli/.test.env. + * + * By default, a CouchDB Docker container is started automatically + * (LIVESYNC_START_DOCKER=1). Set LIVESYNC_START_DOCKER=0 to use an existing + * CouchDB instance instead. + * + * Run: + * deno test -A test-sync-two-local-databases.ts + * + * With an existing CouchDB: + * COUCHDB_URI=http://127.0.0.1:5984 \ + * COUCHDB_USER=admin \ + * COUCHDB_PASSWORD=password \ + * COUCHDB_DBNAME=livesync-test \ + * LIVESYNC_START_DOCKER=0 \ + * deno test -A test-sync-two-local-databases.ts + */ + +import { join } from "@std/path"; +import { assertEquals, assert } from "@std/assert"; +import { TempDir } from "./helpers/temp.ts"; +import { CLI_DIR, runCliOrFail, jsonFieldIsNa } from "./helpers/cli.ts"; +import { applyCouchdbSettings, initSettingsFile } from "./helpers/settings.ts"; +import { startCouchdb, stopCouchdb } from "./helpers/docker.ts"; +import { loadEnvFile } from "./helpers/env.ts"; + +// --------------------------------------------------------------------------- +// Load configuration +// --------------------------------------------------------------------------- + +async function resolveConfig(): Promise<{ + uri: string; + user: string; + password: string; + baseDbname: string; +} | null> { + let env: Record = {}; + + // 1. Explicit environment variables take priority + if (Deno.env.get("COUCHDB_URI")) { + env = Object.fromEntries(Deno.env.toObject()); + } else { + // 2. TEST_ENV_FILE env var + const envFile = Deno.env.get("TEST_ENV_FILE") ?? join(CLI_DIR, ".test.env"); + try { + env = await loadEnvFile(envFile); + } catch { + return null; // no config available — skip + } + } + + const uri = (env["COUCHDB_URI"] ?? env["hostname"] ?? "").replace(/\/$/, ""); + const user = env["COUCHDB_USER"] ?? env["username"] ?? ""; + const password = env["COUCHDB_PASSWORD"] ?? env["password"] ?? ""; + const baseDbname = env["COUCHDB_DBNAME"] ?? env["dbname"] ?? "livesync-test"; + + if (!uri || !user || !password) return null; + return { uri, user, password, baseDbname }; +} + +const config = await resolveConfig(); +const START_DOCKER = Deno.env.get("LIVESYNC_START_DOCKER") !== "0"; +const KEEP_DOCKER = Deno.env.get("LIVESYNC_DEBUG_KEEP_DOCKER") === "1"; +const SYNC_RETRY = Number(Deno.env.get("LIVESYNC_SYNC_RETRY") ?? "8"); + +// Provide a sane default for flaky remote connectivity in Docker-on-WSL +// environments. Users can override explicitly if needed. +if (!Deno.env.has("LIVESYNC_CLI_RETRY")) { + Deno.env.set("LIVESYNC_CLI_RETRY", "2"); +} + +// --------------------------------------------------------------------------- +// Test suite +// --------------------------------------------------------------------------- + +Deno.test( + { + name: "sync two local databases: sync + conflict detection + resolution", + ignore: config === null, + }, + async (t) => { + if (!config) return; // narrowing for TypeScript + + const suffix = `${Date.now()}-${Math.floor(Math.random() * 65535)}`; + const dbname = `${config.baseDbname}-${suffix}`; + + await using workDir = await TempDir.create("livesync-cli-two-db-test"); + + // ------------------------------------------------------------------ + // Docker lifecycle + // ------------------------------------------------------------------ + if (START_DOCKER) { + await startCouchdb(config.uri, config.user, config.password, dbname); + } + + try { + await runSuite(t, workDir, config, dbname); + } finally { + if (START_DOCKER && !KEEP_DOCKER) { + await stopCouchdb().catch(() => {}); + } + if (START_DOCKER && KEEP_DOCKER) { + console.log("[INFO] LIVESYNC_DEBUG_KEEP_DOCKER=1, keeping couchdb-test container"); + } + console.log(`[INFO] test database '${dbname}' is preserved for debugging.`); + } + } +); + +// --------------------------------------------------------------------------- +// Suite implementation +// --------------------------------------------------------------------------- + +async function runSuite( + t: Deno.TestContext, + workDir: TempDir, + config: { uri: string; user: string; password: string }, + dbname: string +): Promise { + const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + const runWithRetry = async (label: string, fn: () => Promise, retries = SYNC_RETRY): Promise => { + let lastErr: unknown; + for (let i = 0; i <= retries; i++) { + try { + return await fn(); + } catch (err) { + lastErr = err; + if (i === retries) break; + const delayMs = 500 * (i + 1); + console.warn(`[WARN] ${label} failed, retrying (${i + 1}/${retries}) in ${delayMs}ms`); + await sleep(delayMs); + } + } + throw lastErr; + }; + + const vaultA = workDir.join("vault-a"); + const vaultB = workDir.join("vault-b"); + const settingsA = workDir.join("a-settings.json"); + const settingsB = workDir.join("b-settings.json"); + await Deno.mkdir(vaultA, { recursive: true }); + await Deno.mkdir(vaultB, { recursive: true }); + + await initSettingsFile(settingsA); + await initSettingsFile(settingsB); + + const applySettings = async (f: string) => + applyCouchdbSettings(f, config.uri, config.user, config.password, dbname, /* liveSync */ true); + await applySettings(settingsA); + await applySettings(settingsB); + + const runA = (...args: string[]) => runCliOrFail(vaultA, "--settings", settingsA, ...args); + const runB = (...args: string[]) => runCliOrFail(vaultB, "--settings", settingsB, ...args); + + const syncA = () => runWithRetry("syncA", () => runA("sync")); + const syncB = () => runWithRetry("syncB", () => runB("sync")); + const catA = (path: string) => runA("cat", path); + const catB = (path: string) => runB("cat", path); + + // ------------------------------------------------------------------ + // Case 1: A creates file, B reads after sync + // ------------------------------------------------------------------ + await t.step("case 1: A creates file -> B can read after sync", async () => { + const srcA = workDir.join("from-a-src.txt"); + await Deno.writeTextFile(srcA, "from-a\n"); + await runA("push", srcA, "shared/from-a.txt"); + await syncA(); + await syncB(); + const value = (await catB("shared/from-a.txt")).replace(/\r\n/g, "\n").trimEnd(); + assertEquals(value, "from-a", "B could not read file created on A"); + console.log("[PASS] case 1 passed"); + }); + + // ------------------------------------------------------------------ + // Case 2: B creates file, A reads after sync + // ------------------------------------------------------------------ + await t.step("case 2: B creates file -> A can read after sync", async () => { + const srcB = workDir.join("from-b-src.txt"); + await Deno.writeTextFile(srcB, "from-b\n"); + await runB("push", srcB, "shared/from-b.txt"); + await syncB(); + await syncA(); + const value = (await catA("shared/from-b.txt")).replace(/\r\n/g, "\n").trimEnd(); + assertEquals(value, "from-b", "A could not read file created on B"); + console.log("[PASS] case 2 passed"); + }); + + // ------------------------------------------------------------------ + // Case 3: concurrent edits create a conflict + // ------------------------------------------------------------------ + await t.step("case 3: concurrent edits create conflict", async () => { + const baseSrc = workDir.join("base-src.txt"); + await Deno.writeTextFile(baseSrc, "base\n"); + await runA("push", baseSrc, "shared/conflicted.txt"); + await syncA(); + await syncB(); + + const aEdit = workDir.join("edit-a.txt"); + const bEdit = workDir.join("edit-b.txt"); + await Deno.writeTextFile(aEdit, "edit-from-a\n"); + await Deno.writeTextFile(bEdit, "edit-from-b\n"); + await runA("push", aEdit, "shared/conflicted.txt"); + await runB("push", bEdit, "shared/conflicted.txt"); + + const infoFileA = workDir.join("info-a.json"); + const infoFileB = workDir.join("info-b.json"); + + let conflictDetected = false; + for (const side of ["a", "b"] as const) { + if (side === "a") await syncA(); + else await syncB(); + await Deno.writeTextFile(infoFileA, await runA("info", "shared/conflicted.txt")); + await Deno.writeTextFile(infoFileB, await runB("info", "shared/conflicted.txt")); + const da = JSON.parse(await Deno.readTextFile(infoFileA)) as Record; + const db = JSON.parse(await Deno.readTextFile(infoFileB)) as Record; + if (!jsonFieldIsNa(da, "conflicts") || !jsonFieldIsNa(db, "conflicts")) { + conflictDetected = true; + break; + } + } + assert(conflictDetected, "expected conflict after concurrent edits, but both sides show N/A"); + console.log("[PASS] case 3 conflict detected"); + }); + + // ------------------------------------------------------------------ + // Case 4: resolve on A, verify B has no conflict after sync + // ------------------------------------------------------------------ + await t.step("case 4: resolve on A propagates to B", async () => { + const infoFileA = workDir.join("info-a-resolve.json"); + const infoFileB = workDir.join("info-b-resolve.json"); + + // Ensure A sees the conflict + for (let i = 0; i < 5; i++) { + const raw = await runA("info", "shared/conflicted.txt"); + await Deno.writeTextFile(infoFileA, raw); + const da = JSON.parse(raw) as Record; + if (!jsonFieldIsNa(da, "conflicts")) break; + await syncB(); + await syncA(); + } + + const rawA = await runA("info", "shared/conflicted.txt"); + await Deno.writeTextFile(infoFileA, rawA); + const dataA = JSON.parse(rawA) as Record; + assert(!jsonFieldIsNa(dataA, "conflicts"), "A does not see conflict, cannot resolve from A only"); + + const keepRev = dataA["revision"] as string; + assert(keepRev?.length > 0, "could not read revision from A info output"); + + await runA("resolve", "shared/conflicted.txt", keepRev); + + let resolved = false; + for (let i = 0; i < 6; i++) { + await syncA(); + await syncB(); + const rawA2 = await runA("info", "shared/conflicted.txt"); + const rawB2 = await runB("info", "shared/conflicted.txt"); + await Deno.writeTextFile(infoFileA, rawA2); + await Deno.writeTextFile(infoFileB, rawB2); + const da2 = JSON.parse(rawA2) as Record; + const db2 = JSON.parse(rawB2) as Record; + if (jsonFieldIsNa(da2, "conflicts") && jsonFieldIsNa(db2, "conflicts")) { + resolved = true; + break; + } + // If A still sees a conflict, resolve it again + if (!jsonFieldIsNa(da2, "conflicts")) { + const rev2 = da2["revision"] as string; + if (rev2) await runA("resolve", "shared/conflicted.txt", rev2).catch(() => {}); + } + } + assert(resolved, "conflicts should be resolved on both A and B"); + + const contentA = (await catA("shared/conflicted.txt")).replace(/\r\n/g, "\n"); + const contentB = (await catB("shared/conflicted.txt")).replace(/\r\n/g, "\n"); + assertEquals(contentA, contentB, "resolved content mismatch between A and B"); + console.log("[PASS] case 4 passed"); + console.log("[PASS] all sync/resolve scenarios passed"); + }); +} diff --git a/src/apps/cli/testdeno/test_dev_deno.md b/src/apps/cli/testdeno/test_dev_deno.md new file mode 100644 index 0000000..521661a --- /dev/null +++ b/src/apps/cli/testdeno/test_dev_deno.md @@ -0,0 +1,292 @@ +# CLI Deno Test Development Notes + +This document provides an overview of the Deno-based compatibility tests under `src/apps/cli/testdeno/`. +The existing bash tests under `src/apps/cli/test/` are preserved, while a Windows-friendly suite is maintained in parallel. + +--- + +## Goals + +- Keep existing bash tests intact. +- Provide direct execution from Windows PowerShell. +- Establish a TypeScript (Deno) foundation for core end-to-end and integration scenarios. + +--- + +## Directory structure + +``` +src/apps/cli/testdeno/ + deno.json + CONTRIBUTING_TESTS.md + helpers/ + backgroundCli.ts + cli.ts + docker.ts + env.ts + p2p.ts + settings.ts + temp.ts + test-e2e-two-vaults-couchdb.ts + test-push-pull.ts + test-p2p-host.ts + test-p2p-peers-local-relay.ts + test-p2p-sync.ts + test-p2p-three-nodes-conflict.ts + test-p2p-upload-download-repro.ts + test-e2e-two-vaults-matrix.ts + test-setup-put-cat.ts + test-mirror.ts + test-sync-two-local-databases.ts + test-sync-locked-remote.ts +``` + +--- + +## Key files + +### `deno.json` + +- Defines Deno tasks. +- Defines import maps for `@std/assert` and `@std/path`. + +Main tasks: + +- `deno task test` +- `deno task test:local` +- `deno task test:push-pull` +- `deno task test:setup-put-cat` +- `deno task test:mirror` +- `deno task test:sync-two-local` +- `deno task test:sync-locked-remote` +- `deno task test:p2p-host` +- `deno task test:p2p-peers` +- `deno task test:p2p-sync` +- `deno task test:p2p-three-nodes` +- `deno task test:p2p-upload-download` +- `deno task test:e2e-couchdb` +- `deno task test:e2e-matrix` + +### `helpers/cli.ts` + +- CLI execution wrappers. +- `runCli`, `runCliOrFail`, `runCliWithInput`. +- Output normalisation via `sanitiseCatStdout`. +- Comparison utilities, including `assertFilesEqual`. + +This file corresponds to `run_cli` and common assertions in `test-helpers.sh`. + +### `helpers/settings.ts` + +- Executes `init-settings --force`. +- Marks `isConfigured = true`. +- Applies CouchDB and P2P settings. +- Applies remote synchronisation settings and P2P test tweaks. + +This file corresponds to settings helpers in `test-helpers.sh`. + +### `helpers/docker.ts` + +- Starts, stops, and initialises CouchDB directly from Deno. +- Configures CouchDB via `fetch + retry`. +- Starts and stops the P2P relay through the same Docker runner. + +Both CouchDB and P2P relay flows are bash-independent. + +### `helpers/backgroundCli.ts` + +- Starts long-running commands such as `p2p-host` in the background. +- Waits for readiness logs and handles termination. + +### `helpers/p2p.ts` + +- Determines whether a local relay should be started. +- Parses `p2p-peers` output. +- Discovers peer IDs with a fallback based on advertisement logs. + +### `helpers/env.ts` + +- Loads `.test.env`. +- Supports `KEY=value`, single-quoted values, and double-quoted values. + +### `helpers/temp.ts` + +- Provides `TempDir`. +- Uses `await using` to auto-clean temporary directories. + +--- + +## Implemented tests + +### `test-push-pull.ts` + +- Verifies push and pull round trips. +- Uses environment variables or `.test.env` for CouchDB values. + +### `test-setup-put-cat.ts` + +- Verifies `setup` with full setup URI generation via `encodeSettingsToSetupURI`. +- Verifies `push`, `cat`, `ls`, `info`, `rm`, `resolve`, `cat-rev`, and `pull-rev`. +- Does not require an external remote. + +### `test-mirror.ts` + +- Verifies six core mirror scenarios. +- Does not require an external remote. + +### `test-sync-two-local-databases.ts` + +- Verifies sync between two vaults and CouchDB. +- Verifies conflict detection and resolve propagation. +- Starts Docker CouchDB by default when `LIVESYNC_START_DOCKER != 0`. + +### `test-sync-locked-remote.ts` + +- Updates the CouchDB milestone `locked` flag. +- Verifies sync success when unlocked. +- Verifies actionable CLI error when locked. + +### `test-p2p-host.ts` + +- Verifies that `p2p-host` starts and emits readiness output. + +### `test-p2p-peers-local-relay.ts` + +- Verifies peer discovery through a local relay. + +### `test-p2p-sync.ts` + +- Verifies that `p2p-sync` completes after peer discovery. + +### `test-p2p-three-nodes-conflict.ts` + +- Uses one host and two clients. +- Verifies conflict creation, detection via `info`, and resolution via `resolve`. + +### `test-p2p-upload-download-repro.ts` + +- Uses host, upload, and download nodes. +- Verifies transfer of text files and binary files, including larger files. + +### `test-e2e-two-vaults-couchdb.ts` + +- Verifies two-vault end-to-end scenarios on CouchDB. +- Runs both encryption-off and encryption-on cases. +- Includes conflict marker checks in `ls` and resolve propagation checks. + +### `test-e2e-two-vaults-matrix.ts` + +- Verifies the matrix equivalent of the bash script. +- Runs four combinations: + - `COUCHDB-enc0` + - `COUCHDB-enc1` + - `MINIO-enc0` + - `MINIO-enc1` + +--- + +## Running tests (PowerShell) + +From `src/apps/cli/testdeno`: + +```powershell +cd src/apps/cli/testdeno + +# Local-only set +deno task test:local + +# Individual tests +deno task test:setup-put-cat +deno task test:mirror +deno task test:push-pull +deno task test:sync-locked-remote + +# CouchDB-based tests +deno task test:sync-two-local +deno task test:e2e-couchdb + +# P2P-based tests +deno task test:p2p-host +deno task test:p2p-peers +deno task test:p2p-sync +deno task test:p2p-three-nodes +deno task test:p2p-upload-download +deno task test:e2e-matrix +``` + +--- + +## Environment variables + +### CouchDB + +- `COUCHDB_URI` +- `COUCHDB_USER` +- `COUCHDB_PASSWORD` +- `COUCHDB_DBNAME` + +Equivalent keys in `src/apps/cli/.test.env`: + +- `hostname` +- `username` +- `password` +- `dbname` + +### Behaviour switches + +- `LIVESYNC_START_DOCKER=0`: use existing CouchDB. +- `REMOTE_PATH`: override target path for selected tests. +- `LIVESYNC_TEST_TEE=1`: stream CLI stdout and stderr during execution. +- `LIVESYNC_DOCKER_TEE=1`: stream Docker stdout and stderr. +- `LIVESYNC_CLI_RETRY=`: retry transient network failures. +- `LIVESYNC_DEBUG_KEEP_DOCKER=1`: keep `couchdb-test` after test completion. + +### Docker command selection + +`helpers/docker.ts` supports command selection via environment variables. + +- `LIVESYNC_DOCKER_MODE=auto` (default) + - Windows: tries `wsl docker` first, then `docker`. + - Non-Windows: tries `docker` first, then `wsl docker`. +- `LIVESYNC_DOCKER_MODE=native`: always uses `docker`. +- `LIVESYNC_DOCKER_MODE=wsl`: always uses `wsl docker`. +- `LIVESYNC_DOCKER_COMMAND="..."`: custom command, for example `wsl docker`. + +`LIVESYNC_DOCKER_COMMAND` has priority over `LIVESYNC_DOCKER_MODE`. + +PowerShell examples: + +```powershell +# Use Docker in WSL explicitly +$env:LIVESYNC_DOCKER_MODE = "wsl" +deno task test:sync-two-local + +# Full custom command +$env:LIVESYNC_DOCKER_COMMAND = "wsl docker" +deno task test:sync-two-local +``` + +### P2P + +- `RELAY` +- `ROOM_ID` +- `PASSPHRASE` +- `APP_ID` +- `PEERS_TIMEOUT` +- `SYNC_TIMEOUT` +- `USE_INTERNAL_RELAY=0|1` +- `TIMEOUT_SECONDS` + +--- + +## Current limitations + +- MinIO startup and matrix coverage are ported. Current limits are elsewhere, not setup URI generation. + +--- + +## Maintenance policy + +- Existing bash tests remain available. +- Deno tests are expanded in parallel for cross-platform usage. +- New scenarios should be added through reusable helpers in `helpers/`. From 279fc8876e8cc8900ea8099b2db3ed912787541e Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 7 May 2026 11:22:56 +0100 Subject: [PATCH 159/339] feat(tests): enhance push/pull test with Docker integration and improved environment variable handling style(test): format comment --- src/apps/cli/testdeno/helpers/env.ts | 2 +- src/apps/cli/testdeno/test-push-pull.ts | 60 +++++++++++++++---------- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/src/apps/cli/testdeno/helpers/env.ts b/src/apps/cli/testdeno/helpers/env.ts index 656a2f4..4187d39 100644 --- a/src/apps/cli/testdeno/helpers/env.ts +++ b/src/apps/cli/testdeno/helpers/env.ts @@ -8,7 +8,7 @@ * KEY='single quoted' * KEY="double quoted" * # comment lines are ignored - */ + */ export async function loadEnvFile(filePath: string): Promise> { const text = await Deno.readTextFile(filePath); const result: Record = {}; diff --git a/src/apps/cli/testdeno/test-push-pull.ts b/src/apps/cli/testdeno/test-push-pull.ts index 3b04a1e..b5dd61b 100644 --- a/src/apps/cli/testdeno/test-push-pull.ts +++ b/src/apps/cli/testdeno/test-push-pull.ts @@ -21,6 +21,7 @@ import { assertEquals } from "@std/assert"; import { TempDir } from "./helpers/temp.ts"; import { runCliOrFail } from "./helpers/cli.ts"; import { applyCouchdbSettings, initSettingsFile } from "./helpers/settings.ts"; +import { startCouchdb, stopCouchdb } from "./helpers/docker.ts"; const REMOTE_PATH = Deno.env.get("REMOTE_PATH") ?? "test/push-pull.txt"; @@ -31,34 +32,47 @@ Deno.test("push/pull roundtrip", async () => { const vaultDir = workDir.join("vault"); await Deno.mkdir(join(vaultDir, "test"), { recursive: true }); - await initSettingsFile(settingsFile); + const uri = Deno.env.get("COUCHDB_URI") ?? "http://127.0.0.1:5989/"; + const user = Deno.env.get("COUCHDB_USER") ?? "admin"; + const password = Deno.env.get("COUCHDB_PASSWORD") ?? "testpassword"; + const dbname = Deno.env.get("COUCHDB_DBNAME") ?? `push-pull-${Date.now()}`; - const uri = Deno.env.get("COUCHDB_URI") ?? ""; - const user = Deno.env.get("COUCHDB_USER") ?? ""; - const password = Deno.env.get("COUCHDB_PASSWORD") ?? ""; - const dbname = Deno.env.get("COUCHDB_DBNAME") ?? ""; + const shouldStartDocker = Deno.env.get("LIVESYNC_START_DOCKER") !== "0"; + const keepDocker = Deno.env.get("LIVESYNC_DEBUG_KEEP_DOCKER") === "1"; - if (uri && user && password && dbname) { - console.log("[INFO] applying CouchDB env vars to settings"); - await applyCouchdbSettings(settingsFile, uri, user, password, dbname); - } else { - console.warn( - "[WARN] CouchDB env vars not fully set — push/pull may fail unless the generated settings already contain connection details" - ); + if (shouldStartDocker) { + await startCouchdb(uri, user, password, dbname); } - const srcFile = workDir.join("push-source.txt"); - const pulledFile = workDir.join("pull-result.txt"); - const content = `push-pull-test ${new Date().toISOString()}\n`; - await Deno.writeTextFile(srcFile, content); + try { + await initSettingsFile(settingsFile); - console.log(`[INFO] push -> ${REMOTE_PATH}`); - await runCliOrFail(vaultDir, "--settings", settingsFile, "push", srcFile, REMOTE_PATH); + if (uri && user && password && dbname) { + console.log("[INFO] applying CouchDB env vars to settings"); + await applyCouchdbSettings(settingsFile, uri, user, password, dbname); + } else { + console.warn( + "[WARN] CouchDB env vars not fully set — push/pull may fail unless the generated settings already contain connection details" + ); + } - console.log(`[INFO] pull <- ${REMOTE_PATH}`); - await runCliOrFail(vaultDir, "--settings", settingsFile, "pull", REMOTE_PATH, pulledFile); + const srcFile = workDir.join("push-source.txt"); + const pulledFile = workDir.join("pull-result.txt"); + const content = `push-pull-test ${new Date().toISOString()}\n`; + await Deno.writeTextFile(srcFile, content); - const pulled = await Deno.readTextFile(pulledFile); - assertEquals(content, pulled, "push/pull roundtrip content mismatch"); - console.log("[PASS] push/pull roundtrip matched"); + console.log(`[INFO] push -> ${REMOTE_PATH}`); + await runCliOrFail(vaultDir, "--settings", settingsFile, "push", srcFile, REMOTE_PATH); + + console.log(`[INFO] pull <- ${REMOTE_PATH}`); + await runCliOrFail(vaultDir, "--settings", settingsFile, "pull", REMOTE_PATH, pulledFile); + + const pulled = await Deno.readTextFile(pulledFile); + assertEquals(content, pulled, "push/pull roundtrip content mismatch"); + console.log("[PASS] push/pull roundtrip matched"); + } finally { + if (shouldStartDocker && !keepDocker) { + await stopCouchdb().catch(() => {}); + } + } }); From 4a9d6c134948236981866e5a649f0938fad68796 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 7 May 2026 11:23:51 +0100 Subject: [PATCH 160/339] Add ci --- .github/workflows/cli-deno-tests.yml | 75 ++++++++++++++++++++++++++ src/apps/cli/testdeno/test_dev_deno.md | 6 +++ 2 files changed, 81 insertions(+) create mode 100644 .github/workflows/cli-deno-tests.yml diff --git a/.github/workflows/cli-deno-tests.yml b/.github/workflows/cli-deno-tests.yml new file mode 100644 index 0000000..b2b4d13 --- /dev/null +++ b/.github/workflows/cli-deno-tests.yml @@ -0,0 +1,75 @@ +name: cli-deno-tests + +on: + workflow_dispatch: + inputs: + test_task: + description: 'Deno test task to run' + type: choice + options: + - test + - test:local + - test:e2e-matrix + - test:p2p-sync + default: test + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '24.x' + cache: 'npm' + + - name: Setup Deno + uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Install dependencies + run: npm ci + + - name: Build CLI + working-directory: src/apps/cli + run: npm run build + + - name: Create .test.env + working-directory: src/apps/cli + run: | + cat < .test.env + hostname=http://127.0.0.1:5989/ + dbname=livesync-test-db-ci + username=admin + password=testpassword + minioEndpoint=http://127.0.0.1:9000 + accessKey=minioadmin + secretKey=minioadmin + bucketName=livesync-test-bucket-ci + EOF + + - name: Run Deno tests + working-directory: src/apps/cli/testdeno + env: + LIVESYNC_DOCKER_MODE: native + LIVESYNC_CLI_RETRY: 3 + run: | + TASK="${{ github.event_name == 'workflow_dispatch' && inputs.test_task || 'test' }}" + echo "[INFO] Running Deno task: $TASK" + deno task "$TASK" + + - name: Stop leftover containers + if: always() + run: | + docker stop couchdb-test minio-test relay-test >/dev/null 2>&1 || true + docker rm couchdb-test minio-test relay-test >/dev/null 2>&1 || true diff --git a/src/apps/cli/testdeno/test_dev_deno.md b/src/apps/cli/testdeno/test_dev_deno.md index 521661a..acc07ba 100644 --- a/src/apps/cli/testdeno/test_dev_deno.md +++ b/src/apps/cli/testdeno/test_dev_deno.md @@ -279,6 +279,12 @@ deno task test:sync-two-local --- +## Continuous Integration + +The GitHub Actions workflow `.github/workflows/cli-deno-tests.yml` is used to run these tests automatically on push and pull requests affecting the CLI. + +--- + ## Current limitations - MinIO startup and matrix coverage are ported. Current limits are elsewhere, not setup URI generation. From 2afe12ad2dde347dc93d4eca9a32e89c0b289cff Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Thu, 7 May 2026 11:28:01 +0100 Subject: [PATCH 161/339] fix pattern --- src/apps/cli/testdeno/deno.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/cli/testdeno/deno.json b/src/apps/cli/testdeno/deno.json index 18797ae..c056f9c 100644 --- a/src/apps/cli/testdeno/deno.json +++ b/src/apps/cli/testdeno/deno.json @@ -1,6 +1,6 @@ { "tasks": { - "test": "deno test -A --no-check .", + "test": "deno test -A --no-check test-*.ts", "test:local": "deno test -A --no-check test-setup-put-cat.ts test-mirror.ts", "test:push-pull": "deno test -A --no-check test-push-pull.ts", "test:setup-put-cat": "deno test -A --no-check test-setup-put-cat.ts", From e81f023943c0144997c1aa88d5f4392a4aa7e885 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Fri, 8 May 2026 03:01:22 +0000 Subject: [PATCH 162/339] Add default test env --- src/apps/cli/.gitignore | 4 +++- src/apps/cli/testdeno/.test.env | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 src/apps/cli/testdeno/.test.env diff --git a/src/apps/cli/.gitignore b/src/apps/cli/.gitignore index 69dd5ab..24987cb 100644 --- a/src/apps/cli/.gitignore +++ b/src/apps/cli/.gitignore @@ -3,4 +3,6 @@ test/* !test/*.sh test/test-init.local.sh node_modules -.*.json \ No newline at end of file +.*.json +*.env +!.test.env \ No newline at end of file diff --git a/src/apps/cli/testdeno/.test.env b/src/apps/cli/testdeno/.test.env new file mode 100644 index 0000000..4ce5fb8 --- /dev/null +++ b/src/apps/cli/testdeno/.test.env @@ -0,0 +1,9 @@ +hostname=http://127.0.0.1:5989/ +dbname=livesync-test-db-ci +username=admin +password=testpassword +minioEndpoint=http://127.0.0.1:9000 +accessKey=minioadmin +secretKey=minioadmin +bucketName=livesync-test-bucket-ci +LIVESYNC_TEST_TEE=1 \ No newline at end of file From a9c87fa52e99ac4a676916e43c6e16ad568b1539 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Fri, 8 May 2026 03:04:14 +0000 Subject: [PATCH 163/339] - Add default test environment - Fixed to use environment by APIs - Make test parallel --- .github/workflows/cli-deno-tests.yml | 41 ++++++++++++++++++- src/apps/cli/testdeno/deno.json | 28 ++++++------- .../testdeno/test-e2e-two-vaults-couchdb.ts | 31 +++++++------- .../cli/testdeno/test-sync-locked-remote.ts | 22 ++++------ .../testdeno/test-sync-two-local-databases.ts | 19 +-------- 5 files changed, 79 insertions(+), 62 deletions(-) diff --git a/.github/workflows/cli-deno-tests.yml b/.github/workflows/cli-deno-tests.yml index b2b4d13..9230910 100644 --- a/.github/workflows/cli-deno-tests.yml +++ b/.github/workflows/cli-deno-tests.yml @@ -17,9 +17,48 @@ permissions: contents: read jobs: + prepare: + runs-on: ubuntu-latest + outputs: + task_matrix: ${{ steps.select.outputs.task_matrix }} + steps: + - name: Select task matrix + id: select + shell: bash + run: | + set -euo pipefail + SELECTED_TASK="${{ github.event_name == 'workflow_dispatch' && inputs.test_task || 'test' }}" + echo "[INFO] Selected task set: $SELECTED_TASK" + + case "$SELECTED_TASK" in + test) + TASK_MATRIX='["test:setup-put-cat","test:mirror","test:push-pull","test:sync-two-local","test:sync-locked-remote","test:p2p-host","test:p2p-peers","test:p2p-sync","test:p2p-three-nodes","test:p2p-upload-download","test:e2e-couchdb","test:e2e-matrix"]' + ;; + test:local) + TASK_MATRIX='["test:setup-put-cat","test:mirror"]' + ;; + test:e2e-matrix) + TASK_MATRIX='["test:e2e-matrix"]' + ;; + test:p2p-sync) + TASK_MATRIX='["test:p2p-sync"]' + ;; + *) + echo "[ERROR] Unknown task set: $SELECTED_TASK" >&2 + exit 1 + ;; + esac + + echo "task_matrix=$TASK_MATRIX" >> "$GITHUB_OUTPUT" + test: + needs: prepare runs-on: ubuntu-latest timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + task: ${{ fromJson(needs.prepare.outputs.task_matrix) }} steps: - name: Checkout uses: actions/checkout@v4 @@ -64,7 +103,7 @@ jobs: LIVESYNC_DOCKER_MODE: native LIVESYNC_CLI_RETRY: 3 run: | - TASK="${{ github.event_name == 'workflow_dispatch' && inputs.test_task || 'test' }}" + TASK="${{ matrix.task }}" echo "[INFO] Running Deno task: $TASK" deno task "$TASK" diff --git a/src/apps/cli/testdeno/deno.json b/src/apps/cli/testdeno/deno.json index c056f9c..7f0d035 100644 --- a/src/apps/cli/testdeno/deno.json +++ b/src/apps/cli/testdeno/deno.json @@ -1,19 +1,19 @@ { "tasks": { - "test": "deno test -A --no-check test-*.ts", - "test:local": "deno test -A --no-check test-setup-put-cat.ts test-mirror.ts", - "test:push-pull": "deno test -A --no-check test-push-pull.ts", - "test:setup-put-cat": "deno test -A --no-check test-setup-put-cat.ts", - "test:mirror": "deno test -A --no-check test-mirror.ts", - "test:sync-two-local": "deno test -A --no-check test-sync-two-local-databases.ts", - "test:sync-locked-remote": "deno test -A --no-check test-sync-locked-remote.ts", - "test:p2p-host": "deno test -A --no-check test-p2p-host.ts", - "test:p2p-peers": "deno test -A --no-check test-p2p-peers-local-relay.ts", - "test:p2p-sync": "deno test -A --no-check test-p2p-sync.ts", - "test:p2p-three-nodes": "deno test -A --no-check test-p2p-three-nodes-conflict.ts", - "test:p2p-upload-download": "deno test -A --no-check test-p2p-upload-download-repro.ts", - "test:e2e-couchdb": "deno test -A --no-check test-e2e-two-vaults-couchdb.ts", - "test:e2e-matrix": "deno test -A --no-check test-e2e-two-vaults-matrix.ts" + "test": "deno test --env-file=.test.env -A --no-check test-*.ts", + "test:local": "deno test --env-file=.test.env -A --no-check test-setup-put-cat.ts test-mirror.ts", + "test:push-pull": "deno test --env-file=.test.env -A --no-check test-push-pull.ts", + "test:setup-put-cat": "deno test --env-file=.test.env -A --no-check test-setup-put-cat.ts", + "test:mirror": "deno test --env-file=.test.env -A --no-check test-mirror.ts", + "test:sync-two-local": "deno test --env-file=.test.env -A --no-check test-sync-two-local-databases.ts", + "test:sync-locked-remote": "deno test --env-file=.test.env -A --no-check test-sync-locked-remote.ts", + "test:p2p-host": "deno test --env-file=.test.env -A --no-check test-p2p-host.ts", + "test:p2p-peers": "deno test --env-file=.test.env -A --no-check test-p2p-peers-local-relay.ts", + "test:p2p-sync": "deno test --env-file=.test.env -A --no-check test-p2p-sync.ts", + "test:p2p-three-nodes": "deno test --env-file=.test.env -A --no-check test-p2p-three-nodes-conflict.ts", + "test:p2p-upload-download": "deno test --env-file=.test.env -A --no-check test-p2p-upload-download-repro.ts", + "test:e2e-couchdb": "deno test --env-file=.test.env -A --no-check test-e2e-two-vaults-couchdb.ts", + "test:e2e-matrix": "deno test --env-file=.test.env -A --no-check test-e2e-two-vaults-matrix.ts" }, "imports": { "@std/assert": "jsr:@std/assert@^1.0.13", 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 f1b60f1..6f5244b 100644 --- a/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts +++ b/src/apps/cli/testdeno/test-e2e-two-vaults-couchdb.ts @@ -1,6 +1,5 @@ import { assert } from "@std/assert"; import { TempDir } from "./helpers/temp.ts"; -import { loadEnvFile } from "./helpers/env.ts"; import { runCli, runCliOrFail, @@ -11,31 +10,29 @@ import { } from "./helpers/cli.ts"; import { applyRemoteSyncSettings, initSettingsFile } from "./helpers/settings.ts"; import { startCouchdb, startMinio, stopCouchdb, stopMinio } from "./helpers/docker.ts"; -import { join } from "@std/path"; - -const TEST_ENV = join(import.meta.dirname!, "..", ".test.env"); type RemoteType = "COUCHDB" | "MINIO"; -function requireEnv(env: Record, key: string): string { - const value = env[key]?.trim(); - if (!value) throw new Error(`Required env var is missing: ${key}`); - return value; +function requireEnv(...keys: string[]): string { + for (const key of keys) { + const value = Deno.env.get(key)?.trim(); + if (value) return value; + } + throw new Error(`Required env var is missing: ${keys.join(" or ")}`); } export async function runScenario(remoteType: RemoteType, encrypt: boolean): Promise { - const env = await loadEnvFile(TEST_ENV); const dbSuffix = `${Date.now()}-${Math.floor(Math.random() * 100000)}`; - const couchdbUri = remoteType === "COUCHDB" ? requireEnv(env, "hostname").replace(/\/$/, "") : ""; - const couchdbUser = remoteType === "COUCHDB" ? requireEnv(env, "username") : ""; - const couchdbPassword = remoteType === "COUCHDB" ? requireEnv(env, "password") : ""; - const dbPrefix = remoteType === "COUCHDB" ? requireEnv(env, "dbname") : ""; + const couchdbUri = remoteType === "COUCHDB" ? requireEnv("COUCHDB_URI", "hostname").replace(/\/$/, "") : ""; + const couchdbUser = remoteType === "COUCHDB" ? requireEnv("COUCHDB_USER", "username") : ""; + const couchdbPassword = remoteType === "COUCHDB" ? requireEnv("COUCHDB_PASSWORD", "password") : ""; + const dbPrefix = remoteType === "COUCHDB" ? requireEnv("COUCHDB_DBNAME", "dbname") : ""; const dbname = remoteType === "COUCHDB" ? `${dbPrefix}-${dbSuffix}` : ""; - const minioEndpoint = remoteType === "MINIO" ? requireEnv(env, "minioEndpoint").replace(/\/$/, "") : ""; - const minioAccessKey = remoteType === "MINIO" ? requireEnv(env, "accessKey") : ""; - const minioSecretKey = remoteType === "MINIO" ? requireEnv(env, "secretKey") : ""; - const minioBucketBase = remoteType === "MINIO" ? requireEnv(env, "bucketName") : ""; + 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") : ""; const minioBucket = remoteType === "MINIO" ? `${minioBucketBase}-${dbSuffix}` : ""; const passphrase = "e2e-passphrase"; diff --git a/src/apps/cli/testdeno/test-sync-locked-remote.ts b/src/apps/cli/testdeno/test-sync-locked-remote.ts index 1dfc568..d8b2e3d 100644 --- a/src/apps/cli/testdeno/test-sync-locked-remote.ts +++ b/src/apps/cli/testdeno/test-sync-locked-remote.ts @@ -6,30 +6,26 @@ */ import { assert, assertStringIncludes } from "@std/assert"; -import { join } from "@std/path"; -import { loadEnvFile } from "./helpers/env.ts"; import { TempDir } from "./helpers/temp.ts"; import { runCli } from "./helpers/cli.ts"; import { applyCouchdbSettings, initSettingsFile } from "./helpers/settings.ts"; import { createCouchdbDatabase, startCouchdb, stopCouchdb, updateCouchdbDoc } from "./helpers/docker.ts"; -const TEST_ENV = join(import.meta.dirname!, "..", ".test.env"); const MILESTONE_DOC = "_local/obsydian_livesync_milestone"; -function requireEnv(env: Record, key: string): string { - const value = env[key]?.trim(); - if (!value) { - throw new Error(`Required env var is missing: ${key}`); +function requireEnv(...keys: string[]): string { + for (const key of keys) { + const value = Deno.env.get(key)?.trim(); + if (value) return value; } - return value; + throw new Error(`Required env var is missing: ${keys.join(" or ")}`); } Deno.test("sync: actionable error against locked remote DB", async () => { - const env = await loadEnvFile(TEST_ENV); - const couchdbUri = requireEnv(env, "hostname").replace(/\/$/, ""); - const couchdbUser = requireEnv(env, "username"); - const couchdbPassword = requireEnv(env, "password"); - const dbPrefix = requireEnv(env, "dbname"); + const couchdbUri = requireEnv("COUCHDB_URI", "hostname").replace(/\/$/, ""); + const couchdbUser = requireEnv("COUCHDB_USER", "username"); + const couchdbPassword = requireEnv("COUCHDB_PASSWORD", "password"); + const dbPrefix = requireEnv("COUCHDB_DBNAME", "dbname"); const dbname = `${dbPrefix}-locked-${Date.now()}-${Math.floor(Math.random() * 100000)}`; await using workDir = await TempDir.create("livesync-cli-locked-test"); diff --git a/src/apps/cli/testdeno/test-sync-two-local-databases.ts b/src/apps/cli/testdeno/test-sync-two-local-databases.ts index c14ee08..5717d40 100644 --- a/src/apps/cli/testdeno/test-sync-two-local-databases.ts +++ b/src/apps/cli/testdeno/test-sync-two-local-databases.ts @@ -23,13 +23,11 @@ * deno test -A test-sync-two-local-databases.ts */ -import { join } from "@std/path"; import { assertEquals, assert } from "@std/assert"; import { TempDir } from "./helpers/temp.ts"; -import { CLI_DIR, runCliOrFail, jsonFieldIsNa } from "./helpers/cli.ts"; +import { runCliOrFail, jsonFieldIsNa } from "./helpers/cli.ts"; import { applyCouchdbSettings, initSettingsFile } from "./helpers/settings.ts"; import { startCouchdb, stopCouchdb } from "./helpers/docker.ts"; -import { loadEnvFile } from "./helpers/env.ts"; // --------------------------------------------------------------------------- // Load configuration @@ -41,20 +39,7 @@ async function resolveConfig(): Promise<{ password: string; baseDbname: string; } | null> { - let env: Record = {}; - - // 1. Explicit environment variables take priority - if (Deno.env.get("COUCHDB_URI")) { - env = Object.fromEntries(Deno.env.toObject()); - } else { - // 2. TEST_ENV_FILE env var - const envFile = Deno.env.get("TEST_ENV_FILE") ?? join(CLI_DIR, ".test.env"); - try { - env = await loadEnvFile(envFile); - } catch { - return null; // no config available — skip - } - } + const env = Deno.env.toObject(); const uri = (env["COUCHDB_URI"] ?? env["hostname"] ?? "").replace(/\/$/, ""); const user = env["COUCHDB_USER"] ?? env["username"] ?? ""; From 81dc7f604ba0c17d1da6f06b4b4bc869a76e8331 Mon Sep 17 00:00:00 2001 From: SeleiXi Date: Sat, 9 May 2026 14:07:08 +0800 Subject: [PATCH 164/339] feat: auto navigation to diff --- src/modules/features/DocumentHistory/DocumentHistoryModal.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts index 0056832..7e7560a 100644 --- a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts +++ b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts @@ -223,6 +223,9 @@ export class DocumentHistoryModal extends Modal { } // Reset diff navigation after content changes this.resetDiffNavigation(); + if (this.showDiff) { + this.navigateDiff("next"); + } } /** From a6be20695aad2db18baed6d0b5212d4a25b1c799 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 11 May 2026 03:49:35 +0100 Subject: [PATCH 165/339] 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 68e0610f1d23cd55b705cfc373f3b6c1c9cb3981 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 11 May 2026 09:49:32 +0100 Subject: [PATCH 166/339] (chore) remove obsoleted file --- src/modules/features/ModuleSetupObsidian.ts | 208 -------------------- 1 file changed, 208 deletions(-) delete mode 100644 src/modules/features/ModuleSetupObsidian.ts diff --git a/src/modules/features/ModuleSetupObsidian.ts b/src/modules/features/ModuleSetupObsidian.ts deleted file mode 100644 index 2c715d5..0000000 --- a/src/modules/features/ModuleSetupObsidian.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { type ObsidianLiveSyncSettings, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "../../lib/src/common/types.ts"; -import { configURIBase } from "../../common/types.ts"; -// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.js"; -import { fireAndForget } from "../../lib/src/common/utils.ts"; -import { - EVENT_REQUEST_COPY_SETUP_URI, - EVENT_REQUEST_OPEN_P2P_SETTINGS, - EVENT_REQUEST_OPEN_SETUP_URI, - EVENT_REQUEST_SHOW_SETUP_QR, - eventHub, -} from "../../common/events.ts"; -import { $msg } from "../../lib/src/common/i18n.ts"; -// import { performDoctorConsultation, RebuildOptions } from "@/lib/src/common/configForDoc.ts"; -import type { LiveSyncCore } from "../../main.ts"; -import { - encodeQR, - encodeSettingsToQRCodeData, - encodeSettingsToSetupURI, - OutputFormat, -} from "../../lib/src/API/processSetting.ts"; -import { SetupManager, UserMode } from "./SetupManager.ts"; -import { AbstractModule } from "../AbstractModule.ts"; - -export class ModuleSetupObsidian extends AbstractModule { - private _setupManager!: SetupManager; - private _everyOnload(): Promise { - this._setupManager = this.core.getModule(SetupManager); - try { - this.registerObsidianProtocolHandler("setuplivesync", async (conf: any) => { - if (conf.settings) { - await this._setupManager.onUseSetupURI( - UserMode.Unknown, - `${configURIBase}${encodeURIComponent(conf.settings)}` - ); - } else if (conf.settingsQR) { - await this._setupManager.decodeQR(conf.settingsQR); - } - }); - } catch (e) { - this._log( - "Failed to register protocol handler. This feature may not work in some environments.", - LOG_LEVEL_NOTICE - ); - this._log(e, LOG_LEVEL_VERBOSE); - } - this.addCommand({ - id: "livesync-setting-qr", - name: "Show settings as a QR code", - callback: () => fireAndForget(this.encodeQR()), - }); - - this.addCommand({ - id: "livesync-copysetupuri", - name: "Copy settings as a new setup URI", - callback: () => fireAndForget(this.command_copySetupURI()), - }); - this.addCommand({ - id: "livesync-copysetupuri-short", - name: "Copy settings as a new setup URI (With customization sync)", - callback: () => fireAndForget(this.command_copySetupURIWithSync()), - }); - - this.addCommand({ - id: "livesync-copysetupurifull", - name: "Copy settings as a new setup URI (Full)", - callback: () => fireAndForget(this.command_copySetupURIFull()), - }); - - this.addCommand({ - id: "livesync-opensetupuri", - name: "Use the copied setup URI (Formerly Open setup URI)", - callback: () => fireAndForget(this.command_openSetupURI()), - }); - - eventHub.onEvent(EVENT_REQUEST_OPEN_SETUP_URI, () => fireAndForget(() => this.command_openSetupURI())); - eventHub.onEvent(EVENT_REQUEST_COPY_SETUP_URI, () => fireAndForget(() => this.command_copySetupURI())); - eventHub.onEvent(EVENT_REQUEST_SHOW_SETUP_QR, () => fireAndForget(() => this.encodeQR())); - eventHub.onEvent(EVENT_REQUEST_OPEN_P2P_SETTINGS, () => - fireAndForget(() => { - return this._setupManager.onP2PManualSetup(UserMode.Update, this.settings, false); - }) - ); - return Promise.resolve(true); - } - async encodeQR() { - const settingString = encodeSettingsToQRCodeData(this.settings); - const codeSVG = encodeQR(settingString, OutputFormat.SVG); - if (codeSVG == "") { - return ""; - } - const msg = $msg("Setup.QRCode", { qr_image: codeSVG }); - await this.core.confirm.confirmWithMessage("Settings QR Code", msg, ["OK"], "OK"); - return await Promise.resolve(codeSVG); - } - - async askEncryptingPassphrase(): Promise { - const encryptingPassphrase = await this.core.confirm.askString( - "Encrypt your settings", - "The passphrase to encrypt the setup URI", - "", - true - ); - return encryptingPassphrase; - } - - async command_copySetupURI(stripExtra = true) { - const encryptingPassphrase = await this.askEncryptingPassphrase(); - if (encryptingPassphrase === false) return; - const encryptedURI = await encodeSettingsToSetupURI( - this.settings, - encryptingPassphrase, - [...((stripExtra ? ["pluginSyncExtendedSetting"] : []) as (keyof ObsidianLiveSyncSettings)[])], - true - ); - if (await this.services.UI.promptCopyToClipboard("Setup URI", encryptedURI)) { - this._log("Setup URI copied to clipboard", LOG_LEVEL_NOTICE); - } - // await navigator.clipboard.writeText(encryptedURI); - } - - async command_copySetupURIFull() { - const encryptingPassphrase = await this.askEncryptingPassphrase(); - if (encryptingPassphrase === false) return; - const encryptedURI = await encodeSettingsToSetupURI(this.settings, encryptingPassphrase, [], false); - await navigator.clipboard.writeText(encryptedURI); - this._log("Setup URI copied to clipboard", LOG_LEVEL_NOTICE); - } - - async command_copySetupURIWithSync() { - await this.command_copySetupURI(false); - } - async command_openSetupURI() { - await this._setupManager.onUseSetupURI(UserMode.Unknown); - } - - // TODO: Where to implement these? - - // async askSyncWithRemoteConfig(tryingSettings: ObsidianLiveSyncSettings): Promise { - // const buttons = { - // fetch: $msg("Setup.FetchRemoteConf.Buttons.Fetch"), - // no: $msg("Setup.FetchRemoteConf.Buttons.Skip"), - // } as const; - // const fetchRemoteConf = await this.core.confirm.askSelectStringDialogue( - // $msg("Setup.FetchRemoteConf.Message"), - // Object.values(buttons), - // { defaultAction: buttons.fetch, timeout: 0, title: $msg("Setup.FetchRemoteConf.Title") } - // ); - // if (fetchRemoteConf == buttons.no) { - // return tryingSettings; - // } - - // const newSettings = JSON.parse(JSON.stringify(tryingSettings)) as ObsidianLiveSyncSettings; - // const remoteConfig = await this.services.tweakValue.fetchRemotePreferred(newSettings); - // if (remoteConfig) { - // this._log("Remote configuration found.", LOG_LEVEL_NOTICE); - // const resultSettings = { - // ...DEFAULT_SETTINGS, - // ...tryingSettings, - // ...remoteConfig, - // } satisfies ObsidianLiveSyncSettings; - // return resultSettings; - // } else { - // this._log("Remote configuration not applied.", LOG_LEVEL_NOTICE); - // return { - // ...DEFAULT_SETTINGS, - // ...tryingSettings, - // } satisfies ObsidianLiveSyncSettings; - // } - // } - // async askPerformDoctor( - // tryingSettings: ObsidianLiveSyncSettings - // ): Promise<{ settings: ObsidianLiveSyncSettings; shouldRebuild: boolean; isModified: boolean }> { - // const buttons = { - // yes: $msg("Setup.Doctor.Buttons.Yes"), - // no: $msg("Setup.Doctor.Buttons.No"), - // } as const; - // const performDoctor = await this.core.confirm.askSelectStringDialogue( - // $msg("Setup.Doctor.Message"), - // Object.values(buttons), - // { defaultAction: buttons.yes, timeout: 0, title: $msg("Setup.Doctor.Title") } - // ); - // if (performDoctor == buttons.no) { - // return { settings: tryingSettings, shouldRebuild: false, isModified: false }; - // } - - // const newSettings = JSON.parse(JSON.stringify(tryingSettings)) as ObsidianLiveSyncSettings; - // const { settings, shouldRebuild, isModified } = await performDoctorConsultation(this.core, newSettings, { - // localRebuild: RebuildOptions.AutomaticAcceptable, // Because we are in the setup wizard, we can skip the confirmation. - // remoteRebuild: RebuildOptions.SkipEvenIfRequired, - // activateReason: "New settings from URI", - // }); - // if (isModified) { - // this._log("Doctor has fixed some issues!", LOG_LEVEL_NOTICE); - // return { - // settings, - // shouldRebuild, - // isModified, - // }; - // } else { - // this._log("Doctor detected no issues!", LOG_LEVEL_NOTICE); - // return { settings: tryingSettings, shouldRebuild: false, isModified: false }; - // } - // } - - override onBindFunction(core: LiveSyncCore, services: typeof core.services): void { - services.appLifecycle.onLoaded.addHandler(this._everyOnload.bind(this)); - } -} From d5b93e89cd5bb191770e9b63b63ed5f328c39b2d Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 11 May 2026 17:55:31 +0900 Subject: [PATCH 167/339] Change the default issue report label from 'bug' to 'uncategorised' --- .github/ISSUE_TEMPLATE/issue-report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/issue-report.md b/.github/ISSUE_TEMPLATE/issue-report.md index 2ca8a66..d12287f 100644 --- a/.github/ISSUE_TEMPLATE/issue-report.md +++ b/.github/ISSUE_TEMPLATE/issue-report.md @@ -2,7 +2,7 @@ name: Issue report about: Create a report to help us improve title: '' -labels: 'bug' +labels: 'uncategorised' assignees: '' --- From 112e3c8b1d00532472f49f8f6b0c932ab3036e4e Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 11 May 2026 12:33:32 +0100 Subject: [PATCH 168/339] Fix prettier config --- .prettierrc.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.prettierrc.mjs b/.prettierrc.mjs index d0c0d3e..33b5690 100644 --- a/.prettierrc.mjs +++ b/.prettierrc.mjs @@ -13,7 +13,7 @@ const prettierConfig = { tabWidth: 4, printWidth: 120, semi: true, - endOfLine: "cr", + endOfLine: "lf", ...localPrettierConfig, }; From eca6a6e0ba948590325c01aa64a085075bd21403 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 11 May 2026 13:00:32 +0100 Subject: [PATCH 169/339] chore: Change eslint config to ignore _tools --- eslint.config.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/eslint.config.mjs b/eslint.config.mjs index b2fbf76..41cb90b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -38,6 +38,7 @@ export default [ "modules/octagonal-wheels/rollup.config.js", "modules/octagonal-wheels/dist/**/*", "src/lib/test", + "src/lib/_tools", "src/lib/src/cli", "**/main.js", "src/apps/**/*", From bfff6ea7b8a6e207bbd856be15b0f7fbc258757f Mon Sep 17 00:00:00 2001 From: SeleiXi Date: Mon, 11 May 2026 19:27:59 +0800 Subject: [PATCH 170/339] feat: add document history search support --- src/lib | 2 +- .../DocumentHistory/DocumentHistoryModal.ts | 177 +++++++++++++++++- 2 files changed, 175 insertions(+), 4 deletions(-) diff --git a/src/lib b/src/lib index 9753055..91b5981 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 97530553a63dc206ea3fb7ef60721cabda6c74cc +Subproject commit 91b59812191dc8e190658b4110eedd4dca5e1803 diff --git a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts index 7e7560a..97a7236 100644 --- a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts +++ b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts @@ -71,6 +71,14 @@ export class DocumentHistoryModal extends Modal { diffNavContainer!: HTMLDivElement; diffNavIndicator!: HTMLSpanElement; + // Search state + searchKeyword = ""; + searchResults: { rev: string; index: number; matchType: "Content" | "Diff" }[] = []; + currentSearchIndex = -1; + searchResultIndicator!: HTMLSpanElement; + searchProgressIndicator!: HTMLSpanElement; + searchTimeout: number | null = null; + constructor( app: App, core: LiveSyncBaseCore, @@ -176,12 +184,20 @@ export class DocumentHistoryModal extends Modal { for (const v of diff) { const x1 = v[0]; const x2 = v[1]; + let text = escapeStringToHTML(x2); + if (this.searchKeyword) { + const regex = new RegExp( + `(${this.searchKeyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, + "gi" + ); + text = text.replace(regex, "$1"); + } if (x1 == DIFF_DELETE) { - result += "" + escapeStringToHTML(x2) + ""; + result += "" + text + ""; } else if (x1 == DIFF_EQUAL) { - result += "" + escapeStringToHTML(x2) + ""; + result += "" + text + ""; } else if (x1 == DIFF_INSERT) { - result += "" + escapeStringToHTML(x2) + ""; + result += "" + text + ""; } } result = result.replace(/\n/g, "
"); @@ -218,6 +234,12 @@ export class DocumentHistoryModal extends Modal { } } if (result == undefined) result = typeof w1data == "string" ? escapeStringToHTML(w1data) : "Binary file"; + + if (this.searchKeyword && typeof result == "string" && !this.showDiff) { + const regex = new RegExp(`(${this.searchKeyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, "gi"); + result = result.replace(regex, "$1"); + } + this.contentView.innerHTML = (this.currentDeleted ? "(At this revision, the file has been deleted)\n" : "") + result; } @@ -225,6 +247,11 @@ export class DocumentHistoryModal extends Modal { this.resetDiffNavigation(); if (this.showDiff) { this.navigateDiff("next"); + } else if (this.searchKeyword) { + const firstMark = this.contentView.querySelector("mark"); + if (firstMark) { + firstMark.scrollIntoView({ behavior: "smooth", block: "center" }); + } } } @@ -281,12 +308,156 @@ export class DocumentHistoryModal extends Modal { } } + /** + * Search through the last 100 revisions for the given keyword. + */ + async performSearch(keyword: string) { + this.searchKeyword = keyword; + this.searchResults = []; + this.currentSearchIndex = -1; + + if (!keyword) { + this.searchResultIndicator.textContent = ""; + this.searchProgressIndicator.textContent = ""; + return; + } + + const db = this.core.localDatabase; + const limit = 100; + const totalRevs = this.revs_info.length; + const end = Math.min(totalRevs, limit); + + this.searchProgressIndicator.textContent = "Searching..."; + + const dmp = new diff_match_patch(); + + // 0 is the newest, higher index is older. + for (let i = 0; i < end; i++) { + const revInfo = this.revs_info[i]; + const rev = revInfo.rev; + + this.searchProgressIndicator.textContent = `Searching ${i + 1}/${end}...`; + + const doc = await db.getDBEntry(this.file, { rev: rev }, false, false, true); + if (doc === false) continue; + + const content = readDocument(doc); + if (typeof content !== "string") continue; + + const keywordLower = keyword.toLocaleLowerCase(); + + // Search in content + if (content.toLocaleLowerCase().includes(keywordLower)) { + this.searchResults.push({ rev, index: i, matchType: "Content" }); + this.updateSearchUI(); + continue; + } + + // Search in diff (from older version to this version) + // Older version is at i + 1 + if (i < totalRevs - 1) { + const olderRev = this.revs_info[i + 1].rev; + const olderDoc = await db.getDBEntry(this.file, { rev: olderRev }, false, false, true); + if (olderDoc !== false) { + const olderContent = readDocument(olderDoc); + if (typeof olderContent === "string") { + const diffs = dmp.diff_main(olderContent, content); + let foundInDiff = false; + for (const d of diffs) { + if ((d[0] === DIFF_INSERT || d[0] === DIFF_DELETE) && + d[1].toLocaleLowerCase().includes(keywordLower)) { + foundInDiff = true; + break; + } + } + if (foundInDiff) { + this.searchResults.push({ rev, index: i, matchType: "Diff" }); + this.updateSearchUI(); + } + } + } + } + } + + this.searchProgressIndicator.textContent = "Done"; + this.updateSearchUI(); + } + + updateSearchUI() { + if (this.searchResults.length === 0) { + this.searchResultIndicator.textContent = this.searchKeyword ? "No matches found" : ""; + } else { + const current = this.currentSearchIndex >= 0 ? this.currentSearchIndex + 1 : 0; + this.searchResultIndicator.textContent = `${current}/${this.searchResults.length} matches`; + } + } + + navigateSearch(direction: "prev" | "next") { + if (this.searchResults.length === 0) return; + + if (direction === "next") { + this.currentSearchIndex = (this.currentSearchIndex + 1) % this.searchResults.length; + } else { + this.currentSearchIndex = + this.currentSearchIndex <= 0 ? this.searchResults.length - 1 : this.currentSearchIndex - 1; + } + + const match = this.searchResults[this.currentSearchIndex]; + this.range.value = `${this.revs_info.length - 1 - match.index}`; + void scheduleOnceIfDuplicated("loadRevs", () => this.loadRevs()); + this.updateSearchUI(); + + // If it's a diff match, make sure Highlight diff is on + if (match.matchType === "Diff" && !this.showDiff) { + // We could auto-enable it, but maybe just notify the user? + // For now, let's just let the user toggle it if they want to see the diff. + } + } + override onOpen() { const { contentEl } = this; this.titleEl.setText("Document History"); contentEl.empty(); this.fileInfo = contentEl.createDiv(""); this.fileInfo.addClass("op-info"); + + // Search Row + const searchRow = contentEl.createDiv(""); + searchRow.addClass("op-info"); + searchRow.addClass("search-row"); + searchRow.style.display = "flex"; + searchRow.style.gap = "5px"; + searchRow.style.alignItems = "center"; + searchRow.style.marginBottom = "10px"; + + const searchInput = searchRow.createEl("input", { type: "text", placeholder: "Search in history (last 100)..." }); + searchInput.style.flexGrow = "1"; + searchInput.addEventListener("input", () => { + if (this.searchTimeout) { + clearTimeout(this.searchTimeout); + } + this.searchTimeout = window.setTimeout(() => { + void this.performSearch(searchInput.value); + }, 500); + }); + + searchRow.createEl("button", { text: "\u25B2" }, (e) => { + e.title = "Previous match"; + e.addEventListener("click", () => this.navigateSearch("prev")); + }); + searchRow.createEl("button", { text: "\u25BC" }, (e) => { + e.title = "Next match"; + e.addEventListener("click", () => this.navigateSearch("next")); + }); + + this.searchResultIndicator = searchRow.createEl("span", { text: "" }); + this.searchResultIndicator.style.fontSize = "0.8em"; + this.searchResultIndicator.style.minWidth = "80px"; + + this.searchProgressIndicator = searchRow.createEl("span", { text: "" }); + this.searchProgressIndicator.style.fontSize = "0.8em"; + this.searchProgressIndicator.style.color = "var(--text-muted)"; + const divView = contentEl.createDiv(""); divView.addClass("op-flex"); From 429a3ff1fd6beb39dc97fb9ca4104c4939668a3c Mon Sep 17 00:00:00 2001 From: SeleiXi Date: Mon, 11 May 2026 23:53:07 +0800 Subject: [PATCH 171/339] feat: add diff-only view button to document history --- .../DocumentHistory/DocumentHistoryModal.ts | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts index 7e7560a..a8c22b3 100644 --- a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts +++ b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts @@ -56,6 +56,7 @@ export class DocumentHistoryModal extends Modal { info!: HTMLDivElement; fileInfo!: HTMLDivElement; showDiff = false; + diffOnly = false; id?: DocumentID; file: FilePathWithPrefix; @@ -70,6 +71,7 @@ export class DocumentHistoryModal extends Modal { currentDiffIndex = -1; diffNavContainer!: HTMLDivElement; diffNavIndicator!: HTMLSpanElement; + diffOnlyLabel!: HTMLLabelElement; constructor( app: App, @@ -91,6 +93,9 @@ export class DocumentHistoryModal extends Modal { if (localStorage.getItem("ols-history-highlightdiff") == "1") { this.showDiff = true; } + if (localStorage.getItem("ols-history-diffonly") == "1") { + this.diffOnly = true; + } } async loadFile(initialRev?: string) { @@ -173,15 +178,25 @@ export class DocumentHistoryModal extends Modal { const w2data = readDocument(w2) as string; const diff = dmp.diff_main(w2data, w1data); dmp.diff_cleanupSemantic(diff); + let hasOmitted = false; for (const v of diff) { const x1 = v[0]; const x2 = v[1]; if (x1 == DIFF_DELETE) { result += "" + escapeStringToHTML(x2) + ""; + hasOmitted = false; } else if (x1 == DIFF_EQUAL) { - result += "" + escapeStringToHTML(x2) + ""; + if (!this.diffOnly) { + result += "" + escapeStringToHTML(x2) + ""; + } else { + if (!hasOmitted) { + result += "\n...\n"; + hasOmitted = true; + } + } } else if (x1 == DIFF_INSERT) { result += "" + escapeStringToHTML(x2) + ""; + hasOmitted = false; } } result = result.replace(/\n/g, "
"); @@ -279,6 +294,9 @@ export class DocumentHistoryModal extends Modal { if (this.diffNavContainer) { this.diffNavContainer.style.display = this.showDiff ? "flex" : "none"; } + if (this.diffOnlyLabel) { + this.diffOnlyLabel.style.display = this.showDiff ? "inline-block" : "none"; + } } override onOpen() { @@ -320,6 +338,24 @@ export class DocumentHistoryModal extends Modal { label.appendText("Highlight diff"); }); + const diffOnlyLabel = diffOptionsRow.createEl("label", {}); + diffOnlyLabel.appendChild( + createEl("input", { type: "checkbox" }, (checkbox) => { + if (this.diffOnly) { + checkbox.checked = true; + } + checkbox.addEventListener("input", (evt: any) => { + this.diffOnly = checkbox.checked; + localStorage.setItem("ols-history-diffonly", this.diffOnly == true ? "1" : ""); + void scheduleOnceIfDuplicated("loadRevs", () => this.loadRevs()); + }); + }) + ); + diffOnlyLabel.appendText("Diff only"); + diffOnlyLabel.style.marginLeft = "10px"; + diffOnlyLabel.style.display = this.showDiff ? "inline-block" : "none"; + this.diffOnlyLabel = diffOnlyLabel; + // Diff navigation buttons this.diffNavContainer = diffOptionsRow.createDiv(""); this.diffNavContainer.addClass("diff-nav"); From 0d9397c8b9f91ac1a694d7f9d4bb90d72586e9b7 Mon Sep 17 00:00:00 2001 From: SeleiXi Date: Tue, 12 May 2026 00:52:20 +0800 Subject: [PATCH 172/339] fix: resolve UI alignment issue for diff navigation buttons --- .../DocumentHistory/DocumentHistoryModal.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts index 97a7236..742e873 100644 --- a/src/modules/features/DocumentHistory/DocumentHistoryModal.ts +++ b/src/modules/features/DocumentHistory/DocumentHistoryModal.ts @@ -473,8 +473,18 @@ export class DocumentHistoryModal extends Modal { const diffOptionsRow = contentEl.createDiv(""); diffOptionsRow.addClass("op-info"); diffOptionsRow.addClass("diff-options-row"); + diffOptionsRow.style.display = "flex"; + diffOptionsRow.style.justifyContent = "space-between"; + diffOptionsRow.style.alignItems = "center"; - diffOptionsRow.createEl("label", {}, (label) => { + const highlightDiffContainer = diffOptionsRow.createDiv(""); + highlightDiffContainer.style.display = "flex"; + highlightDiffContainer.style.alignItems = "center"; + + highlightDiffContainer.createEl("label", {}, (label) => { + label.style.display = "flex"; + label.style.alignItems = "center"; + label.style.gap = "4px"; label.appendChild( createEl("input", { type: "checkbox" }, (checkbox) => { if (this.showDiff) { @@ -495,6 +505,7 @@ export class DocumentHistoryModal extends Modal { this.diffNavContainer = diffOptionsRow.createDiv(""); this.diffNavContainer.addClass("diff-nav"); this.diffNavContainer.style.display = this.showDiff ? "flex" : "none"; + this.diffNavContainer.style.marginLeft = "auto"; this.diffNavContainer.createEl("button", { text: "\u25B2 Prev" }, (e) => { e.addClass("diff-nav-btn"); From 5454e1106fedfcde81bb7a81b7f234efda57131b Mon Sep 17 00:00:00 2001 From: SeleiXi Date: Wed, 13 May 2026 00:19:56 +0800 Subject: [PATCH 173/339] feat: add diff navigation to conflict resolver --- .../ConflictResolveModal.ts | 61 +++++++++++++++++-- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts b/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts index ad308e5..ab16823 100644 --- a/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts +++ b/src/modules/features/InteractiveConflictResolving/ConflictResolveModal.ts @@ -28,6 +28,9 @@ export class ConflictResolveModal extends Modal { localName: string = "Base"; remoteName: string = "Conflicted"; offEvent?: ReturnType; + currentDiffIndex = -1; + diffView!: HTMLDivElement; + diffNavIndicator!: HTMLSpanElement; constructor(app: App, filename: string, diff: diff_result, pluginPickMode?: boolean, remoteName?: string) { super(app); @@ -44,6 +47,34 @@ export class ConflictResolveModal extends Modal { // sendValue("close-resolve-conflict:" + this.filename, false); } + navigateDiff(direction: "prev" | "next") { + const diffElements = this.diffView.querySelectorAll(".added, .deleted"); + if (diffElements.length === 0) return; + + const prevFocused = this.diffView.querySelector(".diff-focused"); + if (prevFocused) { + prevFocused.classList.remove("diff-focused"); + } + + if (direction === "next") { + this.currentDiffIndex = (this.currentDiffIndex + 1) % diffElements.length; + } else { + this.currentDiffIndex = + this.currentDiffIndex <= 0 ? diffElements.length - 1 : this.currentDiffIndex - 1; + } + + const target = diffElements[this.currentDiffIndex]; + target.classList.add("diff-focused"); + target.scrollIntoView({ behavior: "smooth", block: "center" }); + this.diffNavIndicator.textContent = `${this.currentDiffIndex + 1}/${diffElements.length}`; + } + + resetDiffNavigation() { + this.currentDiffIndex = -1; + const diffElements = this.diffView.querySelectorAll(".added, .deleted"); + this.diffNavIndicator.textContent = diffElements.length > 0 ? `0/${diffElements.length}` : "\u2014"; + } + override onOpen() { const { contentEl } = this; // Send cancel signal for the previous merge dialogue @@ -60,10 +91,26 @@ export class ConflictResolveModal extends Modal { // sendValue("close-resolve-conflict:" + this.filename, false); this.titleEl.setText(this.title); contentEl.empty(); - contentEl.createEl("span", { text: this.filename }); - const div = contentEl.createDiv(""); - div.addClass("op-scrollable"); - div.addClass("ls-dialog"); + const diffOptionsRow = contentEl.createDiv(""); + diffOptionsRow.addClass("diff-options-row"); + diffOptionsRow.createEl("span", { text: this.filename }); + + const diffNavContainer = diffOptionsRow.createDiv(""); + diffNavContainer.addClass("diff-nav"); + diffNavContainer.createEl("button", { text: "\u25B2 Prev" }, (e) => { + e.addClass("diff-nav-btn"); + e.addEventListener("click", () => this.navigateDiff("prev")); + }); + diffNavContainer.createEl("button", { text: "\u25BC Next" }, (e) => { + e.addClass("diff-nav-btn"); + e.addEventListener("click", () => this.navigateDiff("next")); + }); + this.diffNavIndicator = diffNavContainer.createEl("span", { text: "\u2014" }); + this.diffNavIndicator.addClass("diff-nav-indicator"); + + this.diffView = contentEl.createDiv(""); + this.diffView.addClass("op-scrollable"); + this.diffView.addClass("ls-dialog"); let diff = ""; for (const v of this.result.diff) { const x1 = v[0]; @@ -110,10 +157,12 @@ export class ConflictResolveModal extends Modal { ).style.marginRight = "4px"; diff = diff.replace(/\n/g, "
"); if (diff.length > 100 * 1024) { - div.innerText = "(Too large diff to display)"; + this.diffView.innerText = "(Too large diff to display)"; } else { - div.innerHTML = diff; + this.diffView.innerHTML = diff; } + this.resetDiffNavigation(); + this.navigateDiff("next"); } sendResponse(result: MergeDialogResult) { 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 174/339] 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 175/339] =?UTF-8?q?cli:=20implement=20daemon=20startup=20s?= =?UTF-8?q?equence=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 176/339] =?UTF-8?q?cli:=20implement=20local=E2=86=92CouchD?= =?UTF-8?q?B=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 177/339] 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 178/339] 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 179/339] (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 180/339] 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 181/339] 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 182/339] 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 183/339] 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 184/339] 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 185/339] 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 186/339] (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 187/339] 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 188/339] 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 189/339] 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 190/339] 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 191/339] 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 192/339] 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 193/339] 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 194/339] 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 195/339] 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 196/339] (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 197/339] 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 198/339] 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 199/339] 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 200/339] 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 201/339] (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 202/339] 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 203/339] 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 204/339] 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 205/339] 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 206/339] 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 207/339] 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 208/339] 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 209/339] 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 210/339] 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 211/339] 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 212/339] 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 213/339] 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 214/339] 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 215/339] 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 216/339] 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 217/339] 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 218/339] 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 219/339] 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 220/339] 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 221/339] 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 @@
- -
-
- - - + +
+
+ + + -
-
-
- {#each messages as line} -
{line}
- {/each} -
+
+
+
+ {#each messages as line} +
{line}
+ {/each} +
diff --git a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts index 9bdfa56..f6bbd80 100644 --- a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts +++ b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts @@ -62,6 +62,7 @@ import { paneAdvanced } from "./PaneAdvanced.ts"; import { panePowerUsers } from "./PanePowerUsers.ts"; import { panePatches } from "./PanePatches.ts"; import { paneMaintenance } from "./PaneMaintenance.ts"; +import { compatGlobal } from "@lib/common/coreEnvFunctions.ts"; // For creating a document const toc = new Set(); @@ -141,7 +142,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { async saveLocalSetting(key: keyof typeof OnDialogSettingsDefault) { if (key == "configPassphrase") { - localStorage.setItem("ls-setting-passphrase", this.editingSettings?.[key] ?? ""); + compatGlobal.localStorage.setItem("ls-setting-passphrase", this.editingSettings?.[key] ?? ""); return await Promise.resolve(); } if (key == "deviceAndVaultName") { @@ -214,7 +215,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { reloadAllLocalSettings() { const ret = { ...OnDialogSettingsDefault }; - ret.configPassphrase = localStorage.getItem("ls-setting-passphrase") || ""; + ret.configPassphrase = compatGlobal.localStorage.getItem("ls-setting-passphrase") || ""; ret.preset = ""; ret.deviceAndVaultName = this.services.setting.getDeviceAndVaultName(); return ret; diff --git a/src/modules/features/SettingDialogue/PanePatches.ts b/src/modules/features/SettingDialogue/PanePatches.ts index 13d3bf3..839f7b2 100644 --- a/src/modules/features/SettingDialogue/PanePatches.ts +++ b/src/modules/features/SettingDialogue/PanePatches.ts @@ -188,7 +188,7 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen } this.requestUpdate(); }; - text.inputEl.before((dateEl = document.createElement("span"))); + text.inputEl.before((dateEl = activeDocument.createElement("span"))); text.inputEl.type = "datetime-local"; if (this.editingSettings.maxMTimeForReflectEvents > 0) { const date = new Date(this.editingSettings.maxMTimeForReflectEvents); diff --git a/src/modules/features/SettingDialogue/settingUtils.ts b/src/modules/features/SettingDialogue/settingUtils.ts index b965298..847ebc0 100644 --- a/src/modules/features/SettingDialogue/settingUtils.ts +++ b/src/modules/features/SettingDialogue/settingUtils.ts @@ -75,7 +75,7 @@ export function getSummaryFromPartialSettings(setting: Partial Date: Mon, 1 Jun 2026 05:28:03 +0100 Subject: [PATCH 275/339] Aligned to eslint rules and fixed following things: This commit may contains behavioural changes. - Fix for the issue with corrupted log displays - Wrap the activeDocument - Reduced potential type errors and strengthened certain checks - Made error handling more robust (by rewriting the error class) --- eslint.config.mjs | 21 ++- package-lock.json | 16 +-- package.json | 3 +- src/features/ConfigSync/CmdConfigSync.ts | 7 +- .../HiddenFileSync/CmdHiddenFileSync.ts | 5 +- .../CmdLocalDatabaseMainte.ts | 131 +++++++++--------- src/lib | 2 +- src/modules/coreObsidian/UILib/dialogs.ts | 7 +- .../GlobalHistory/GlobalHistory.svelte | 2 +- .../ModuleInteractiveConflictResolver.ts | 4 +- .../ObsidianLiveSyncSettingTab.ts | 6 +- .../SettingDialogue/PaneRemoteConfig.ts | 10 +- src/modules/features/SetupManager.ts | 4 +- src/modules/main/ModuleLiveSyncMain.ts | 7 +- .../services/ObsidianSettingService.ts | 10 +- src/serviceFeatures/redFlag.ts | 2 +- 16 files changed, 130 insertions(+), 107 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 0c47cb2..65f81a3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -4,7 +4,7 @@ import globals from "globals"; import { defineConfig, globalIgnores } from "eslint/config"; import * as sveltePlugin from "eslint-plugin-svelte"; import svelteParser from "svelte-eslint-parser"; - +const warnWhileDev = "off"; // Change to "warn" to enable warnings for rules that are currently disabled. export default defineConfig([ globalIgnores([ // Build outputs and legacy files @@ -64,6 +64,9 @@ export default defineConfig([ project: "./tsconfig.json", }, }, + linterOptions:{ + reportUnusedDisableDirectives: false, + }, rules: { // -- Base rules (turned off in favour of TS specific versions or explicitly disabled). "no-unused-vars": "off", @@ -81,29 +84,33 @@ export default defineConfig([ "@typescript-eslint/no-unsafe-return": "off", "@typescript-eslint/no-unsafe-assignment": "off", // -- Reasonable rules. - "@typescript-eslint/no-deprecated": "warn", + "@typescript-eslint/no-deprecated": warnWhileDev, "@typescript-eslint/no-unused-vars": ["error", { args: "none" }], "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/require-await": "warn", + "@typescript-eslint/require-await": "error", "@typescript-eslint/no-misused-promises": "error", "@typescript-eslint/no-floating-promises": "error", "@typescript-eslint/no-unnecessary-type-assertion": "error", // -- Obsidian rules // obsidianmd/no-unsupported-api: usually this project checks for API support at runtime, so this rule is not critical but can be helpful to catch potential issues. - "obsidianmd/no-unsupported-api": "warn", + "obsidianmd/no-unsupported-api": warnWhileDev, // -- General rules - "no-async-promise-executor": "warn", + "no-async-promise-executor": warnWhileDev, "no-constant-condition": ["error", { checkLoops: false }], - // -- Disabled rules (Pending review) + // -- Disabled rules // no-undef: This option breaks the global declarations for the library files and is not worth the effort to fix at this time. "no-undef": "off", - // -- Plugin specific overrides (Pending review) + // -- Plugin specific overrides "obsidianmd/rule-custom-message": "off", "obsidianmd/ui/sentence-case": "off", + "obsidianmd/no-plugin-as-component": "off", + + // -- Temporary overrides for migration + "obsidianmd/no-static-styles-assignment": "off", }, }, { diff --git a/package-lock.json b/package-lock.json index 3912bc4..eb84601 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@smithy/middleware-apply-body-checksum": "^4.3.9", "@smithy/protocol-http": "^5.3.9", "@smithy/querystring-builder": "^4.2.9", + "@smithy/util-retry": "^4.4.5", "@trystero-p2p/nostr": "^0.24.0", "chokidar": "^4.0.0", "commander": "^14.0.3", @@ -3546,9 +3547,9 @@ } }, "node_modules/@smithy/core": { - "version": "3.24.3", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.3.tgz", - "integrity": "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg==", + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.5.tgz", + "integrity": "sha512-Kt8phUg45M15EjhYAbZ+fFikYneijLu9Liugz8ZsYz2i8j0hzGv27LWKpEHYRfvj+LyCOSijpcR/2i8RouV+cA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", @@ -4149,13 +4150,12 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.2.12", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.12.tgz", - "integrity": "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.4.5.tgz", + "integrity": "sha512-W9Ovy9i02yGqtLlpqZNQuXNxXc5OPfXujnembxN/FxyBtGjJd8vKY0PQYEJ8FNybTOcXG+ZxsSsX23HOb3zQzg==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.12", - "@smithy/types": "^4.13.1", + "@smithy/core": "^3.24.5", "tslib": "^2.6.2" }, "engines": { diff --git a/package.json b/package.json index db366fd..6700e33 100644 --- a/package.json +++ b/package.json @@ -130,16 +130,17 @@ "@smithy/middleware-apply-body-checksum": "^4.3.9", "@smithy/protocol-http": "^5.3.9", "@smithy/querystring-builder": "^4.2.9", + "@smithy/util-retry": "^4.4.5", "@trystero-p2p/nostr": "^0.24.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", "markdown-it": "^14.1.1", "micromatch": "^4.0.0", "minimatch": "^10.2.2", + "obsidian": "^1.12.3", "octagonal-wheels": "^0.1.46", "pouchdb-adapter-leveldb": "^9.0.0", "qrcode-generator": "^1.4.4", diff --git a/src/features/ConfigSync/CmdConfigSync.ts b/src/features/ConfigSync/CmdConfigSync.ts index e6f4264..585ef5c 100644 --- a/src/features/ConfigSync/CmdConfigSync.ts +++ b/src/features/ConfigSync/CmdConfigSync.ts @@ -71,6 +71,7 @@ import { PluginDialogModal } from "./PluginDialogModal.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 { LiveSyncError } from "@lib/common/LSError.ts"; const d = "\u200b"; const d2 = "\n"; @@ -1069,10 +1070,10 @@ export class ConfigSync extends LiveSyncCommands { } const baseDir = this.configDir; try { - if (!data.documentPath) throw "InternalError: Document path not exist"; + if (!data.documentPath) throw new LiveSyncError("InternalError: Document path not exist"); const dx = await this.localDatabase.getDBEntry(data.documentPath); if (dx == false) { - throw "Not found on database"; + throw new LiveSyncError("Not found on database"); } const loadedData = deserialize(getDocDataAsArray(dx.data), {}) as PluginDataEx; for (const f of loadedData.files) { @@ -1317,7 +1318,7 @@ export class ConfigSync extends LiveSyncCommands { } const docXDoc = await this.localDatabase.getDBEntryFromMeta(old, false, false); if (docXDoc == false) { - throw "Could not load the document"; + throw new LiveSyncError("Could not load the document"); } const dataSrc = getDocData(docXDoc.data); const dataStart = dataSrc.indexOf(DUMMY_END); diff --git a/src/features/HiddenFileSync/CmdHiddenFileSync.ts b/src/features/HiddenFileSync/CmdHiddenFileSync.ts index ed3aa84..3c086d6 100644 --- a/src/features/HiddenFileSync/CmdHiddenFileSync.ts +++ b/src/features/HiddenFileSync/CmdHiddenFileSync.ts @@ -50,6 +50,7 @@ import { hiddenFilesEventCount, hiddenFilesProcessingCount } from "../../lib/src import { EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts"; import { Semaphore } from "octagonal-wheels/concurrency/semaphore"; import type { LiveSyncCore } from "../../main.ts"; +import { tryGetFilePath } from "@lib/common/utils.doc.ts"; type SyncDirection = "push" | "pull" | "safe" | "pullForce" | "pushForce"; declare global { @@ -1047,7 +1048,7 @@ Offline Changed files: ${processFiles.length}`; } notifyProgress(); } catch (ex) { - this._log(`Failed to process storage change file:${file}`, logLevel); + this._log(`Failed to process storage change file:${tryGetFilePath(file)}`, logLevel); this._log(ex, LOG_LEVEL_VERBOSE); } }); @@ -1159,7 +1160,7 @@ Offline Changed files: ${files.length}`; await this.trackDatabaseFileModification(path, "[Scanning]", true, onlyNew, file); notifyProgress(); } catch (ex) { - this._log(`Failed to process database changes:${file}`); + this._log(`Failed to process database changes:${tryGetFilePath(file)}`); this._log(ex, LOG_LEVEL_VERBOSE); } return; diff --git a/src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts b/src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts index df5f921..54eb7aa 100644 --- a/src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts +++ b/src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts @@ -16,9 +16,8 @@ import { serialized } from "octagonal-wheels/concurrency/lock_v2"; import { arrayToChunkedArray } from "octagonal-wheels/collection"; import { EVENT_ANALYSE_DB_USAGE, EVENT_REQUEST_PERFORM_GC_V3, eventHub } from "@/common/events"; import type { LiveSyncCouchDBReplicator } from "@/lib/src/replication/couchdb/LiveSyncReplicator"; -import { delay, parseHeaderValues } from "@/lib/src/common/utils"; -import { generateCredentialObject } from "@/lib/src/replication/httplib"; -import { _requestToCouchDB } from "@/common/utils"; +import { delay } from "@/lib/src/common/utils"; +// import { _requestToCouchDB } from "@/common/utils"; const DB_KEY_SEQ = "gc-seq"; const DB_KEY_CHUNK_SET = "chunk-set"; const DB_KEY_DOC_USAGE_MAP = "doc-usage-map"; @@ -533,7 +532,7 @@ Success: ${successCount}, Errored: ${errored}`; const docMap = new Map>(); const info = await db.info(); // Total number of revisions to process (approximate) - const maxSeq = new Number(info.update_seq); + const maxSeq = Number.parseInt(`${info.update_seq ?? 0}`, 10); let processed = 0; let read = 0; let errored = 0; @@ -759,68 +758,68 @@ Success: ${successCount}, Errored: ${errored}`; } } - /** - * Compact the database by temporarily setting the revision limit to 1. - * @returns - */ - async compactDatabaseWithRevLimit() { - // Temporarily set revs_limit to 1, perform compaction, and restore the original revs_limit. - // Very dangerous operation, so now suppressed. - return false; - const replicator = this.core.replicator as LiveSyncCouchDBReplicator; - const remote = await replicator.connectRemoteCouchDBWithSetting(this.settings, false, false, true); - if (!remote) { - this._notice("Failed to connect to remote for compaction."); - return; - } - if (typeof remote == "string") { - this._notice(`Failed to connect to remote for compaction. ${remote}`); - return; - } - const customHeaders = parseHeaderValues(this.settings.couchDB_CustomHeaders); - const credential = generateCredentialObject(this.settings); - const request = async (path: string, method: string = "GET", body: any = undefined) => { - const req = await _requestToCouchDB( - this.settings.couchDB_URI.replace(/\/+$/, "") + - (this.settings.couchDB_DBNAME ? `/${this.settings.couchDB_DBNAME}` : ""), - credential, - window.origin, - path, - body, - method, - customHeaders - ); - return req; - }; - let revsLimit = ""; - const req = await request(`_revs_limit`, "GET"); - if (req.status == 200) { - revsLimit = req.text.trim(); - this._info(`Remote database _revs_limit: ${revsLimit}`); - } else { - this._notice(`Failed to get remote database _revs_limit. Status: ${req.status}`); - return; - } - const req2 = await request(`_revs_limit`, "PUT", 1); - if (req2.status == 200) { - this._info(`Set remote database _revs_limit to 1 for compaction.`); - } - try { - await this.compactDatabase(); - } finally { - // Restore revs_limit - if (revsLimit) { - const req3 = await request(`_revs_limit`, "PUT", parseInt(revsLimit)); - if (req3.status == 200) { - this._info(`Restored remote database _revs_limit to ${revsLimit}.`); - } else { - this._notice( - `Failed to restore remote database _revs_limit. Status: ${req3.status} / ${req3.text}` - ); - } - } - } - } + // /** + // * Compact the database by temporarily setting the revision limit to 1. + // * @returns + // */ + // async compactDatabaseWithRevLimit() { + // // Temporarily set revs_limit to 1, perform compaction, and restore the original revs_limit. + // // Very dangerous operation, so now suppressed. + // return Promise.resolve(false); + // const replicator = this.core.replicator as LiveSyncCouchDBReplicator; + // const remote = await replicator.connectRemoteCouchDBWithSetting(this.settings, false, false, true); + // if (!remote) { + // this._notice("Failed to connect to remote for compaction."); + // return; + // } + // if (typeof remote == "string") { + // this._notice(`Failed to connect to remote for compaction. ${remote}`); + // return; + // } + // const customHeaders = parseHeaderValues(this.settings.couchDB_CustomHeaders); + // const credential = generateCredentialObject(this.settings); + // const request = async (path: string, method: string = "GET", body: any = undefined) => { + // const req = await _requestToCouchDB( + // this.settings.couchDB_URI.replace(/\/+$/, "") + + // (this.settings.couchDB_DBNAME ? `/${this.settings.couchDB_DBNAME}` : ""), + // credential, + // window.origin, + // path, + // body, + // method, + // customHeaders + // ); + // return req; + // }; + // let revsLimit = ""; + // const req = await request(`_revs_limit`, "GET"); + // if (req.status == 200) { + // revsLimit = req.text.trim(); + // this._info(`Remote database _revs_limit: ${revsLimit}`); + // } else { + // this._notice(`Failed to get remote database _revs_limit. Status: ${req.status}`); + // return; + // } + // const req2 = await request(`_revs_limit`, "PUT", 1); + // if (req2.status == 200) { + // this._info(`Set remote database _revs_limit to 1 for compaction.`); + // } + // try { + // await this.compactDatabase(); + // } finally { + // // Restore revs_limit + // if (revsLimit) { + // const req3 = await request(`_revs_limit`, "PUT", parseInt(revsLimit)); + // if (req3.status == 200) { + // this._info(`Restored remote database _revs_limit to ${revsLimit}.`); + // } else { + // this._notice( + // `Failed to restore remote database _revs_limit. Status: ${req3.status} / ${req3.text}` + // ); + // } + // } + // } + // } async gcv3() { if (!this.isAvailable()) return; const replicator = this.core.replicator as LiveSyncCouchDBReplicator; diff --git a/src/lib b/src/lib index af21598..2c6c1df 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit af21598b785e03f08a01a9bcb317ff686c39c76f +Subproject commit 2c6c1dfadb8d2128bae3dda2bf2d8dcf1c36470d diff --git a/src/modules/coreObsidian/UILib/dialogs.ts b/src/modules/coreObsidian/UILib/dialogs.ts index 2d8504a..d84bcab 100644 --- a/src/modules/coreObsidian/UILib/dialogs.ts +++ b/src/modules/coreObsidian/UILib/dialogs.ts @@ -1,10 +1,11 @@ import { ButtonComponent } from "@/deps.ts"; import { App, FuzzySuggestModal, MarkdownRenderer, Modal, Plugin, Setting } from "../../../deps.ts"; import { EVENT_PLUGIN_UNLOADED, eventHub } from "../../../common/events.ts"; -import { compatGlobal } from "@lib/common/coreEnvFunctions.ts"; +import { compatGlobal, type CompatIntervalHandle } from "@lib/common/coreEnvFunctions.ts"; class AutoClosableModal extends Modal { _closeByUnload() { + // eslint-disable-next-line @typescript-eslint/unbound-method eventHub.off(EVENT_PLUGIN_UNLOADED, this._closeByUnload); this.close(); } @@ -12,9 +13,11 @@ class AutoClosableModal extends Modal { constructor(app: App) { super(app); this._closeByUnload = this._closeByUnload.bind(this); + // eslint-disable-next-line @typescript-eslint/unbound-method eventHub.once(EVENT_PLUGIN_UNLOADED, this._closeByUnload); } override onClose() { + // eslint-disable-next-line @typescript-eslint/unbound-method eventHub.off(EVENT_PLUGIN_UNLOADED, this._closeByUnload); } } @@ -140,7 +143,7 @@ export class MessageBox extends AutoClosableModal { isManuallyClosed = false; defaultAction: string | undefined; timeout: number | undefined; - timer: ReturnType | undefined = undefined; + timer: CompatIntervalHandle | undefined = undefined; defaultButtonComponent: ButtonComponent | undefined; wideButton: boolean; diff --git a/src/modules/features/GlobalHistory/GlobalHistory.svelte b/src/modules/features/GlobalHistory/GlobalHistory.svelte index f150e72..4aeb0bf 100644 --- a/src/modules/features/GlobalHistory/GlobalHistory.svelte +++ b/src/modules/features/GlobalHistory/GlobalHistory.svelte @@ -248,7 +248,7 @@
- /{entry.dirname.split("/").join(`​/`)} + /{entry.dirname.split("/").join(`\u200b/`)} diff --git a/src/modules/features/ModuleInteractiveConflictResolver.ts b/src/modules/features/ModuleInteractiveConflictResolver.ts index 0c04092..0ef7c12 100644 --- a/src/modules/features/ModuleInteractiveConflictResolver.ts +++ b/src/modules/features/ModuleInteractiveConflictResolver.ts @@ -88,7 +88,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule { return false; } } else { - this._log(`Merge: Something went wrong: ${filename}, (${toDelete})`, LOG_LEVEL_NOTICE); + this._log(`Merge: Something went wrong: ${filename}, (${toDelete as string})`, LOG_LEVEL_NOTICE); return false; } // In here, some merge has been processed. @@ -163,7 +163,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule { this._log(`There are no conflicting files`, LOG_LEVEL_VERBOSE); } } catch (e) { - this._log(`Error while scanning conflicted files: ${e}`, LOG_LEVEL_NOTICE); + this._log(`Error while scanning conflicted files...`, LOG_LEVEL_NOTICE); this._log(e, LOG_LEVEL_VERBOSE); return false; } diff --git a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts index f6bbd80..c06dcfe 100644 --- a/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts +++ b/src/modules/features/SettingDialogue/ObsidianLiveSyncSettingTab.ts @@ -181,7 +181,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { // if (runOnSaved) { const handlers = this.onSavedHandlers .filter((e) => appliedKeys.indexOf(e.key) !== -1) - .map((e) => e.handler(this.editingSettings[e.key as AllSettingItemKey])); + .map((e) => Promise.resolve(e.handler(this.editingSettings[e.key as AllSettingItemKey]))); await Promise.all(handlers); // } keys.forEach((e) => this.refreshSetting(e)); @@ -648,7 +648,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { this.editingSettings.passphrase = ""; } await this.saveAllDirtySettings(); - await this.applyAllSettings(); + await Promise.resolve(this.applyAllSettings()); if (result == OPTION_FETCH) { await this.core.storageAccess.writeFileAuto(FLAGMD_REDFLAG3_HR, ""); this.services.appLifecycle.scheduleRestart(); @@ -739,6 +739,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab { ); } setLevelClass(el, level); + // TODO: Refactor to use Obsidian's recommended way to create heading. + // eslint-disable-next-line obsidianmd/settings-tab/no-manual-html-headings el.createEl("h3", { text: title, cls: "sls-setting-pane-title" }); if (this.menuEl) { this.menuEl.createEl( diff --git a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts index e609e39..8fb94e9 100644 --- a/src/modules/features/SettingDialogue/PaneRemoteConfig.ts +++ b/src/modules/features/SettingDialogue/PaneRemoteConfig.ts @@ -5,6 +5,7 @@ import { DEFAULT_SETTINGS, LOG_LEVEL_NOTICE, type ObsidianLiveSyncSettings, + LOG_LEVEL_VERBOSE, } from "../../../lib/src/common/types.ts"; import { Menu } from "@/deps.ts"; import { $msg } from "../../../lib/src/common/i18n.ts"; @@ -288,7 +289,8 @@ export function paneRemoteConfig( try { parsed = ConnectionStringParser.parse(trimmedURI); } catch (ex) { - this.services.API.addLog(`Failed to import remote configuration: ${ex}`, LOG_LEVEL_NOTICE); + this.services.API.addLog(`Failed to import remote configuration!`, LOG_LEVEL_NOTICE); + this.services.API.addLog(ex, LOG_LEVEL_VERBOSE); return; } @@ -343,9 +345,10 @@ export function paneRemoteConfig( parsed = ConnectionStringParser.parse(config.uri); } catch (ex) { this.services.API.addLog( - `Failed to parse remote configuration '${config.id}' for editing: ${ex}`, + `Failed to parse remote configuration '${config.id}' for editing!`, LOG_LEVEL_NOTICE ); + this.services.API.addLog(ex, LOG_LEVEL_VERBOSE); return; } const workSettings = createBaseRemoteSettings(); @@ -452,9 +455,10 @@ export function paneRemoteConfig( parsed = ConnectionStringParser.parse(config.uri); } catch (ex) { this.services.API.addLog( - `Failed to parse remote configuration '${config.id}': ${ex}`, + `Failed to parse remote configuration '${config.id}' for fetching settings!`, LOG_LEVEL_NOTICE ); + this.services.API.addLog(ex, LOG_LEVEL_VERBOSE); return; } const workSettings = createBaseRemoteSettings(); diff --git a/src/modules/features/SetupManager.ts b/src/modules/features/SetupManager.ts index 3d69968..02f19d6 100644 --- a/src/modules/features/SetupManager.ts +++ b/src/modules/features/SetupManager.ts @@ -227,7 +227,7 @@ export class SetupManager extends AbstractModule { const e2eeConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteE2EE, currentSetting); if (e2eeConf === "cancelled") { this._log("E2EE configuration cancelled.", LOG_LEVEL_NOTICE); - return await false; + return false; } const newSetting = { ...currentSetting, @@ -367,7 +367,7 @@ export class SetupManager extends AbstractModule { const qrResult = await this.dialogManager.open(ScanQRCode); this._log("QR Code dialog closed.", LOG_LEVEL_VERBOSE); // Result is not used, but log it for debugging. - this._log(`QR Code result: ${qrResult}`, LOG_LEVEL_VERBOSE); + this._log(qrResult, LOG_LEVEL_VERBOSE); // QR Code instruction dialog never yields settings directly. return false; } diff --git a/src/modules/main/ModuleLiveSyncMain.ts b/src/modules/main/ModuleLiveSyncMain.ts index d6d04bb..4be2f40 100644 --- a/src/modules/main/ModuleLiveSyncMain.ts +++ b/src/modules/main/ModuleLiveSyncMain.ts @@ -14,6 +14,7 @@ import type { InjectableServiceHub } from "@lib/services/implements/injectable/I import type { LiveSyncCore } from "../../main.ts"; import { initialiseWorkerModule } from "@lib/worker/bgWorker.ts"; import { manifestVersion, packageVersion } from "@lib/common/coreEnvVars.ts"; +import { compatGlobal } from "@lib/common/coreEnvFunctions.ts"; export class ModuleLiveSyncMain extends AbstractModule { async _onLiveSyncReady() { @@ -97,7 +98,7 @@ export class ModuleLiveSyncMain extends AbstractModule { return false; } const lsKey = "obsidian-live-sync-ver" + this.services.vault.getVaultName(); - const last_version = localStorage.getItem(lsKey); + const last_version = compatGlobal.localStorage.getItem(lsKey); const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000); if (lastVersion > this.settings.lastReadUpdates && this.settings.isConfigured) { @@ -119,7 +120,7 @@ export class ModuleLiveSyncMain extends AbstractModule { this.settings.versionUpFlash = $msg("moduleLiveSyncMain.logVersionUpdate"); await this.saveSettings(); } - localStorage.setItem(lsKey, `${VER}`); + compatGlobal.localStorage.setItem(lsKey, `${VER}`); await this.services.database.openDatabase({ databaseEvents: this.services.databaseEvents, replicator: this.services.replicator, @@ -129,7 +130,7 @@ export class ModuleLiveSyncMain extends AbstractModule { // this.$$replicate = this.$$replicate.bind(this); // this.core.$$onLiveSyncReady = this.core.$$onLiveSyncReady.bind(this); await this.core.services.appLifecycle.onLoaded(); - await Promise.all(this.core.addOns.map((e) => e.onload())); + await Promise.all(this.core.addOns.map((e) => Promise.resolve(e.onload()))); return true; } diff --git a/src/modules/services/ObsidianSettingService.ts b/src/modules/services/ObsidianSettingService.ts index a9dfbb4..e684ea5 100644 --- a/src/modules/services/ObsidianSettingService.ts +++ b/src/modules/services/ObsidianSettingService.ts @@ -1,3 +1,4 @@ +import { compatGlobal } from "@/lib/src/common/coreEnvFunctions"; import { type ObsidianLiveSyncSettings } from "@lib/common/types"; import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED } from "@lib/events/coreEvents"; import { eventHub } from "@lib/hub/hub"; @@ -17,13 +18,16 @@ export class ObsidianSettingService extends Se }); } protected setItem(key: string, value: string) { - return localStorage.setItem(key, value); + // TODO: Implement nativeLocalStorage. + return compatGlobal.localStorage.setItem(key, value); } protected getItem(key: string): string { - return localStorage.getItem(key) ?? ""; + // TODO: Implement nativeLocalStorage. + return compatGlobal.localStorage.getItem(key) ?? ""; } protected deleteItem(key: string): void { - localStorage.removeItem(key); + // TODO: Implement nativeLocalStorage. + compatGlobal.localStorage.removeItem(key); } protected override async saveData(data: ObsidianLiveSyncSettings): Promise { diff --git a/src/serviceFeatures/redFlag.ts b/src/serviceFeatures/redFlag.ts index fa1dada..00af3b4 100644 --- a/src/serviceFeatures/redFlag.ts +++ b/src/serviceFeatures/redFlag.ts @@ -73,7 +73,7 @@ export function createFetchAllFlagHandler( return false; } const { vault, extra } = method; - const settings = await host.services.setting.currentSettings(); + const settings = await Promise.resolve(host.services.setting.currentSettings()); // If remote is MinIO, makeLocalChunkBeforeSync is not available. (because no-deduplication on sending). const makeLocalChunkBeforeSyncAvailable = settings.remoteType !== REMOTE_MINIO; const mapVaultStateToAction = { From c6697327d504663315d2141b1f41a14438655434 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 1 Jun 2026 06:20:33 +0100 Subject: [PATCH 276/339] Fixed typings Fixed wrong typing for serviceHub, svelte dialog --- src/lib | 2 +- src/modules/extras/devUtil/TestPaneView.ts | 5 +++++ src/modules/features/ModuleLog.ts | 7 ++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/lib b/src/lib index 2c6c1df..6f97753 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 2c6c1dfadb8d2128bae3dda2bf2d8dcf1c36470d +Subproject commit 6f977537f42bafc41f231a61379b613b22f1773b diff --git a/src/modules/extras/devUtil/TestPaneView.ts b/src/modules/extras/devUtil/TestPaneView.ts index 32e29d4..addbe88 100644 --- a/src/modules/extras/devUtil/TestPaneView.ts +++ b/src/modules/extras/devUtil/TestPaneView.ts @@ -3,6 +3,11 @@ import TestPaneComponent from "./TestPane.svelte"; import type ObsidianLiveSyncPlugin from "../../../main.ts"; import type { ModuleDev } from "../ModuleDev.ts"; export const VIEW_TYPE_TEST = "ols-pane-test"; +declare global { + interface LSEvents { + "debug-sync-status": string[]; + } +} //Log view export class TestPaneView extends ItemView { component?: TestPaneComponent; diff --git a/src/modules/features/ModuleLog.ts b/src/modules/features/ModuleLog.ts index 2fba758..81118d2 100644 --- a/src/modules/features/ModuleLog.ts +++ b/src/modules/features/ModuleLog.ts @@ -516,7 +516,12 @@ ${stringifyYaml(info)} let errorInfo = ""; if (message instanceof Error) { if (message instanceof LiveSyncError) { - errorInfo = `${message.cause?.name}:${message.cause?.message}\n[StackTrace]: ${message.stack}\n[CausedBy]: ${message.cause?.stack}`; + if (message.cause && message.cause instanceof Error) { + const causedError = message.cause; + errorInfo = `${causedError?.name}:${causedError?.message}\n[StackTrace]: ${message.stack}\n[CausedBy]: ${causedError?.stack}`; + } else { + errorInfo = `${message.name}:${message.message}\n[StackTrace]: ${message.stack}`; + } } else { const thisStack = new Error().stack; errorInfo = `${message.name}:${message.message}\n[StackTrace]: ${message.stack}\n[LogCallStack]: ${thisStack}`; From 5a280c791974083956072b5d68a9db5718ffa0d8 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 1 Jun 2026 10:41:48 +0100 Subject: [PATCH 277/339] (feat): Bulk database fetching is now work in progress. This feature is expected to speed up rebuilds and setups. (WIP) --- src/lib | 2 +- src/modules/extras/ModuleDev.ts | 80 +++++++++++++++++++++++++++++++-- updates.md | 7 +++ 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/lib b/src/lib index 6f97753..b143cf8 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 6f977537f42bafc41f231a61379b613b22f1773b +Subproject commit b143cf887bee591d298f6343fa1601fbc8024ab0 diff --git a/src/modules/extras/ModuleDev.ts b/src/modules/extras/ModuleDev.ts index ab48c31..45ccd26 100644 --- a/src/modules/extras/ModuleDev.ts +++ b/src/modules/extras/ModuleDev.ts @@ -1,17 +1,21 @@ import { delay, fireAndForget } from "octagonal-wheels/promises"; import { __onMissingTranslation } from "../../lib/src/common/i18n"; import { AbstractObsidianModule } from "../AbstractObsidianModule.ts"; -import { LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger"; +import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger"; import { eventHub } from "../../common/events"; import { enableTestFunction } from "./devUtil/testUtils.ts"; import { TestPaneView, VIEW_TYPE_TEST } from "./devUtil/TestPaneView.ts"; import { writable } from "svelte/store"; -import type { FilePathWithPrefix } from "../../lib/src/common/types.ts"; +import type { CouchDBCredentials, FilePathWithPrefix } from "../../lib/src/common/types.ts"; import type { LiveSyncCore } from "../../main.ts"; - +import { getConfiguredFunctionsForEncryption } from "@/lib/src/pouchdb/encryption.ts"; +import { AuthorizationHeaderGenerator } from "@/lib/src/replication/httplib.ts"; +import { fetchChangesForInitialSync } from "@/lib/src/pouchdb/StreamingFetch.ts"; +import { PouchDB } from '@lib/pouchdb/pouchdb-browser.ts'; +import { sizeToHumanReadable } from "octagonal-wheels/number"; export class ModuleDev extends AbstractObsidianModule { _everyOnloadStart(): Promise { - __onMissingTranslation(() => {}); + __onMissingTranslation(() => { }); return Promise.resolve(true); } async onMissingTranslation(key: string): Promise { @@ -98,7 +102,75 @@ export class ModuleDev extends AbstractObsidianModule { }); return Promise.resolve(true); } + async _runBulkCopyTest() { + const settings = this.settings; + const dummyLocalDatabaseForDrop = new PouchDB("dummy-local"); + await dummyLocalDatabaseForDrop.destroy(); + const dummyLocalDatabase = new PouchDB("dummy-local"); + const replicator = await this.core.services.replicator.getNewReplicator(); + if (!replicator) { + return; + } + const salt = () => replicator.getReplicationPBKDF2Salt(settings); + const enc = getConfiguredFunctionsForEncryption(settings.passphrase, + false, + false, + salt, + settings.E2EEAlgorithm, + ); + + const auth = ( + settings.useJWT + ? { + jwtAlgorithm: settings.jwtAlgorithm, + jwtKey: settings.jwtKey, + jwtExpDuration: settings.jwtExpDuration, + jwtKid: settings.jwtKid, + jwtSub: settings.jwtSub, + type: "jwt", + } + : { + username: settings.couchDB_USER, + password: settings.couchDB_PASSWORD, + type: "basic", + } + ) satisfies CouchDBCredentials; + const authHeader = await (new AuthorizationHeaderGenerator().getAuthorizationHeader(auth)); + const remote = + settings.couchDB_URI.replace(/\/+$/, "") + + (settings.couchDB_DBNAME == "" ? "" : "/" + settings.couchDB_DBNAME); + // + const ret = fetchChangesForInitialSync( + dummyLocalDatabase, + remote, + authHeader, + enc.outgoing, + "0", + (progress) => { + Logger(`Initial sync progress: ${progress.totalValidFetched} / ${progress.docsToFetch} +Total bytes fetched: ${sizeToHumanReadable(progress.totalBytes)}`, + LOG_LEVEL_NOTICE, "fetch-init-progress" + ); + + } + ); + await ret; + + const allDocs = await dummyLocalDatabase.allDocs({ include_docs: false }); + Logger(`Bulk copy test completed. Total documents in local database: ${allDocs.total_rows}`, LOG_LEVEL_NOTICE, "fetch-init-complete"); + await dummyLocalDatabase.destroy(); + Logger(`Dummy local database has been destroyed after test.`, LOG_LEVEL_NOTICE); + } async _everyOnLayoutReady(): Promise { + + this.addCommand({ + "id": "bulk-copy-test", + "name": "(DEBUG) Bulk copy test", + "callback": async () => { + await this._runBulkCopyTest(); + } + }) + if (!this.settings.enableDebugTools) return Promise.resolve(true); // if (await this.core.storageAccess.isExistsIncludeHidden("_SHOWDIALOGAUTO.md")) { // void this.core.$$showView(VIEW_TYPE_TEST); diff --git a/updates.md b/updates.md index a81c82a..cd54957 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 + +### Under development + +- Bulk database fetching is now work in progress. This feature is expected to speed up rebuilds and setups. + Another feature that is needed is the ability to enforce a specific order during the initial comparison between the storage and the local database. + ## 0.25.70 25th May, 2026 From cd2bff5fc7b406007fa62b4ca0de951764aee13a Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 1 Jun 2026 11:18:23 +0100 Subject: [PATCH 278/339] Refactor types in svelte components. --- src/modules/features/SetupManager.ts | 36 +++--- .../dialogs/FetchEverything.svelte | 65 ++++------- .../features/SetupWizard/dialogs/Intro.svelte | 26 ++--- .../dialogs/OutroAskUserMode.svelte | 23 ++-- .../dialogs/OutroExistingUser.svelte | 19 ++- .../SetupWizard/dialogs/OutroNewUser.svelte | 9 +- .../dialogs/PanelCouchDBCheck.svelte | 6 +- .../dialogs/RebuildEverything.svelte | 30 ++--- .../SetupWizard/dialogs/ScanQRCode.svelte | 6 +- .../dialogs/SelectMethodExisting.svelte | 17 +-- .../dialogs/SelectMethodNewUser.svelte | 15 ++- .../SetupWizard/dialogs/SetupRemote.svelte | 17 +-- .../dialogs/SetupRemoteBucket.svelte | 11 +- .../dialogs/SetupRemoteCouchDB.svelte | 13 +-- .../dialogs/SetupRemoteE2EE.svelte | 12 +- .../SetupWizard/dialogs/SetupRemoteP2P.svelte | 6 +- .../SetupWizard/dialogs/UseSetupURI.svelte | 14 +-- .../SetupWizard/dialogs/setupDialogTypes.ts | 108 ++++++++++++++++++ src/serviceFeatures/redFlag.ts | 5 +- 19 files changed, 268 insertions(+), 170 deletions(-) create mode 100644 src/modules/features/SetupWizard/dialogs/setupDialogTypes.ts diff --git a/src/modules/features/SetupManager.ts b/src/modules/features/SetupManager.ts index 02f19d6..6bc2132 100644 --- a/src/modules/features/SetupManager.ts +++ b/src/modules/features/SetupManager.ts @@ -1,12 +1,16 @@ import { + type BucketSyncSetting, + type CouchDBConnection, + type EncryptionSettings, type ObsidianLiveSyncSettings, + type P2PSyncSetting, DEFAULT_SETTINGS, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, REMOTE_COUCHDB, REMOTE_MINIO, REMOTE_P2P, -} from "../../lib/src/common/types.ts"; +} from "@lib/common/types.ts"; import { isObjectDifferent } from "@lib/common/utils.ts"; import Intro from "./SetupWizard/dialogs/Intro.svelte"; import SelectMethodNewUser from "./SetupWizard/dialogs/SelectMethodNewUser.svelte"; @@ -21,9 +25,15 @@ import SetupRemoteCouchDB from "./SetupWizard/dialogs/SetupRemoteCouchDB.svelte" import SetupRemoteBucket from "./SetupWizard/dialogs/SetupRemoteBucket.svelte"; import SetupRemoteP2P from "./SetupWizard/dialogs/SetupRemoteP2P.svelte"; import SetupRemoteE2EE from "./SetupWizard/dialogs/SetupRemoteE2EE.svelte"; -import { decodeSettingsFromQRCodeData } from "../../lib/src/API/processSetting.ts"; +import { decodeSettingsFromQRCodeData } from "@lib/API/processSetting.ts"; import { AbstractModule } from "../AbstractModule.ts"; import { ConnectionStringParser } from "@lib/common/ConnectionString.ts"; +import type { + OutroAskUserModeResultType, OutroExistingUserResultType, OutroNewUserResultType, + ScanQRCodeResultType, SetupRemoteBucketResultType, SetupRemoteCouchDBResultType, + SetupRemoteE2EEResultType, SetupRemoteP2PResultType, + SetupRemoteResultType, UseSetupURIResultType +} from "./SetupWizard/dialogs/setupDialogTypes.ts"; /** * User modes for onboarding and setup @@ -118,7 +128,7 @@ export class SetupManager extends AbstractModule { * @returns Promise that resolves to true if onboarding completed successfully, false otherwise */ async onUseSetupURI(userMode: UserMode, setupURI: string = ""): Promise { - const newSetting = await this.dialogManager.openWithExplicitCancel(UseSetupURI, setupURI); + const newSetting = await this.dialogManager.openWithExplicitCancel(UseSetupURI, setupURI); if (newSetting === "cancelled") { this._log("Setup URI dialog cancelled.", LOG_LEVEL_NOTICE); return false; @@ -141,7 +151,7 @@ export class SetupManager extends AbstractModule { ): Promise { const originalSetting = JSON.parse(JSON.stringify(currentSetting)) as ObsidianLiveSyncSettings; const baseSetting = JSON.parse(JSON.stringify(originalSetting)) as ObsidianLiveSyncSettings; - const couchConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteCouchDB, originalSetting); + const couchConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteCouchDB, originalSetting); if (couchConf === "cancelled") { this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE); return await this.onOnboard(userMode); @@ -165,7 +175,7 @@ export class SetupManager extends AbstractModule { currentSetting: ObsidianLiveSyncSettings, activate = true ): Promise { - const bucketConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteBucket, currentSetting); + const bucketConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteBucket, currentSetting); if (bucketConf === "cancelled") { this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE); return await this.onOnboard(userMode); @@ -189,7 +199,7 @@ export class SetupManager extends AbstractModule { currentSetting: ObsidianLiveSyncSettings, activate = true ): Promise { - const p2pConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteP2P, currentSetting); + const p2pConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteP2P, currentSetting); if (p2pConf === "cancelled") { this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE); return await this.onOnboard(userMode); @@ -224,7 +234,7 @@ export class SetupManager extends AbstractModule { * @returns */ async onlyE2EEConfiguration(userMode: UserMode, currentSetting: ObsidianLiveSyncSettings): Promise { - const e2eeConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteE2EE, currentSetting); + const e2eeConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteE2EE, currentSetting); if (e2eeConf === "cancelled") { this._log("E2EE configuration cancelled.", LOG_LEVEL_NOTICE); return false; @@ -243,7 +253,7 @@ export class SetupManager extends AbstractModule { * @returns */ async onConfigureManually(originalSetting: ObsidianLiveSyncSettings, userMode: UserMode): Promise { - const e2eeConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteE2EE, originalSetting); + const e2eeConf = await this.dialogManager.openWithExplicitCancel(SetupRemoteE2EE, originalSetting); if (e2eeConf === "cancelled") { this._log("Manual configuration cancelled.", LOG_LEVEL_NOTICE); return await this.onOnboard(userMode); @@ -262,7 +272,7 @@ export class SetupManager extends AbstractModule { * @returns */ async onSelectServer(currentSetting: ObsidianLiveSyncSettings, userMode: UserMode): Promise { - const method = await this.dialogManager.openWithExplicitCancel(SetupRemote); + const method = await this.dialogManager.openWithExplicitCancel(SetupRemote); if (method === "couchdb") { return await this.onCouchDBManualSetup(userMode, currentSetting, true); } else if (method === "bucket") { @@ -290,7 +300,7 @@ export class SetupManager extends AbstractModule { newConf: ObsidianLiveSyncSettings, _userMode: UserMode, activate: boolean = true, - extra: () => void = () => {} + extra: () => void = () => { } ): Promise { newConf = await this.services.setting.adjustSettings({ ...this.settings, @@ -321,7 +331,7 @@ export class SetupManager extends AbstractModule { this._log("Settings from wizard applied.", LOG_LEVEL_NOTICE); return true; } else { - const userModeResult = await this.dialogManager.openWithExplicitCancel(OutroAskUserMode); + const userModeResult = await this.dialogManager.openWithExplicitCancel(OutroAskUserMode); if (userModeResult === "new-user") { userMode = UserMode.NewUser; } else if (userModeResult === "existing-user") { @@ -338,7 +348,7 @@ export class SetupManager extends AbstractModule { } } const component = userMode === UserMode.NewUser ? OutroNewUser : OutroExistingUser; - const confirm = await this.dialogManager.openWithExplicitCancel(component); + const confirm = await this.dialogManager.openWithExplicitCancel(component); if (confirm === "cancelled") { this._log("User cancelled applying settings from wizard..", LOG_LEVEL_NOTICE); return false; @@ -364,7 +374,7 @@ export class SetupManager extends AbstractModule { */ async onPromptQRCodeInstruction(): Promise { - const qrResult = await this.dialogManager.open(ScanQRCode); + const qrResult = await this.dialogManager.open(ScanQRCode); this._log("QR Code dialog closed.", LOG_LEVEL_VERBOSE); // Result is not used, but log it for debugging. this._log(qrResult, LOG_LEVEL_VERBOSE); diff --git a/src/modules/features/SetupWizard/dialogs/FetchEverything.svelte b/src/modules/features/SetupWizard/dialogs/FetchEverything.svelte index e704782..9bb06d7 100644 --- a/src/modules/features/SetupWizard/dialogs/FetchEverything.svelte +++ b/src/modules/features/SetupWizard/dialogs/FetchEverything.svelte @@ -1,47 +1,30 @@ diff --git a/src/modules/features/SetupWizard/dialogs/OutroNewUser.svelte b/src/modules/features/SetupWizard/dialogs/OutroNewUser.svelte index ff39c62..3818a29 100644 --- a/src/modules/features/SetupWizard/dialogs/OutroNewUser.svelte +++ b/src/modules/features/SetupWizard/dialogs/OutroNewUser.svelte @@ -5,14 +5,13 @@ import Question from "@/lib/src/UI/components/Question.svelte"; import Instruction from "@/lib/src/UI/components/Instruction.svelte"; import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte"; - const TYPE_APPLY = "apply"; - const TYPE_CANCELLED = "cancelled"; - type ResultType = typeof TYPE_APPLY | typeof TYPE_CANCELLED; + import { TYPE_APPLY, TYPE_CANCELLED, type OutroNewUserResultType } from "./setupDialogTypes"; + type Props = { - setResult: (result: ResultType) => void; + setResult: (result: OutroNewUserResultType) => void; }; const { setResult }: Props = $props(); - // let userType = $state(TYPE_CANCELLED); + // let userType = $state(TYPE_CANCELLED); diff --git a/src/modules/features/SetupWizard/dialogs/PanelCouchDBCheck.svelte b/src/modules/features/SetupWizard/dialogs/PanelCouchDBCheck.svelte index d2cef32..4ac0f85 100644 --- a/src/modules/features/SetupWizard/dialogs/PanelCouchDBCheck.svelte +++ b/src/modules/features/SetupWizard/dialogs/PanelCouchDBCheck.svelte @@ -2,9 +2,9 @@ /** * Panel to check and fix CouchDB configuration issues */ - import type { ObsidianLiveSyncSettings } from "../../../../lib/src/common/types"; - import Decision from "../../../../lib/src/UI/components/Decision.svelte"; - import UserDecisions from "../../../../lib/src/UI/components/UserDecisions.svelte"; + import type { ObsidianLiveSyncSettings } from "@lib/common/types"; + import Decision from "@lib/UI/components/Decision.svelte"; + import UserDecisions from "@lib/UI/components/UserDecisions.svelte"; import { checkConfig, type ConfigCheckResult, type ResultError, type ResultErrorMessage } from "./utilCheckCouchDB"; type Props = { trialRemoteSetting: ObsidianLiveSyncSettings; diff --git a/src/modules/features/SetupWizard/dialogs/RebuildEverything.svelte b/src/modules/features/SetupWizard/dialogs/RebuildEverything.svelte index 93aa834..1fe06e3 100644 --- a/src/modules/features/SetupWizard/dialogs/RebuildEverything.svelte +++ b/src/modules/features/SetupWizard/dialogs/RebuildEverything.svelte @@ -10,29 +10,17 @@ import InfoNote from "@/lib/src/UI/components/InfoNote.svelte"; import ExtraItems from "@/lib/src/UI/components/ExtraItems.svelte"; import Check from "@/lib/src/UI/components/Check.svelte"; - const TYPE_CANCEL = "cancelled"; + import { + TYPE_CANCEL, + TYPE_BACKUP_DONE, + TYPE_BACKUP_SKIPPED, + TYPE_UNABLE_TO_BACKUP, + type RebuildEverythingResult, + type ResultTypeBackup, + } from "./setupDialogTypes"; - const TYPE_BACKUP_DONE = "backup_done"; - const TYPE_BACKUP_SKIPPED = "backup_skipped"; - const TYPE_UNABLE_TO_BACKUP = "unable_to_backup"; - - type ResultTypeBackup = - | typeof TYPE_BACKUP_DONE - | typeof TYPE_BACKUP_SKIPPED - | typeof TYPE_UNABLE_TO_BACKUP - | typeof TYPE_CANCEL; - - type ResultTypeExtra = { - preventFetchingConfig: boolean; - }; - type ResultType = - | { - backup: ResultTypeBackup; - extra: ResultTypeExtra; - } - | typeof TYPE_CANCEL; type Props = { - setResult: (result: ResultType) => void; + setResult: (result: RebuildEverythingResult) => void; }; const { setResult }: Props = $props(); diff --git a/src/modules/features/SetupWizard/dialogs/ScanQRCode.svelte b/src/modules/features/SetupWizard/dialogs/ScanQRCode.svelte index 57c0621..4a24f46 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"; - type ResultType = typeof TYPE_CLOSE; + import { TYPE_CLOSE, type ScanQRCodeResultType } from "./setupDialogTypes"; + type Props = { - setResult: (_result: ResultType) => void; + setResult: (_result: ScanQRCodeResultType) => void; }; const { setResult }: Props = $props(); diff --git a/src/modules/features/SetupWizard/dialogs/SelectMethodExisting.svelte b/src/modules/features/SetupWizard/dialogs/SelectMethodExisting.svelte index 8c72ffb..982d60c 100644 --- a/src/modules/features/SetupWizard/dialogs/SelectMethodExisting.svelte +++ b/src/modules/features/SetupWizard/dialogs/SelectMethodExisting.svelte @@ -7,16 +7,19 @@ import Options from "@/lib/src/UI/components/Options.svelte"; import Instruction from "@/lib/src/UI/components/Instruction.svelte"; import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte"; - const TYPE_USE_SETUP_URI = "use-setup-uri"; - const TYPE_SCAN_QR_CODE = "scan-qr-code"; - const TYPE_CONFIGURE_MANUALLY = "configure-manually"; - const TYPE_CANCELLED = "cancelled"; - type ResultType = typeof TYPE_USE_SETUP_URI | typeof TYPE_SCAN_QR_CODE | typeof TYPE_CONFIGURE_MANUALLY | typeof TYPE_CANCELLED; + import { + TYPE_USE_SETUP_URI, + TYPE_SCAN_QR_CODE, + TYPE_CONFIGURE_MANUALLY, + TYPE_CANCELLED, + type SelectMethodExistingResultType, + } from "./setupDialogTypes"; + type Props = { - setResult: (result: ResultType) => void; + setResult: (result: SelectMethodExistingResultType) => void; }; const { setResult }: Props = $props(); - let userType = $state(TYPE_CANCELLED); + let userType = $state(TYPE_CANCELLED); let proceedTitle = $derived.by(() => { if (userType === TYPE_USE_SETUP_URI) { return "Proceed with Setup URI"; diff --git a/src/modules/features/SetupWizard/dialogs/SelectMethodNewUser.svelte b/src/modules/features/SetupWizard/dialogs/SelectMethodNewUser.svelte index 88a6aed..36e90fc 100644 --- a/src/modules/features/SetupWizard/dialogs/SelectMethodNewUser.svelte +++ b/src/modules/features/SetupWizard/dialogs/SelectMethodNewUser.svelte @@ -7,15 +7,18 @@ import Options from "@/lib/src/UI/components/Options.svelte"; import Instruction from "@/lib/src/UI/components/Instruction.svelte"; import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte"; - const TYPE_USE_SETUP_URI = "use-setup-uri"; - const TYPE_CONFIGURE_MANUALLY = "configure-manually"; - const TYPE_CANCELLED = "cancelled"; - type ResultType = typeof TYPE_USE_SETUP_URI | typeof TYPE_CONFIGURE_MANUALLY | typeof TYPE_CANCELLED; + import { + TYPE_USE_SETUP_URI, + TYPE_CONFIGURE_MANUALLY, + TYPE_CANCELLED, + type SelectMethodNewUserResultType, + } from "./setupDialogTypes"; + type Props = { - setResult: (result: ResultType) => void; + setResult: (result: SelectMethodNewUserResultType) => void; }; const { setResult }: Props = $props(); - let userType = $state(TYPE_CANCELLED); + let userType = $state(TYPE_CANCELLED); let proceedTitle = $derived.by(() => { if (userType === TYPE_USE_SETUP_URI) { return "Proceed with Setup URI"; diff --git a/src/modules/features/SetupWizard/dialogs/SetupRemote.svelte b/src/modules/features/SetupWizard/dialogs/SetupRemote.svelte index adcb87a..365f117 100644 --- a/src/modules/features/SetupWizard/dialogs/SetupRemote.svelte +++ b/src/modules/features/SetupWizard/dialogs/SetupRemote.svelte @@ -6,16 +6,19 @@ import Options from "@/lib/src/UI/components/Options.svelte"; import Instruction from "@/lib/src/UI/components/Instruction.svelte"; import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte"; - const TYPE_COUCHDB = "couchdb"; - const TYPE_BUCKET = "bucket"; - const TYPE_P2P = "p2p"; - const TYPE_CANCELLED = "cancelled"; - type ResultType = typeof TYPE_COUCHDB | typeof TYPE_BUCKET | typeof TYPE_P2P | typeof TYPE_CANCELLED; + import { + TYPE_COUCHDB, + TYPE_BUCKET, + TYPE_P2P, + TYPE_CANCELLED, + type SetupRemoteResultType, + } from "./setupDialogTypes"; + type Props = { - setResult: (result: ResultType) => void; + setResult: (result: SetupRemoteResultType) => void; }; const { setResult }: Props = $props(); - let userType = $state(TYPE_CANCELLED); + let userType = $state(TYPE_CANCELLED); let proceedTitle = $derived.by(() => { if (userType === TYPE_COUCHDB) { return "Continue to CouchDB setup"; diff --git a/src/modules/features/SetupWizard/dialogs/SetupRemoteBucket.svelte b/src/modules/features/SetupWizard/dialogs/SetupRemoteBucket.svelte index f2270dc..7c04c38 100644 --- a/src/modules/features/SetupWizard/dialogs/SetupRemoteBucket.svelte +++ b/src/modules/features/SetupWizard/dialogs/SetupRemoteBucket.svelte @@ -13,19 +13,18 @@ DEFAULT_SETTINGS, PREFERRED_JOURNAL_SYNC, RemoteTypes, - } from "../../../../lib/src/common/types"; + } from "@lib/common/types"; import { onMount } from "svelte"; - import { getDialogContext, type GuestDialogProps } from "../../../../lib/src/UI/svelteDialog"; - import { copyTo, pickBucketSyncSettings } from "../../../../lib/src/common/utils"; + import { getDialogContext, type GuestDialogProps } from "@lib/UI/svelteDialog"; + import { copyTo, pickBucketSyncSettings } from "@lib/common/utils"; + import { TYPE_CANCELLED, type SetupRemoteBucketResultType } from "./setupDialogTypes"; const default_setting = pickBucketSyncSettings(DEFAULT_SETTINGS); let syncSetting = $state({ ...default_setting }); - type ResultType = typeof TYPE_CANCELLED | BucketSyncSetting; - type Props = GuestDialogProps; - const TYPE_CANCELLED = "cancelled"; + type Props = GuestDialogProps; const { setResult, getInitialData }: Props = $props(); diff --git a/src/modules/features/SetupWizard/dialogs/SetupRemoteCouchDB.svelte b/src/modules/features/SetupWizard/dialogs/SetupRemoteCouchDB.svelte index 671af71..be18b75 100644 --- a/src/modules/features/SetupWizard/dialogs/SetupRemoteCouchDB.svelte +++ b/src/modules/features/SetupWizard/dialogs/SetupRemoteCouchDB.svelte @@ -14,20 +14,19 @@ RemoteTypes, type CouchDBConnection, type ObsidianLiveSyncSettings, - } from "../../../../lib/src/common/types"; - import { isCloudantURI } from "../../../../lib/src/pouchdb/utils_couchdb"; + } from "@lib/common/types"; + import { isCloudantURI } from "@lib/pouchdb/utils_couchdb"; import { onMount } from "svelte"; - import { getDialogContext, type GuestDialogProps } from "../../../../lib/src/UI/svelteDialog"; - import { copyTo, pickCouchDBSyncSettings } from "../../../../lib/src/common/utils"; + import { getDialogContext, type GuestDialogProps } from "@lib/UI/svelteDialog"; + import { copyTo, pickCouchDBSyncSettings } from "@lib/common/utils"; import PanelCouchDBCheck from "./PanelCouchDBCheck.svelte"; + import { TYPE_CANCELLED, type SetupRemoteCouchDBResultType } from "./setupDialogTypes"; const default_setting = pickCouchDBSyncSettings(DEFAULT_SETTINGS); let syncSetting = $state({ ...default_setting }); - type ResultType = typeof TYPE_CANCELLED | CouchDBConnection; - const TYPE_CANCELLED = "cancelled"; - type Props = GuestDialogProps; + type Props = GuestDialogProps; const { setResult, getInitialData }: Props = $props(); onMount(() => { if (getInitialData) { diff --git a/src/modules/features/SetupWizard/dialogs/SetupRemoteE2EE.svelte b/src/modules/features/SetupWizard/dialogs/SetupRemoteE2EE.svelte index 052e97d..f3c40ed 100644 --- a/src/modules/features/SetupWizard/dialogs/SetupRemoteE2EE.svelte +++ b/src/modules/features/SetupWizard/dialogs/SetupRemoteE2EE.svelte @@ -12,13 +12,13 @@ E2EEAlgorithmNames, E2EEAlgorithms, type EncryptionSettings, - } from "../../../../lib/src/common/types"; + } from "@lib/common/types"; import { onMount } from "svelte"; - import type { GuestDialogProps } from "../../../../lib/src/UI/svelteDialog"; - import { copyTo, pickEncryptionSettings } from "../../../../lib/src/common/utils"; - const TYPE_CANCELLED = "cancelled"; - type ResultType = typeof TYPE_CANCELLED | EncryptionSettings; - type Props = GuestDialogProps; + import type { GuestDialogProps } from "@lib/UI/svelteDialog"; + import { copyTo, pickEncryptionSettings } from "@lib/common/utils"; + import { TYPE_CANCELLED, type SetupRemoteE2EEResultType } from "./setupDialogTypes"; + + type Props = GuestDialogProps; const { setResult, getInitialData }: Props = $props(); let default_encryption: EncryptionSettings = { encrypt: true, diff --git a/src/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte b/src/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte index cd2020e..4e1067a 100644 --- a/src/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte +++ b/src/modules/features/SetupWizard/dialogs/SetupRemoteP2P.svelte @@ -26,16 +26,14 @@ import { getDialogContext, type GuestDialogProps } from "@lib/UI/svelteDialog"; import { SETTING_KEY_P2P_DEVICE_NAME } from "@lib/common/types"; import ExtraItems from "@lib/UI/components/ExtraItems.svelte"; + import { TYPE_CANCELLED, type SetupRemoteP2PResultType } from "./setupDialogTypes"; const default_setting = pickP2PSyncSettings(DEFAULT_SETTINGS); let syncSetting = $state({ ...default_setting }); const context = getDialogContext(); let error = $state(""); - const TYPE_CANCELLED = "cancelled"; - type SettingInfo = P2PConnectionInfo; - type ResultType = typeof TYPE_CANCELLED | SettingInfo; - type Props = GuestDialogProps; + type Props = GuestDialogProps; const { setResult, getInitialData }: Props = $props(); onMount(() => { diff --git a/src/modules/features/SetupWizard/dialogs/UseSetupURI.svelte b/src/modules/features/SetupWizard/dialogs/UseSetupURI.svelte index d6152b7..d1ed1ac 100644 --- a/src/modules/features/SetupWizard/dialogs/UseSetupURI.svelte +++ b/src/modules/features/SetupWizard/dialogs/UseSetupURI.svelte @@ -1,6 +1,6 @@