From 0742773e1eacdc675add16fa5ac479aca02aa6d0 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 11 Mar 2026 14:51:01 +0100 Subject: [PATCH] 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"), + }, +});