From fe25a77d332b905e05766a399e58d32ee46daf36 Mon Sep 17 00:00:00 2001 From: Michael Naumov Date: Tue, 26 May 2026 12:37:41 -0600 Subject: [PATCH] chore: update template --- cspell.json | 3 +- package-lock.json | 861 ++++++++++++-------------- package.json | 21 +- scripts/vitest-config.ts | 5 +- src/main.test.ts | 7 +- src/plugin-settings-component.test.ts | 42 -- src/plugin-settings-component.ts | 23 +- src/plugin-settings-tab.test.ts | 166 +++-- src/plugin-settings-tab.ts | 23 - src/plugin.test.ts | 733 +++++++++++++++++++++- src/plugin.ts | 62 +- tsconfig.json | 2 +- 12 files changed, 1317 insertions(+), 631 deletions(-) delete mode 100644 src/plugin-settings-component.test.ts diff --git a/cspell.json b/cspell.json index 532cf7a..30e5519 100644 --- a/cspell.json +++ b/cspell.json @@ -17,7 +17,8 @@ "mnaoumov", "Naumov", "noopAsync", - "tsbuildinfo" + "tsbuildinfo", + "unsanitized" ], "ignoreWords": [], "import": [], diff --git a/package-lock.json b/package-lock.json index 9e37ffc..22678a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,11 +12,11 @@ "@commitlint/cli": "^21.0.1", "@commitlint/config-conventional": "^21.0.1", "@commitlint/types": "^21.0.1", - "@obsidian-typings/obsidian-public-latest": "^6.4.0", + "@obsidian-typings/obsidian-public-latest": "^6.11.0", "@total-typescript/ts-reset": "^0.6.1", "@tsconfig/strictest": "^2.0.8", - "@types/node": "^25.8.0", - "@vitest/coverage-v8": "^4.1.6", + "@types/node": "^25.9.1", + "@vitest/coverage-v8": "^4.1.7", "better-typescript-lib": "^2.12.0", "czg": "^1.13.1", "eslint": "^10.4.0", @@ -25,10 +25,11 @@ "jsdom": "^29.1.1", "nano-staged": "^1.0.2", "obsidian": "^1.12.3", - "obsidian-dev-utils": "^66.0.1", - "obsidian-test-mocks": "^2.0.3", - "sass-embedded": "^1.99.0", - "vitest": "^4.1.6" + "obsidian-dev-utils": "^69.1.1", + "obsidian-integration-testing": "^4.1.5", + "obsidian-test-mocks": "^3.0.0", + "sass-embedded": "^1.100.0", + "vitest": "^4.1.7" } }, "node_modules/@asamuzakjp/css-color": { @@ -2516,9 +2517,9 @@ } }, "node_modules/@obsidian-typings/obsidian-public-1.12.7": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@obsidian-typings/obsidian-public-1.12.7/-/obsidian-public-1.12.7-6.5.0.tgz", - "integrity": "sha512-LWl5RjJyOmdqfbBNL4lfpYHpWuT8FGtawbf+f6BQmEVgqk8I4dp8AIFtRI6BdFPgi1HuZGqhebAyaHQgmhhDaw==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@obsidian-typings/obsidian-public-1.12.7/-/obsidian-public-1.12.7-6.13.0.tgz", + "integrity": "sha512-rmJy3FJvyIYzaQykcF9y3Q+bVG9sOygW0rtpe3w60r/8ViAhMAVfPACujur/p8mWsUlu3aGjq0XMj8gLLRSHHA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -2526,19 +2527,19 @@ } }, "node_modules/@obsidian-typings/obsidian-public-latest": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@obsidian-typings/obsidian-public-latest/-/obsidian-public-latest-6.4.0.tgz", - "integrity": "sha512-Z6/wvxZmmLaaR4NtOhzir9FSQANG6yzD5UwfVIEHwmPMjvs0lB54QI8K3FQMz1hvRHnFiGpBPhSn83wknt7sQw==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@obsidian-typings/obsidian-public-latest/-/obsidian-public-latest-6.11.0.tgz", + "integrity": "sha512-WbeuSiEnfO5uThitJFGFf2JiJ+AW7nlHYOzqcF9mHUU3DaIaaVMUzGYcjGhWqL6sfV4hOgSAMaHgtWpkMVP/5w==", "dev": true, "license": "MIT", "dependencies": { - "@obsidian-typings/obsidian-public-1.12.7": "^6.5.0" + "@obsidian-typings/obsidian-public-1.12.7": "^6.13.0" } }, "node_modules/@oxc-project/types": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz", - "integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==", + "version": "0.132.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", + "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", "dev": true, "license": "MIT", "funding": { @@ -2925,9 +2926,9 @@ } }, "node_modules/@puppeteer/browsers": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-3.0.2.tgz", - "integrity": "sha512-JnOSHrAdCQOj27P5QnTrd6bkYd9cXXeFMJS5UJF3UmQbpZQAMMO7AaL0NyrT7i2l/43bwjaHguU+LOpBRyx66w==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-3.0.3.tgz", + "integrity": "sha512-v3YaiGpzUTgOZkHBFR0iZg58Vto25SqBQxfLUXDiofJccwVl6Mlr7BdLCS1NZgxikdeIHf936cxYWL9IZp3tow==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3076,9 +3077,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz", - "integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz", + "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==", "cpu": [ "arm64" ], @@ -3093,9 +3094,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz", - "integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==", "cpu": [ "arm64" ], @@ -3110,9 +3111,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz", - "integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz", + "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==", "cpu": [ "x64" ], @@ -3127,9 +3128,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz", - "integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz", + "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==", "cpu": [ "x64" ], @@ -3144,9 +3145,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz", - "integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz", + "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==", "cpu": [ "arm" ], @@ -3161,9 +3162,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz", - "integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz", + "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==", "cpu": [ "arm64" ], @@ -3181,9 +3182,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz", - "integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz", + "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==", "cpu": [ "arm64" ], @@ -3201,9 +3202,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz", - "integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz", + "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==", "cpu": [ "ppc64" ], @@ -3221,9 +3222,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz", - "integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz", + "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==", "cpu": [ "s390x" ], @@ -3241,9 +3242,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz", - "integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz", + "integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==", "cpu": [ "x64" ], @@ -3261,9 +3262,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.1.tgz", - "integrity": "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz", + "integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==", "cpu": [ "x64" ], @@ -3281,9 +3282,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.1.tgz", - "integrity": "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz", + "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==", "cpu": [ "arm64" ], @@ -3298,9 +3299,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.1.tgz", - "integrity": "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz", + "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==", "cpu": [ "wasm32" ], @@ -3317,9 +3318,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz", - "integrity": "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz", + "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==", "cpu": [ "arm64" ], @@ -3334,9 +3335,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.1.tgz", - "integrity": "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz", + "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==", "cpu": [ "x64" ], @@ -3682,9 +3683,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.8.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.8.0.tgz", - "integrity": "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ==", + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", "dev": true, "license": "MIT", "dependencies": { @@ -3720,9 +3721,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", - "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", + "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3804,17 +3805,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.3.tgz", - "integrity": "sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.0.tgz", + "integrity": "sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.59.3", - "@typescript-eslint/type-utils": "8.59.3", - "@typescript-eslint/utils": "8.59.3", - "@typescript-eslint/visitor-keys": "8.59.3", + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/type-utils": "8.60.0", + "@typescript-eslint/utils": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -3827,22 +3828,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.59.3", + "@typescript-eslint/parser": "^8.60.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.3.tgz", - "integrity": "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.0.tgz", + "integrity": "sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.59.3", - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/typescript-estree": "8.59.3", - "@typescript-eslint/visitor-keys": "8.59.3", + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", "debug": "^4.4.3" }, "engines": { @@ -3858,14 +3859,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.3.tgz", - "integrity": "sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.0.tgz", + "integrity": "sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.59.3", - "@typescript-eslint/types": "^8.59.3", + "@typescript-eslint/tsconfig-utils": "^8.60.0", + "@typescript-eslint/types": "^8.60.0", "debug": "^4.4.3" }, "engines": { @@ -3880,14 +3881,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.3.tgz", - "integrity": "sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.0.tgz", + "integrity": "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/visitor-keys": "8.59.3" + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3898,9 +3899,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.3.tgz", - "integrity": "sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.0.tgz", + "integrity": "sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==", "dev": true, "license": "MIT", "engines": { @@ -3915,15 +3916,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.3.tgz", - "integrity": "sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.0.tgz", + "integrity": "sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/typescript-estree": "8.59.3", - "@typescript-eslint/utils": "8.59.3", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/utils": "8.60.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -3940,9 +3941,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.3.tgz", - "integrity": "sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz", + "integrity": "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==", "dev": true, "license": "MIT", "engines": { @@ -3954,16 +3955,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.3.tgz", - "integrity": "sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.0.tgz", + "integrity": "sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.59.3", - "@typescript-eslint/tsconfig-utils": "8.59.3", - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/visitor-keys": "8.59.3", + "@typescript-eslint/project-service": "8.60.0", + "@typescript-eslint/tsconfig-utils": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -3982,16 +3983,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.3.tgz", - "integrity": "sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.0.tgz", + "integrity": "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.59.3", - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/typescript-estree": "8.59.3" + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4006,13 +4007,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.3.tgz", - "integrity": "sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.0.tgz", + "integrity": "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/types": "8.60.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -4517,14 +4518,14 @@ ] }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.6.tgz", - "integrity": "sha512-36l628fQ/9a/8ihy97eOtEnvWQEdqULQOJtcaxtoNq0G1w3Mxd4szSahOaMM9/NGyZ+hyKcMtIW/WIxq0XQViQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.7.tgz", + "integrity": "sha512-qsYPeXc5Q9dFLd1i8Ap+Bx8sQgcp+rFVQo4R0dDsWNBzl26ldVF1qOO+RL24K7FDrR6pA+50XedRLSoSG24bVQ==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.6", + "@vitest/utils": "4.1.7", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -4538,8 +4539,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.6", - "vitest": "4.1.6" + "@vitest/browser": "4.1.7", + "vitest": "4.1.7" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -4548,16 +4549,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz", - "integrity": "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.7.tgz", + "integrity": "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.6", - "@vitest/utils": "4.1.6", + "@vitest/spy": "4.1.7", + "@vitest/utils": "4.1.7", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -4566,13 +4567,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.6.tgz", - "integrity": "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.7.tgz", + "integrity": "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.6", + "@vitest/spy": "4.1.7", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -4593,9 +4594,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.6.tgz", - "integrity": "sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.7.tgz", + "integrity": "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==", "dev": true, "license": "MIT", "dependencies": { @@ -4606,13 +4607,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.6.tgz", - "integrity": "sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.7.tgz", + "integrity": "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.6", + "@vitest/utils": "4.1.7", "pathe": "^2.0.3" }, "funding": { @@ -4620,14 +4621,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.6.tgz", - "integrity": "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.7.tgz", + "integrity": "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.6", - "@vitest/utils": "4.1.6", + "@vitest/pretty-format": "4.1.7", + "@vitest/utils": "4.1.7", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -4636,9 +4637,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.6.tgz", - "integrity": "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.7.tgz", + "integrity": "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==", "dev": true, "license": "MIT", "funding": { @@ -4646,13 +4647,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.6.tgz", - "integrity": "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.7.tgz", + "integrity": "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.6", + "@vitest/pretty-format": "4.1.7", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -4687,9 +4688,9 @@ "license": "MIT" }, "node_modules/@wdio/config/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { @@ -5286,9 +5287,9 @@ "license": "MIT" }, "node_modules/archiver-utils/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { @@ -7203,9 +7204,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.21.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.3.tgz", - "integrity": "sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.22.0.tgz", + "integrity": "sha512-xYcDWrpELkFzz9SpZ3PlI6Eu6eD93Yf0WLDRxikGhWJ3MAir2SNZTIVCVZqZ/NUyx8AdMc2gT9C0gPiw18kG+A==", "dev": true, "license": "MIT", "dependencies": { @@ -7934,9 +7935,9 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "62.9.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-62.9.0.tgz", - "integrity": "sha512-PY7/X4jrVgoIDncUmITlUqK546Ltmx/Pd4Hdsu4CvSjryQZJI2mEV4vrdMufyTetMiZ5taNSqvK//BTgVUlNkA==", + "version": "63.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-63.0.0.tgz", + "integrity": "sha512-eDHuVGyZydr4BKgjXouU7bsn5qaqUlObXBSWRJk3vXcQgXVFnrwWIqpP7uBhRX9NQpk6NIIFyRc6F6omZNi/8g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -7951,12 +7952,12 @@ "html-entities": "^2.6.0", "object-deep-merge": "^2.0.0", "parse-imports-exports": "^0.2.4", - "semver": "^7.7.4", + "semver": "^7.8.0", "spdx-expression-parse": "^4.0.0", "to-valid-identifier": "^1.0.0" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": "^22.13.0 || >=24" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0" @@ -11311,9 +11312,9 @@ } }, "node_modules/lru-cache": { - "version": "11.3.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", - "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.0.tgz", + "integrity": "sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -11787,15 +11788,6 @@ "dev": true, "license": "MIT" }, - "node_modules/memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", - "dev": true, - "engines": { - "node": ">= 0.10.0" - } - }, "node_modules/meow": { "version": "13.2.0", "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", @@ -12647,69 +12639,6 @@ "node": ">=0.10.0" } }, - "node_modules/npm-normalize-package-bin": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", - "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm-run-all2": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-8.0.4.tgz", - "integrity": "sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "cross-spawn": "^7.0.6", - "memorystream": "^0.3.1", - "picomatch": "^4.0.2", - "pidtree": "^0.6.0", - "read-package-json-fast": "^4.0.0", - "shell-quote": "^1.7.3", - "which": "^5.0.0" - }, - "bin": { - "npm-run-all": "bin/npm-run-all/index.js", - "npm-run-all2": "bin/npm-run-all/index.js", - "run-p": "bin/run-p/index.js", - "run-s": "bin/run-s/index.js" - }, - "engines": { - "node": "^20.5.0 || >=22.0.0", - "npm": ">= 10" - } - }, - "node_modules/npm-run-all2/node_modules/isexe": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", - "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/npm-run-all2/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -12734,9 +12663,9 @@ } }, "node_modules/object-deep-merge": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/object-deep-merge/-/object-deep-merge-2.0.0.tgz", - "integrity": "sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object-deep-merge/-/object-deep-merge-2.0.1.tgz", + "integrity": "sha512-aKttDKcU3pyZqKcCkDhsMn70WmZFG2JGDQLP9EcLyTSIFQRCPWLAmBZRLJnrVUrhPG1jETEEbfdgbNtJf1LyMg==", "dev": true, "license": "MIT" }, @@ -12869,9 +12798,9 @@ } }, "node_modules/obsidian-dev-utils": { - "version": "66.0.1", - "resolved": "https://registry.npmjs.org/obsidian-dev-utils/-/obsidian-dev-utils-66.0.1.tgz", - "integrity": "sha512-14YLybZvmokxRxleQx7X/ToB08xicrYPgzQ8fmgwkHgg7ZuMoFcfH21mTnkza12BC1SKyJSWpQRZw5L+1hSEbg==", + "version": "69.1.1", + "resolved": "https://registry.npmjs.org/obsidian-dev-utils/-/obsidian-dev-utils-69.1.1.tgz", + "integrity": "sha512-rpg/eEOm8ivw2KqTtJUDA3bO3hGFju6KUXSf4tn1hy4kkl/7MYOo5agJTbR1n8b12kVu7BvVpiWMVU/Gy17dTw==", "dev": true, "license": "MIT", "dependencies": { @@ -12882,36 +12811,36 @@ "@eslint/config-helpers": "^0.6.0", "@eslint/js": "^10.0.1", "@lezer/common": "1.2.3", - "@obsidian-typings/obsidian-public-latest": "^6.3.0", + "@obsidian-typings/obsidian-public-latest": "^6.10.0", "@stylistic/eslint-plugin": "^5.10.0", "@tsconfig/strictest": "^2.0.8", "@types/babel__core": "^7.20.5", "@types/doctrine": "^0.0.9", "@types/eslint": "^9.6.1", "@types/luxon": "^3.7.1", - "@types/node": "^25.8.0", + "@types/node": "^25.9.1", "@types/parsimmon": "^1.10.9", "@types/path-browserify": "^1.0.3", "@types/picomatch": "^4.0.3", "@types/pug": "^2.0.10", - "@types/react": "^19.2.14", + "@types/react": "^19.2.15", "@types/semver": "^7.7.1", "@types/shell-quote": "^1.7.5", - "@typescript-eslint/eslint-plugin": "^8.59.3", - "@typescript-eslint/parser": "^8.59.3", + "@typescript-eslint/eslint-plugin": "^8.60.0", + "@typescript-eslint/parser": "^8.60.0", "commander": "^14.0.3", "compare-versions": "^6.1.1", "cspell": "^10.0.0", "debug": "^4.4.3", "dprint": "^0.54.0", - "enhanced-resolve": "^5.21.3", + "enhanced-resolve": "^5.22.0", "esbuild": "^0.28.0", "esbuild-sass-plugin": "^3.7.0", "esbuild-svelte": "^0.9.5", "eslint": "^10.4.0", "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-import-x": "^4.16.2", - "eslint-plugin-jsdoc": "^62.9.0", + "eslint-plugin-jsdoc": "^63.0.0", "eslint-plugin-no-unsanitized": "^4.1.5", "eslint-plugin-obsidianmd": "^0.3.0", "eslint-plugin-perfectionist": "^5.9.0", @@ -12921,30 +12850,29 @@ "jiti": "^2.7.0", "linkinator": "^7.6.1", "localforage": "^1.10.0", - "lru-cache": "^11.3.6", + "lru-cache": "^11.5.0", "markdownlint": "^0.40.0", "markdownlint-cli2": "^0.22.1", "markdownlint-rule-relative-links": "^5.1.0", "monkey-around": "^3.0.0", - "npm-run-all2": "^8.0.4", "obsidian": "^1.12.3", - "obsidian-integration-testing": "^4.1.4", + "obsidian-integration-testing": "^4.1.5", "path-browserify": "^1.0.1", - "postcss": "^8.5.14", + "postcss": "^8.5.15", "postcss-modules": "^6.0.1", "preact": "^10.29.2", "react": "^19.2.6", "remark": "^15.0.1", "remark-parse": "^11.0.0", "remark-wiki-link": "^2.0.1", - "sass": "^1.99.0", - "semver": "^7.8.0", - "shell-quote": "^1.8.3", + "sass": "^1.100.0", + "semver": "^7.8.1", + "shell-quote": "^1.8.4", "svelte-check": "^4.4.8", - "svelte-preprocess": "^6.0.3", + "svelte-preprocess": "^6.0.5", "type-fest": "^5.6.0", "typescript": "^6.0.3", - "typescript-eslint": "^8.59.3", + "typescript-eslint": "^8.60.0", "unist-util-visit": "^5.1.0" }, "engines": { @@ -12952,14 +12880,14 @@ } }, "node_modules/obsidian-integration-testing": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/obsidian-integration-testing/-/obsidian-integration-testing-4.1.4.tgz", - "integrity": "sha512-UMtA4veiSQrhKCcLy8xxo+N8bVwELy0z1IXYQHOiPkdzeWUrHfEkUFh63khPIs6NBZz5Rm7KsC9kbEjTZ1wy3g==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/obsidian-integration-testing/-/obsidian-integration-testing-4.1.5.tgz", + "integrity": "sha512-MVXCSO9ahUROjoD34LYGAwCoTYWQtjmYPmCViEuE1mw9mRX5IjAIcGp3E/q8ArP5m1VVU1HbCixQOe2fDAVUHg==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "puppeteer-core": "^25.0.2", + "puppeteer-core": "^25.0.4", "webdriverio": "9.27.0" }, "peerDependencies": { @@ -12968,9 +12896,9 @@ } }, "node_modules/obsidian-test-mocks": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/obsidian-test-mocks/-/obsidian-test-mocks-2.0.3.tgz", - "integrity": "sha512-HC1PfXuDquFH1Ld7wOoECPAz5Kv8dfk+T45tewZwYCWXGZnf/vRhg/9nU+jpZiaiNnBJ2kGg8XtYSPTqW9dFUA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/obsidian-test-mocks/-/obsidian-test-mocks-3.0.0.tgz", + "integrity": "sha512-uvupfNimjtze8m7gkm9XFRS2OMVlurRILxlhcl3EcsYz3nQmnpYjFb91A51Q7cJ9O/ybvY0yI3hXhG3+TS09kA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -13321,19 +13249,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pidtree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", - "dev": true, - "license": "MIT", - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -13345,9 +13260,9 @@ } }, "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -13365,7 +13280,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -13559,13 +13474,13 @@ } }, "node_modules/puppeteer-core": { - "version": "25.0.2", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-25.0.2.tgz", - "integrity": "sha512-Q0IUIHER1S9PiNIfdNFc+pVOj79Tp4b9v0Fv4enigwsLy0Hbgq45KFgqzmN31DeCXh+Uvxnt9r7fMERhAMjs8Q==", + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-25.0.4.tgz", + "integrity": "sha512-K1LQKDP6w1rIr1jUyN9obH16TO/DCy86k3q+FBd2prGY+TStxhFySxmaZZuRF+0D3BJXjwCYFke7tMHCH4olTA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "3.0.2", + "@puppeteer/browsers": "3.0.3", "chromium-bidi": "16.0.1", "debug": "^4.4.3", "devtools-protocol": "0.0.1608973", @@ -13622,30 +13537,6 @@ "dev": true, "license": "MIT" }, - "node_modules/read-package-json-fast": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz", - "integrity": "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==", - "dev": true, - "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^4.0.0", - "npm-normalize-package-bin": "^4.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/read-package-json-fast/node_modules/json-parse-even-better-errors": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", - "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/readable-stream": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", @@ -13681,9 +13572,9 @@ "license": "MIT" }, "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { @@ -13964,13 +13855,13 @@ "license": "MIT" }, "node_modules/rolldown": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz", - "integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz", + "integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.130.0", + "@oxc-project/types": "=0.132.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -13980,21 +13871,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.1", - "@rolldown/binding-darwin-arm64": "1.0.1", - "@rolldown/binding-darwin-x64": "1.0.1", - "@rolldown/binding-freebsd-x64": "1.0.1", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.1", - "@rolldown/binding-linux-arm64-gnu": "1.0.1", - "@rolldown/binding-linux-arm64-musl": "1.0.1", - "@rolldown/binding-linux-ppc64-gnu": "1.0.1", - "@rolldown/binding-linux-s390x-gnu": "1.0.1", - "@rolldown/binding-linux-x64-gnu": "1.0.1", - "@rolldown/binding-linux-x64-musl": "1.0.1", - "@rolldown/binding-openharmony-arm64": "1.0.1", - "@rolldown/binding-wasm32-wasi": "1.0.1", - "@rolldown/binding-win32-arm64-msvc": "1.0.1", - "@rolldown/binding-win32-x64-msvc": "1.0.1" + "@rolldown/binding-android-arm64": "1.0.2", + "@rolldown/binding-darwin-arm64": "1.0.2", + "@rolldown/binding-darwin-x64": "1.0.2", + "@rolldown/binding-freebsd-x64": "1.0.2", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.2", + "@rolldown/binding-linux-arm64-gnu": "1.0.2", + "@rolldown/binding-linux-arm64-musl": "1.0.2", + "@rolldown/binding-linux-ppc64-gnu": "1.0.2", + "@rolldown/binding-linux-s390x-gnu": "1.0.2", + "@rolldown/binding-linux-x64-gnu": "1.0.2", + "@rolldown/binding-linux-x64-musl": "1.0.2", + "@rolldown/binding-openharmony-arm64": "1.0.2", + "@rolldown/binding-wasm32-wasi": "1.0.2", + "@rolldown/binding-win32-arm64-msvc": "1.0.2", + "@rolldown/binding-win32-x64-msvc": "1.0.2" } }, "node_modules/run-parallel": { @@ -14171,13 +14062,13 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.99.0.tgz", - "integrity": "sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.100.0.tgz", + "integrity": "sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==", "dev": true, "license": "MIT", "dependencies": { - "chokidar": "^4.0.0", + "chokidar": "^5.0.0", "immutable": "^5.1.5", "source-map-js": ">=0.6.2 <2.0.0" }, @@ -14185,16 +14076,16 @@ "sass": "sass.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.19.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" } }, "node_modules/sass-embedded": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.99.0.tgz", - "integrity": "sha512-gF/juR1aX02lZHkvwxdF80SapkQeg2fetoDF6gIQkNbSw5YEUFspMkyGTjPjgZSgIHuZpy+Wz4PlebKnLXMjdg==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.100.0.tgz", + "integrity": "sha512-Ut8wlQSk19tm7jMK6mz6cF1+e+E7tUnW2tM02zQDPnOTcVbV8qCQG8UWxZkkNlY50+hV3hqP24OOkUlMz8xBpw==", "dev": true, "license": "MIT", "dependencies": { @@ -14213,30 +14104,30 @@ "node": ">=16.0.0" }, "optionalDependencies": { - "sass-embedded-all-unknown": "1.99.0", - "sass-embedded-android-arm": "1.99.0", - "sass-embedded-android-arm64": "1.99.0", - "sass-embedded-android-riscv64": "1.99.0", - "sass-embedded-android-x64": "1.99.0", - "sass-embedded-darwin-arm64": "1.99.0", - "sass-embedded-darwin-x64": "1.99.0", - "sass-embedded-linux-arm": "1.99.0", - "sass-embedded-linux-arm64": "1.99.0", - "sass-embedded-linux-musl-arm": "1.99.0", - "sass-embedded-linux-musl-arm64": "1.99.0", - "sass-embedded-linux-musl-riscv64": "1.99.0", - "sass-embedded-linux-musl-x64": "1.99.0", - "sass-embedded-linux-riscv64": "1.99.0", - "sass-embedded-linux-x64": "1.99.0", - "sass-embedded-unknown-all": "1.99.0", - "sass-embedded-win32-arm64": "1.99.0", - "sass-embedded-win32-x64": "1.99.0" + "sass-embedded-all-unknown": "1.100.0", + "sass-embedded-android-arm": "1.100.0", + "sass-embedded-android-arm64": "1.100.0", + "sass-embedded-android-riscv64": "1.100.0", + "sass-embedded-android-x64": "1.100.0", + "sass-embedded-darwin-arm64": "1.100.0", + "sass-embedded-darwin-x64": "1.100.0", + "sass-embedded-linux-arm": "1.100.0", + "sass-embedded-linux-arm64": "1.100.0", + "sass-embedded-linux-musl-arm": "1.100.0", + "sass-embedded-linux-musl-arm64": "1.100.0", + "sass-embedded-linux-musl-riscv64": "1.100.0", + "sass-embedded-linux-musl-x64": "1.100.0", + "sass-embedded-linux-riscv64": "1.100.0", + "sass-embedded-linux-x64": "1.100.0", + "sass-embedded-unknown-all": "1.100.0", + "sass-embedded-win32-arm64": "1.100.0", + "sass-embedded-win32-x64": "1.100.0" } }, "node_modules/sass-embedded-all-unknown": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.99.0.tgz", - "integrity": "sha512-qPIRG8Uhjo6/OKyAKixTnwMliTz+t9K6Duk0mx5z+K7n0Ts38NSJz2sjDnc7cA/8V9Lb3q09H38dZ1CLwD+ssw==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.100.0.tgz", + "integrity": "sha512-auFtXY/kwYILmSVjtBDwyj0axcLbYYiffOKWoaXHnI5bsYwiRbBh3EneR1rpbX2ZIZCrwX93i5pxKLTZF/662Q==", "cpu": [ "!arm", "!arm64", @@ -14247,13 +14138,13 @@ "license": "MIT", "optional": true, "dependencies": { - "sass": "1.99.0" + "sass": "1.100.0" } }, "node_modules/sass-embedded-android-arm": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.99.0.tgz", - "integrity": "sha512-EHvJ0C7/VuP78Qr6f8gIUVUmCqIorEQpw2yp3cs3SMg02ZuumlhjXvkTcFBxHmFdFR23vTNk1WnhY6QSeV1nFQ==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.100.0.tgz", + "integrity": "sha512-70f3HgX2pFNmzpGQ86n5e6QfWn2fP4QUQGfFQK0P1XH73ZLIzLo2YqygrGKGKeeqtc5eU2Wl1/xQzhzuKnO4kw==", "cpu": [ "arm" ], @@ -14268,9 +14159,9 @@ } }, "node_modules/sass-embedded-android-arm64": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.99.0.tgz", - "integrity": "sha512-fNHhdnP23yqqieCbAdym4N47AleSwjbNt6OYIYx4DdACGdtERjQB4iOX/TaKsW034MupfF7SjnAAK8w7Ptldtg==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.100.0.tgz", + "integrity": "sha512-W+Ru9JwTnfU0UX3jSZcbqFdtKFMcYdfFwytc57h2DgnqCOIiAqI2E06mABZBZC+r3LwXCBuS5GbXAGeVgvVDkA==", "cpu": [ "arm64" ], @@ -14285,9 +14176,9 @@ } }, "node_modules/sass-embedded-android-riscv64": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.99.0.tgz", - "integrity": "sha512-4zqDFRvgGDTL5vTHuIhRxUpXFoh0Cy7Gm5Ywk19ASd8Settmd14YdPRZPmMxfgS1GH292PofV1fq1ifiSEJWBw==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.100.0.tgz", + "integrity": "sha512-icU3o0V/uCSytSpf+tX5Lf51BvyQEbLzDUJfUi9etSauYBGHpPKkdtdZH0si4v98phq11Kl8rSV1SggksxF1Hg==", "cpu": [ "riscv64" ], @@ -14302,9 +14193,9 @@ } }, "node_modules/sass-embedded-android-x64": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.99.0.tgz", - "integrity": "sha512-Uk53k/dGYt04RjOL4gFjZ0Z9DH9DKh8IA8WsXUkNqsxerAygoy3zqRBS2zngfE9K2jiOM87q+1R1p87ory9oQQ==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.100.0.tgz", + "integrity": "sha512-mevF9VQk6gEYByy8+jusaHGmd7Usb2ytX/DsEOd0JtOGCtcf1kh575xJ6OUBDIcJ15uLnbau/0iy1eP6WVBvWA==", "cpu": [ "x64" ], @@ -14319,9 +14210,9 @@ } }, "node_modules/sass-embedded-darwin-arm64": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.99.0.tgz", - "integrity": "sha512-u61/7U3IGLqoO6gL+AHeiAtlTPFwJK1+964U8gp45ZN0hzh1yrARf5O1mivXv8NnNgJvbG2wWJbiNZP0lG/lTg==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.100.0.tgz", + "integrity": "sha512-1PVlYi61POo93IT/FfrG1mc1tAHxeSTyUALF2aOFmXGWjVXr3bQzEQiBGCOvQbj/ix+5hNyXFXcEMEyKvtUJJA==", "cpu": [ "arm64" ], @@ -14336,9 +14227,9 @@ } }, "node_modules/sass-embedded-darwin-x64": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.99.0.tgz", - "integrity": "sha512-j/kkk/NcXdIameLezSfXjgCiBkVcA+G60AXrX768/3g0miK1g7M9dj7xOhCb1i7/wQeiEI3rw2LLuO63xRIn4A==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.100.0.tgz", + "integrity": "sha512-x97o3JnGyImZNCIVs9wQHJUE5QCvmVIKaH1cwrz/5dK7OT1FpeNiW+u9TUomP9hG6Ekjd8EL8NBHpxTfIhdjmg==", "cpu": [ "x64" ], @@ -14353,9 +14244,9 @@ } }, "node_modules/sass-embedded-linux-arm": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.99.0.tgz", - "integrity": "sha512-d4IjJZrX2+AwB2YCy1JySwdptJECNP/WfAQLUl8txI3ka8/d3TUI155GtelnoZUkio211PwIeFvvAeZ9RXPQnw==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.100.0.tgz", + "integrity": "sha512-9Ul7O1eKrc5YlhwWjkp8tZPSe3UEwSZ1uwUZOQom1HL0pRlBA6F/IlGZYFTLwnHMIP1fc77MMNaBRfc05mKMpw==", "cpu": [ "arm" ], @@ -14371,9 +14262,9 @@ } }, "node_modules/sass-embedded-linux-arm64": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.99.0.tgz", - "integrity": "sha512-btNcFpItcB56L40n8hDeL7sRSMLDXQ56nB5h2deddJx1n60rpKSElJmkaDGHtpkrY+CTtDRV0FZDjHeTJddYew==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.100.0.tgz", + "integrity": "sha512-Dwjmj8Z6VRy7rAi53JAdEwIyUjpfl7PhpSc2/LpQPQx+aO5Dp7Spaipkax0ufJl1SoDUdchCsM4y/88YaluorQ==", "cpu": [ "arm64" ], @@ -14389,9 +14280,9 @@ } }, "node_modules/sass-embedded-linux-musl-arm": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.99.0.tgz", - "integrity": "sha512-2gvHOupgIw3ytatXT4nFUow71LFbuOZPEwG+HUzcNQDH8ue4Ez8cr03vsv5MDv3lIjOKcXwDvWD980t18MwkoQ==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.100.0.tgz", + "integrity": "sha512-sl0JgbGloPyJg66XXx5UDSDScZ0oU85DpMQU4JU/sCUCFj1Z8zZ69SJWKTCNE4/jwnce7WI2zPCV5AG+RHOZJw==", "cpu": [ "arm" ], @@ -14407,9 +14298,9 @@ } }, "node_modules/sass-embedded-linux-musl-arm64": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.99.0.tgz", - "integrity": "sha512-Hi2bt/IrM5P4FBKz6EcHAlniwfpoz9mnTdvSd58y+avA3SANM76upIkAdSayA8ZGwyL3gZokru1AKDPF9lJDNw==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.100.0.tgz", + "integrity": "sha512-XpACJB2KjSLjf2e9uuvGVdOURsoNrFqgRiihhXyUHK9W0t3LIHb7z5MA/7XGPIT9bWSOO2zyw+rH/FHtDV/Yrg==", "cpu": [ "arm64" ], @@ -14425,9 +14316,9 @@ } }, "node_modules/sass-embedded-linux-musl-riscv64": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.99.0.tgz", - "integrity": "sha512-mKqGvVaJ9rHMqyZsF0kikQe4NO0f4osb67+X6nLhBiVDKvyazQHJ3zJQreNefIE36yL2sjHIclSB//MprzaQDg==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.100.0.tgz", + "integrity": "sha512-ShvI0Kx04mwoCARwZ0UjiT97isQvzO80tAt91zmFyHLN9kelc/IrQi940farSm2xQVPCKdeVyeG0ekBsokSpYQ==", "cpu": [ "riscv64" ], @@ -14443,9 +14334,9 @@ } }, "node_modules/sass-embedded-linux-musl-x64": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.99.0.tgz", - "integrity": "sha512-huhgOMmOc30r7CH7qbRbT9LerSEGSnWuS4CYNOskr9BvNeQp4dIneFufNRGZ7hkOAxUM8DglxIZJN/cyAT95Ew==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.100.0.tgz", + "integrity": "sha512-TDBCRWNuS4RDLQXvRc1gjZlWiWTWaWGp0Bwu/IKwJxov81lsvrCs3TihTyNXtW7V5aoN4Ky3r0QOkNb3mwmBnA==", "cpu": [ "x64" ], @@ -14461,9 +14352,9 @@ } }, "node_modules/sass-embedded-linux-riscv64": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.99.0.tgz", - "integrity": "sha512-mevFPIFAVhrH90THifxLfOntFmHtcEKOcdWnep2gJ0X4DVva4AiVIRlQe/7w9JFx5+gnDRE1oaJJkzuFUuYZsA==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.100.0.tgz", + "integrity": "sha512-j4ENJGOheO+fm3j/yorLxCjBP6/XskrZx7dTLlT+lXYwN/qqCqoA/gsNLI0McS3DFM6GBwPiffzWsdWS8t6sEQ==", "cpu": [ "riscv64" ], @@ -14479,9 +14370,9 @@ } }, "node_modules/sass-embedded-linux-x64": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.99.0.tgz", - "integrity": "sha512-9k7IkULqIZdCIVt4Mboryt6vN8Mjmm3EhI1P3mClU5y5i3wLK5ExC3cbVWk047KsID/fvB1RLslqghXJx5BoxA==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.100.0.tgz", + "integrity": "sha512-0vUSN8j0WGtCJIOPh//EmUvYGHW0QOe5iul8qyhPk50MAcw49MA0r34AhftjDdx94ILPF6vApFs0gwHPQRlpVA==", "cpu": [ "x64" ], @@ -14497,9 +14388,9 @@ } }, "node_modules/sass-embedded-unknown-all": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.99.0.tgz", - "integrity": "sha512-P7MxiUtL/XzGo3PX0CaB8lNNEFLQWKikPA8pbKytx9ZCLZSDkt2NJcdAbblB/sqMs4AV3EK2NadV8rI/diq3xg==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.100.0.tgz", + "integrity": "sha512-c+naBgWId4MIpToXcI0DgqetjdAkwTTAxFAuOaBz7HUXLdyG1oZRrEvSsbe41nEdQOKH0vgofVFCeSQgoXOG9A==", "dev": true, "license": "MIT", "optional": true, @@ -14510,13 +14401,13 @@ "!win32" ], "dependencies": { - "sass": "1.99.0" + "sass": "1.100.0" } }, "node_modules/sass-embedded-win32-arm64": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.99.0.tgz", - "integrity": "sha512-8whpsW7S+uO8QApKfQuc36m3P9EISzbVZOgC79goob4qGy09u8Gz/rYvw8h1prJDSjltpHGhOzBE6LDz7WvzVw==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.100.0.tgz", + "integrity": "sha512-iE+yxj+hUXwwbqpHkXxgAWTzeRfcWxJ7SSTQEPMk48lwq3oCrWLlz5sQuWHbuTK/i0GKQfROdP+hOmPi89yjUg==", "cpu": [ "arm64" ], @@ -14531,9 +14422,9 @@ } }, "node_modules/sass-embedded-win32-x64": { - "version": "1.99.0", - "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.99.0.tgz", - "integrity": "sha512-ipuOv1R2K4MHeuCEAZGpuUbAgma4gb0sdacyrTjJtMOy/OY9UvWfVlwErdB09KIkp4fPDpQJDJfvYN6bC8jeNg==", + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.100.0.tgz", + "integrity": "sha512-qI4F8MI7/KYoy9NdjJfhSspG42WPkADSNDvwEV7qWvCSFC83koJssRsKO2/PfY+niZz6BG65Ic/D+A11h959hw==", "cpu": [ "x64" ], @@ -14563,6 +14454,36 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/sass/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -14577,9 +14498,9 @@ } }, "node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", "dev": true, "license": "ISC", "bin": { @@ -14685,9 +14606,9 @@ } }, "node_modules/shell-quote": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", + "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==", "dev": true, "license": "MIT", "engines": { @@ -15310,9 +15231,9 @@ } }, "node_modules/svelte-preprocess": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-6.0.3.tgz", - "integrity": "sha512-PLG2k05qHdhmRG7zR/dyo5qKvakhm8IJ+hD2eFRQmMLHp7X3eJnjeupUtvuRpbNiF31RjVw45W+abDwHEmP5OA==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-6.0.5.tgz", + "integrity": "sha512-sgwew5yV/2eMeQobIWgAxCNarKwiTUDIc3siAUbq3sp0G6ONtzk0W+wJihMdqjbYb3iGU3ubpGv0usnnuXT3qg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -15330,7 +15251,7 @@ "stylus": ">=0.55", "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0", "svelte": "^4.0.0 || ^5.0.0-next.100 || ^5.0.0", - "typescript": "^5.0.0" + "typescript": "^5.0.0 || ^6.0.0" }, "peerDependenciesMeta": { "@babel/core": { @@ -15846,16 +15767,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.3.tgz", - "integrity": "sha512-KgusgyDgG4LI8Ih/sWaCtZ06tckLAS5CvT5A4D1Q7bYVoAAyzwiZvE4BmwDHkhRVkvhRBepKeASoFzQetha7Fg==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.0.tgz", + "integrity": "sha512-9f65qWLZdAW9m1JaxBDUHcqRUfL8bkxxXL7XxEfI+F09q56PkBvIfCjLF3yInsDM/BBmwkqmCQdCZe/RYlIWEw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.59.3", - "@typescript-eslint/parser": "8.59.3", - "@typescript-eslint/typescript-estree": "8.59.3", - "@typescript-eslint/utils": "8.59.3" + "@typescript-eslint/eslint-plugin": "8.60.0", + "@typescript-eslint/parser": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/utils": "8.60.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -16170,16 +16091,16 @@ "license": "MIT" }, "node_modules/vite": { - "version": "8.0.13", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz", - "integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==", + "version": "8.0.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz", + "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", - "postcss": "^8.5.14", - "rolldown": "1.0.1", + "postcss": "^8.5.15", + "rolldown": "1.0.2", "tinyglobby": "^0.2.16" }, "bin": { @@ -16248,19 +16169,19 @@ } }, "node_modules/vitest": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.6.tgz", - "integrity": "sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.7.tgz", + "integrity": "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.6", - "@vitest/mocker": "4.1.6", - "@vitest/pretty-format": "4.1.6", - "@vitest/runner": "4.1.6", - "@vitest/snapshot": "4.1.6", - "@vitest/spy": "4.1.6", - "@vitest/utils": "4.1.6", + "@vitest/expect": "4.1.7", + "@vitest/mocker": "4.1.7", + "@vitest/pretty-format": "4.1.7", + "@vitest/runner": "4.1.7", + "@vitest/snapshot": "4.1.7", + "@vitest/spy": "4.1.7", + "@vitest/utils": "4.1.7", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -16288,12 +16209,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.6", - "@vitest/browser-preview": "4.1.6", - "@vitest/browser-webdriverio": "4.1.6", - "@vitest/coverage-istanbul": "4.1.6", - "@vitest/coverage-v8": "4.1.6", - "@vitest/ui": "4.1.6", + "@vitest/browser-playwright": "4.1.7", + "@vitest/browser-preview": "4.1.7", + "@vitest/browser-webdriverio": "4.1.7", + "@vitest/coverage-istanbul": "4.1.7", + "@vitest/coverage-v8": "4.1.7", + "@vitest/ui": "4.1.7", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -16473,9 +16394,9 @@ } }, "node_modules/webdriver/node_modules/undici": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz", - "integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.26.0.tgz", + "integrity": "sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==", "dev": true, "license": "MIT", "engines": { @@ -16838,9 +16759,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.20.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", - "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 2f677ab..c3664c2 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,15 @@ "spellcheck": "jiti scripts/spellcheck.ts", "test": "jiti scripts/test.ts", "test:coverage": "jiti scripts/test-coverage.ts", + "test:integration": "jiti scripts/test-integration.ts", + "test:integration:android": "jiti scripts/test-integration-android.ts", + "test:integration:desktop": "jiti scripts/test-integration-desktop.ts", + "test:integration:no-app": "jiti scripts/test-integration-no-app.ts", + "test:watch": "jiti scripts/test-watch.ts", "version": "jiti scripts/version.ts" }, "overrides": { + "@vitest/coverage-v8": "$@vitest/coverage-v8", "eslint": "^10.4.0", "type-fest": "^5.6.0", "typescript": "^6.0.3" @@ -38,11 +44,11 @@ "@commitlint/cli": "^21.0.1", "@commitlint/config-conventional": "^21.0.1", "@commitlint/types": "^21.0.1", - "@obsidian-typings/obsidian-public-latest": "^6.4.0", + "@obsidian-typings/obsidian-public-latest": "^6.11.0", "@total-typescript/ts-reset": "^0.6.1", "@tsconfig/strictest": "^2.0.8", - "@types/node": "^25.8.0", - "@vitest/coverage-v8": "^4.1.6", + "@types/node": "^25.9.1", + "@vitest/coverage-v8": "^4.1.7", "better-typescript-lib": "^2.12.0", "czg": "^1.13.1", "eslint": "^10.4.0", @@ -51,9 +57,10 @@ "jsdom": "^29.1.1", "nano-staged": "^1.0.2", "obsidian": "^1.12.3", - "obsidian-dev-utils": "^66.0.1", - "obsidian-test-mocks": "^2.0.3", - "sass-embedded": "^1.99.0", - "vitest": "^4.1.6" + "obsidian-dev-utils": "^69.1.1", + "obsidian-integration-testing": "^4.1.5", + "obsidian-test-mocks": "^3.0.0", + "sass-embedded": "^1.100.0", + "vitest": "^4.1.7" } } diff --git a/scripts/vitest-config.ts b/scripts/vitest-config.ts index 1226850..6c576b2 100644 --- a/scripts/vitest-config.ts +++ b/scripts/vitest-config.ts @@ -25,6 +25,9 @@ export const config = defineConfig({ inline: ['@obsidian-typings', 'obsidian-dev-utils'] } }, - setupFiles: ['obsidian-test-mocks/setup'] + setupFiles: [ + 'obsidian-test-mocks/vitest-setup', + 'obsidian-test-mocks/obsidian-typings/vitest-setup' + ] } }); diff --git a/src/main.test.ts b/src/main.test.ts index c8f5601..dfdbbc5 100644 --- a/src/main.test.ts +++ b/src/main.test.ts @@ -10,11 +10,12 @@ import { it } from 'vitest'; +// eslint-disable-next-line import-x/no-rename-default -- Renamed to avoid collision with the named `Plugin` import. +import mainExport from './main.ts'; import { Plugin } from './plugin.ts'; describe('main', () => { - it('should export Plugin as default export', async () => { - const mainModule = await import('./main.ts') as { default: typeof Plugin }; - expect(mainModule.default).toBe(Plugin); + it('should export Plugin as default export', () => { + expect(mainExport).toBe(Plugin); }); }); diff --git a/src/plugin-settings-component.test.ts b/src/plugin-settings-component.test.ts deleted file mode 100644 index cd3a951..0000000 --- a/src/plugin-settings-component.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @file - * - * Tests for PluginSettingsComponent. - */ - -import { - describe, - expect, - it, - vi -} from 'vitest'; - -import { PluginSettings } from './plugin-settings.ts'; -import { PluginSettingsComponent } from './plugin-settings-component.ts'; - -describe('PluginSettingsComponent', () => { - it('should create default settings as PluginSettings instance', () => { - const component = new PluginSettingsComponent({ - loadData: vi.fn().mockResolvedValue(null), - saveData: vi.fn().mockResolvedValue(undefined) - }); - - expect(component.defaultSettings).toBeInstanceOf(PluginSettings); - }); - - it('should have all 7 default setting properties', () => { - const component = new PluginSettingsComponent({ - loadData: vi.fn().mockResolvedValue(null), - saveData: vi.fn().mockResolvedValue(undefined) - }); - - const defaults = component.defaultSettings; - expect(defaults).toHaveProperty('pathDepth'); - expect(defaults).toHaveProperty('rootPaths'); - expect(defaults).toHaveProperty('shouldDisplayParentPathOnSeparateLine'); - expect(defaults).toHaveProperty('shouldHighlightFileName'); - expect(defaults).toHaveProperty('shouldIncludeExtension'); - expect(defaults).toHaveProperty('shouldReversePathParts'); - expect(defaults).toHaveProperty('shouldShowEllipsisForSkippedPathParts'); - }); -}); diff --git a/src/plugin-settings-component.ts b/src/plugin-settings-component.ts index 88e933b..b79badd 100644 --- a/src/plugin-settings-component.ts +++ b/src/plugin-settings-component.ts @@ -4,20 +4,23 @@ * Settings component that manages plugin settings persistence. */ +import type { DataHandler } from 'obsidian-dev-utils/obsidian/data-handler'; +import type { PluginEventSource } from 'obsidian-dev-utils/obsidian/plugin/plugin-event-source'; + import { PluginSettingsComponentBase } from 'obsidian-dev-utils/obsidian/components/plugin-settings-component'; import { PluginSettings } from './plugin-settings.ts'; -/** - * Manages persistence and lifecycle for {@link PluginSettings}. - */ +interface PluginSettingsComponentConstructorParams { + readonly dataHandler: DataHandler; + readonly pluginEventSource: PluginEventSource; +} + export class PluginSettingsComponent extends PluginSettingsComponentBase { - /** - * Creates the default settings instance. - * - * @returns A new {@link PluginSettings} with default values. - */ - protected override createDefaultSettings(): PluginSettings { - return new PluginSettings(); + public constructor(params: PluginSettingsComponentConstructorParams) { + super({ + ...params, + pluginSettingsClass: PluginSettings + }); } } diff --git a/src/plugin-settings-tab.test.ts b/src/plugin-settings-tab.test.ts index 8329fb1..a0a360d 100644 --- a/src/plugin-settings-tab.test.ts +++ b/src/plugin-settings-tab.test.ts @@ -4,67 +4,159 @@ * Tests for the PluginSettingsTab class. */ -import type { Plugin as ObsidianPlugin } from 'obsidian'; +import type { PluginManifest } from 'obsidian'; -import { App } from 'obsidian-test-mocks/obsidian'; import { + App, + ToggleComponent +} from 'obsidian-test-mocks/obsidian'; +import { + beforeAll, describe, expect, - it, - vi + it } from 'vitest'; -import { PluginSettingsComponent } from './plugin-settings-component.ts'; import { PluginSettingsTab } from './plugin-settings-tab.ts'; +import { Plugin } from './plugin.ts'; + +interface DisplayedTabResult { + names: string[]; + tab: PluginSettingsTab; +} /** - * Creates a mock plugin object for testing. + * Creates a PluginSettingsTab attached to a freshly created Plugin and calls display(). * - * @returns A mock plugin. + * @returns An object with the tab and the extracted setting names. */ -function createMockPlugin(): ObsidianPlugin { - const app = App.createConfigured__(); - return { - app: app.asOriginalType__(), - manifest: { - author: 'test', - description: 'test', - id: 'backlink-full-path', - minAppVersion: '0.0.0', - name: 'Backlink Full Path', - version: '1.0.0' - } - } as unknown as ObsidianPlugin; +function createDisplayedTab(): DisplayedTabResult { + const plugin = createPlugin(); + const pluginSettingsComponent = Reflect.get(plugin, 'pluginSettingsComponent') as InstanceType< + typeof import('./plugin-settings-component.ts').PluginSettingsComponent + >; + + const tab = new PluginSettingsTab({ + plugin, + pluginSettingsComponent + }); + + tab.display(); + const names = getSettingNames(tab.containerEl); + return { names, tab }; } +/** + * Creates a Plugin instance with mocked dependencies. + * + * @returns The created Plugin. + */ +function createPlugin(): Plugin { + const app = App.createConfigured__(); + const manifest: PluginManifest = { + author: 'test', + description: 'test', + id: 'backlink-full-path', + minAppVersion: '0.0.0', + name: 'Backlink Full Path', + version: '1.0.0' + }; + return new Plugin(app.asOriginalType__(), manifest); +} + +/** + * Extracts setting names from the tab's containerEl after calling display(). + * + * Each Setting mock creates a settingEl > [controlEl, infoEl > [nameEl, descEl]]. + * The nameEl is the second-level div that contains the setting name text. + * + * @param containerEl - The container element. + * @returns The array of setting name strings. + */ +function getSettingNames(containerEl: HTMLElement): string[] { + const names: string[] = []; + for (const settingEl of containerEl.children) { + // SettingEl contains [controlEl, infoEl]. + // InfoEl contains [nameEl, descEl]. + const infoEl = settingEl.children[1]; + if (infoEl) { + const nameEl = infoEl.children[0]; + if (nameEl?.textContent) { + names.push(nameEl.textContent); + } + } + } + return names; +} + +/** + * Patches missing mock properties on obsidian-test-mocks components + * that obsidian-dev-utils's `bind()` method probes via strict proxy. + */ +function patchMissingMockProperties(): void { + // Obsidian-dev-utils checks for setPlaceholderValue to detect text-based components. + if (!('setPlaceholderValue' in ToggleComponent.prototype)) { + Object.defineProperty(ToggleComponent.prototype, 'setPlaceholderValue', { value: undefined }); + } +} + +beforeAll(() => { + patchMissingMockProperties(); +}); + describe('PluginSettingsTab', () => { it('should be constructable', () => { - const plugin = createMockPlugin(); - const settingsComponent = new PluginSettingsComponent({ - loadData: vi.fn().mockResolvedValue(null), - saveData: vi.fn().mockResolvedValue(undefined) - }); - + const plugin = createPlugin(); + const pluginSettingsComponent = Reflect.get(plugin, 'pluginSettingsComponent') as InstanceType< + typeof import('./plugin-settings-component.ts').PluginSettingsComponent + >; const tab = new PluginSettingsTab({ plugin, - settingsComponent + pluginSettingsComponent }); - expect(tab).toBeInstanceOf(PluginSettingsTab); }); - it('should have a containerEl after construction', () => { - const plugin = createMockPlugin(); - const settingsComponent = new PluginSettingsComponent({ - loadData: vi.fn().mockResolvedValue(null), - saveData: vi.fn().mockResolvedValue(undefined) + describe('display', () => { + it('should create all 7 settings', () => { + const { names } = createDisplayedTab(); + const EXPECTED_SETTING_COUNT = 7; + expect(names.length).toBe(EXPECTED_SETTING_COUNT); }); - const tab = new PluginSettingsTab({ - plugin, - settingsComponent + it('should create Include extension setting', () => { + const { names } = createDisplayedTab(); + expect(names).toContain('Include extension'); }); - expect(tab.containerEl).toBeDefined(); + it('should create Path depth setting', () => { + const { names } = createDisplayedTab(); + expect(names).toContain('Path depth'); + }); + + it('should create Show ellipsis for skipped path parts setting', () => { + const { names } = createDisplayedTab(); + expect(names).toContain('Show ellipsis for skipped path parts'); + }); + + it('should create Highlight file name setting', () => { + const { names } = createDisplayedTab(); + expect(names).toContain('Highlight file name'); + }); + + it('should create Reverse path parts setting', () => { + const { names } = createDisplayedTab(); + expect(names).toContain('Reverse path parts'); + }); + + it('should create Display parent path on separate line setting', () => { + const { names } = createDisplayedTab(); + expect(names).toContain('Display parent path on separate line'); + }); + + it('should create Root paths setting', () => { + const { names } = createDisplayedTab(); + expect(names).toContain('Root paths'); + }); }); }); diff --git a/src/plugin-settings-tab.ts b/src/plugin-settings-tab.ts index 0e6c2eb..761073e 100644 --- a/src/plugin-settings-tab.ts +++ b/src/plugin-settings-tab.ts @@ -1,11 +1,3 @@ -/** - * @file - * - * Settings tab UI for the Backlink Full Path plugin. - */ - -import type { PluginSettingsTabBaseParams } from 'obsidian-dev-utils/obsidian/plugin/plugin-settings-tab'; - import { Setting } from 'obsidian'; import { appendCodeBlock } from 'obsidian-dev-utils/html-element'; import { PluginSettingsTabBase } from 'obsidian-dev-utils/obsidian/plugin/plugin-settings-tab'; @@ -13,22 +5,7 @@ import { SettingEx } from 'obsidian-dev-utils/obsidian/setting-ex'; import type { PluginSettings } from './plugin-settings.ts'; -/** - * Settings tab for the Backlink Full Path plugin. - */ export class PluginSettingsTab extends PluginSettingsTabBase { - /** - * Creates a new settings tab. - * - * @param params - The settings tab params. - */ - public constructor(params: PluginSettingsTabBaseParams) { - super(params); - } - - /** - * Renders the settings tab UI. - */ public override display(): void { super.display(); diff --git a/src/plugin.test.ts b/src/plugin.test.ts index 217d782..3877459 100644 --- a/src/plugin.test.ts +++ b/src/plugin.test.ts @@ -4,17 +4,91 @@ * Tests for the Plugin class. */ -import type { PluginManifest } from 'obsidian'; +import type { + BacklinkView, + ResultDom, + ResultDomItem, + ResultDomResult +} from '@obsidian-typings/obsidian-public-latest'; +import type { + PluginManifest, + TFile +} from 'obsidian'; +import type { PartialDeep } from 'type-fest'; -import { App } from 'obsidian-test-mocks/obsidian'; import { + bypassStrictProxy, + strictProxy +} from 'obsidian-dev-utils/strict-proxy'; +import { + App, + MarkdownView, + WorkspaceLeaf +} from 'obsidian-test-mocks/obsidian'; +import { + beforeEach, describe, expect, - it + it, + vi } from 'vitest'; +import type { PluginSettings } from './plugin-settings.ts'; + import { Plugin } from './plugin.ts'; +interface MockCorePlugin { + enabled: boolean; + instance: object; +} + +interface SettingsComponentProxy { + on(...args: never[]): void; + settings: PluginSettings; +} + +interface TestPluginResult { + app: ReturnType; + plugin: Plugin; +} + +/** + * Creates a mock TFile for testing. + * + * @param path - The file path. + * @returns A mock TFile. + */ +function createMockFile(path: string): TFile { + const parts = path.split('/'); + const name = parts.at(-1) ?? ''; + const dotIndex = name.lastIndexOf('.'); + const basename = dotIndex >= 0 ? name.slice(0, dotIndex) : name; + const extension = dotIndex >= 0 ? name.slice(dotIndex + 1) : ''; + return mockProxy({ + basename, + extension, + name, + path + }); +} + +/** + * Creates a mock ResultDomItem with an element that has .tree-item-inner. + * + * @param hasTreeItemInner - Whether the result element should have a .tree-item-inner child. + * @returns The mock ResultDomItem. + */ +function createMockResultDomItem(hasTreeItemInner: boolean): ResultDomItem { + const el = activeDocument.createElement('div'); + if (hasTreeItemInner) { + const inner = activeDocument.createElement('div'); + inner.classList.add('tree-item-inner'); + inner.textContent = 'Original'; + el.appendChild(inner); + } + return mockProxy({ el }); +} + /** * Creates a minimal PluginManifest for testing. * @@ -31,18 +105,659 @@ function createTestManifest(): PluginManifest { }; } +/** + * Creates a test Plugin instance with a configured App. + * + * @returns The plugin and app. + */ +function createTestPlugin(): TestPluginResult { + const app = App.createConfigured__(); + const manifest = createTestManifest(); + const plugin = new Plugin(app.asOriginalType__(), manifest); + return { app, plugin }; +} + +/** + * Gets the plugin's private pluginSettingsComponent via Reflect. + * + * @param plugin - The plugin instance. + * @returns The pluginSettingsComponent. + */ +function getSettingsComponent(plugin: Plugin): SettingsComponentProxy { + return Reflect.get(plugin, 'pluginSettingsComponent') as SettingsComponentProxy; +} + +/** + * Sets `_loaded` to true on the plugin (bypassing strict proxy). + * + * @param plugin - The plugin to mark as loaded. + */ +function markPluginLoaded(plugin: Plugin): void { + Reflect.set(bypassStrictProxy(plugin), '_loaded', true); +} + +/** + * Sets up internalPlugins mock on the plugin's app. + * + * @param plugin - The plugin. + * @param returnValue - The value to return from getPluginById. + * @returns The mocked getPluginById function. + */ +function mockInternalPlugins(plugin: Plugin, returnValue: unknown): ReturnType { + const getPluginById = vi.fn().mockReturnValue(returnValue); + Reflect.set(bypassStrictProxy(plugin.app), 'internalPlugins', strictProxy({ getPluginById })); + return getPluginById; +} + +/** + * Creates a strict proxy, working around `PartialDeep` incompatibility + * with complex DOM types by casting the partial before passing to strictProxy. + * + * @param partial - A partial object with the mocked members. + * @returns A strict proxy typed as T. + */ +// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- T is used in the body for strictProxy. +function mockProxy(partial: object): T { + return strictProxy(partial as PartialDeep); +} + +/** + * Calls a private/protected method on an object via Reflect and binds it. + * + * @param obj - The object. + * @param name - The method name. + * @returns The bound method. + */ +function privateMethod(obj: object, name: string): (...args: never[]) => unknown { + const fn = Reflect.get(obj, name) as (...args: never[]) => unknown; + return fn.bind(obj); +} + describe('Plugin', () => { it('should be constructable', () => { - const app = App.createConfigured__(); - const manifest = createTestManifest(); - const plugin = new Plugin(app.asOriginalType__(), manifest); + const { plugin } = createTestPlugin(); expect(plugin).toBeInstanceOf(Plugin); }); it('should have the correct manifest id', () => { - const app = App.createConfigured__(); - const manifest = createTestManifest(); - const plugin = new Plugin(app.asOriginalType__(), manifest); + const { plugin } = createTestPlugin(); expect(plugin.manifest.id).toBe('backlink-full-path'); }); + + describe('onload', () => { + it('should register saveSettings event listener', () => { + const { plugin } = createTestPlugin(); + const settingsComponent = getSettingsComponent(plugin); + const onSpy = vi.spyOn(settingsComponent, 'on'); + + plugin.onload(); + + expect(onSpy).toHaveBeenCalledWith('saveSettings', expect.any(Function)); + }); + + it('should call refreshBacklinkPanels when saveSettings is triggered', async () => { + const { plugin } = createTestPlugin(); + + const refreshBacklinkPanels = vi.fn().mockResolvedValue(undefined); + Reflect.set(plugin, 'refreshBacklinkPanels', refreshBacklinkPanels); + + const settingsComponent = getSettingsComponent(plugin); + let savedCallback: (() => Promise) | undefined; + vi.spyOn(settingsComponent, 'on').mockImplementation( + ((_event: never, callback: never) => { + savedCallback = callback; + }) as (...args: never[]) => void + ); + + plugin.onload(); + + expect(savedCallback).toBeDefined(); + if (savedCallback) { + await savedCallback(); + } + + expect(refreshBacklinkPanels).toHaveBeenCalled(); + }); + }); + + describe('onLayoutReady', () => { + it('should return early when backlinks core plugin is not found', async () => { + const { plugin } = createTestPlugin(); + + const getPluginById = mockInternalPlugins(plugin, undefined); + + const onLayoutReady = privateMethod(plugin, 'onLayoutReady'); + await onLayoutReady(); + + expect(getPluginById).toHaveBeenCalledWith('backlink'); + }); + + it('should patch but not call patchBacklinksPane when backlinks plugin is disabled', async () => { + const { plugin } = createTestPlugin(); + markPluginLoaded(plugin); + + const instanceProto = {}; + const backlinksCorePlugin = mockProxy({ + enabled: false, + instance: Object.create(instanceProto) + }); + + mockInternalPlugins(plugin, backlinksCorePlugin); + + const patchBacklinksPane = vi.fn().mockResolvedValue(undefined); + Reflect.set(plugin, 'patchBacklinksPane', patchBacklinksPane); + + const onLayoutReady = privateMethod(plugin, 'onLayoutReady'); + await onLayoutReady(); + + expect(patchBacklinksPane).not.toHaveBeenCalled(); + }); + + it('should call patchBacklinksPane and refreshBacklinkPanels when backlinks plugin is enabled', async () => { + const { plugin } = createTestPlugin(); + markPluginLoaded(plugin); + + const instanceProto = {}; + const backlinksCorePlugin = mockProxy({ + enabled: true, + instance: Object.create(instanceProto) + }); + + mockInternalPlugins(plugin, backlinksCorePlugin); + + const patchBacklinksPane = vi.fn().mockResolvedValue(undefined); + const refreshBacklinkPanels = vi.fn().mockResolvedValue(undefined); + Reflect.set(plugin, 'patchBacklinksPane', patchBacklinksPane); + Reflect.set(plugin, 'refreshBacklinkPanels', refreshBacklinkPanels); + + const onLayoutReady = privateMethod(plugin, 'onLayoutReady'); + await onLayoutReady(); + + expect(patchBacklinksPane).toHaveBeenCalled(); + expect(refreshBacklinkPanels).toHaveBeenCalled(); + }); + + it('should invoke onUserEnable patch that calls onBacklinksCorePluginEnable', async () => { + const { plugin } = createTestPlugin(); + markPluginLoaded(plugin); + + const instanceProto = { onUserEnable: vi.fn() }; + const backlinksCorePlugin = mockProxy({ + enabled: false, + instance: Object.create(instanceProto) as object + }); + + mockInternalPlugins(plugin, backlinksCorePlugin); + + const onBacklinksCorePluginEnable = vi.fn(); + Reflect.set(plugin, 'onBacklinksCorePluginEnable', onBacklinksCorePluginEnable); + + const onLayoutReady = privateMethod(plugin, 'onLayoutReady'); + await onLayoutReady(); + + const patchedOnUserEnable = instanceProto.onUserEnable as () => void; + patchedOnUserEnable.call(backlinksCorePlugin.instance); + + expect(onBacklinksCorePluginEnable).toHaveBeenCalled(); + }); + + it('should register unload callback that calls refreshBacklinkPanels', async () => { + const { plugin } = createTestPlugin(); + markPluginLoaded(plugin); + + const instanceProto = {}; + const backlinksCorePlugin = mockProxy({ + enabled: false, + instance: Object.create(instanceProto) + }); + + mockInternalPlugins(plugin, backlinksCorePlugin); + + const refreshBacklinkPanels = vi.fn().mockResolvedValue(undefined); + Reflect.set(plugin, 'refreshBacklinkPanels', refreshBacklinkPanels); + + const registerCallbacks: (() => void)[] = []; + vi.spyOn(plugin, 'register').mockImplementation((cb: () => void) => { + registerCallbacks.push(cb); + }); + + const onLayoutReady = privateMethod(plugin, 'onLayoutReady'); + await onLayoutReady(); + + expect(registerCallbacks.length).toBeGreaterThan(0); + const callback = registerCallbacks[0]; + if (callback) { + callback(); + } + + expect(refreshBacklinkPanels).toHaveBeenCalled(); + }); + }); + + describe('addResult', () => { + let plugin: Plugin; + let addResult: (...args: never[]) => unknown; + + beforeEach(() => { + const result = createTestPlugin(); + plugin = result.plugin; + addResult = privateMethod(plugin, 'addResult'); + }); + + it('should replace tree-item-inner content with generated backlink title', () => { + const file = createMockFile('folder/subfolder/note.md'); + const resultDomItem = createMockResultDomItem(true); + const next = vi.fn().mockReturnValue(resultDomItem); + const resultDom = mockProxy({}); + const result = mockProxy({}); + + const returnedItem = (addResult as (...args: unknown[]) => unknown)(next, resultDom, file, result, 'content', true) as ResultDomItem; + + expect(next).toHaveBeenCalledWith(file, result, 'content', true); + expect(returnedItem).toBe(resultDomItem); + + const inner = resultDomItem.el.querySelector('.tree-item-inner'); + expect(inner?.textContent).not.toBe('Original'); + }); + + it('should not modify result when tree-item-inner is absent', () => { + const file = createMockFile('note.md'); + const resultDomItem = createMockResultDomItem(false); + const next = vi.fn().mockReturnValue(resultDomItem); + const resultDom = mockProxy({}); + const result = mockProxy({}); + + const returnedItem = (addResult as (...args: unknown[]) => unknown)(next, resultDom, file, result, 'content') as ResultDomItem; + expect(returnedItem).toBe(resultDomItem); + }); + }); + + describe('generateBacklinkTitle', () => { + let plugin: Plugin; + let settings: PluginSettings; + + beforeEach(() => { + const result = createTestPlugin(); + plugin = result.plugin; + settings = getSettingsComponent(plugin).settings; + }); + + /** + * Calls the private generateBacklinkTitle method. + * + * @param file - The TFile to generate a title for. + * @returns The generated HTMLDivElement. + */ + function callGenerateBacklinkTitle(file: TFile): HTMLDivElement { + const fn = privateMethod(plugin, 'generateBacklinkTitle'); + return (fn as (...args: unknown[]) => unknown)(file) as HTMLDivElement; + } + + it('should include extension when shouldIncludeExtension is true', () => { + settings.shouldIncludeExtension = true; + const file = createMockFile('folder/note.md'); + const container = callGenerateBacklinkTitle(file); + const fileNameSpan = container.shadowRoot?.querySelector('[part="file-name"]'); + expect(fileNameSpan?.textContent).toBe('note.md'); + }); + + it('should exclude extension when shouldIncludeExtension is false', () => { + settings.shouldIncludeExtension = false; + const file = createMockFile('folder/note.md'); + const container = callGenerateBacklinkTitle(file); + const fileNameSpan = container.shadowRoot?.querySelector('[part="file-name"]'); + expect(fileNameSpan?.textContent).toBe('note'); + }); + + it('should show file at root with no parent path', () => { + const file = createMockFile('note.md'); + const container = callGenerateBacklinkTitle(file); + const parentPathSpan = container.shadowRoot?.querySelector('[part="parent-path"]'); + expect(parentPathSpan).toBeNull(); + }); + + it('should show parent path with trailing separator', () => { + settings.shouldReversePathParts = false; + settings.shouldDisplayParentPathOnSeparateLine = false; + const file = createMockFile('folder/note.md'); + const container = callGenerateBacklinkTitle(file); + const parentPathSpan = container.shadowRoot?.querySelector('[part="parent-path"]'); + expect(parentPathSpan?.textContent).toBe('folder/'); + }); + + it('should strip root path prefix', () => { + settings.rootPaths = ['folder']; + const file = createMockFile('folder/subfolder/note.md'); + const container = callGenerateBacklinkTitle(file); + const parentPathSpan = container.shadowRoot?.querySelector('[part="parent-path"]'); + expect(parentPathSpan?.textContent).toBe('subfolder/'); + }); + + it('should strip nested root path prefix', () => { + settings.rootPaths = ['folder/subfolder']; + const file = createMockFile('folder/subfolder/deep/note.md'); + const container = callGenerateBacklinkTitle(file); + const parentPathSpan = container.shadowRoot?.querySelector('[part="parent-path"]'); + expect(parentPathSpan?.textContent).toBe('deep/'); + }); + + it('should limit depth with pathDepth setting', () => { + settings.pathDepth = 2; + settings.shouldShowEllipsisForSkippedPathParts = false; + const file = createMockFile('a/b/c/note.md'); + const container = callGenerateBacklinkTitle(file); + const parentPathSpan = container.shadowRoot?.querySelector('[part="parent-path"]'); + expect(parentPathSpan?.textContent).toBe('c/'); + }); + + it('should add ellipsis for skipped parts when enabled', () => { + settings.pathDepth = 2; + settings.shouldShowEllipsisForSkippedPathParts = true; + const file = createMockFile('a/b/c/note.md'); + const container = callGenerateBacklinkTitle(file); + const parentPathSpan = container.shadowRoot?.querySelector('[part="parent-path"]'); + expect(parentPathSpan?.textContent).toBe('.../c/'); + }); + + it('should not add ellipsis when pathDepth does not truncate', () => { + settings.pathDepth = 5; + settings.shouldShowEllipsisForSkippedPathParts = true; + const file = createMockFile('a/b/note.md'); + const container = callGenerateBacklinkTitle(file); + const parentPathSpan = container.shadowRoot?.querySelector('[part="parent-path"]'); + expect(parentPathSpan?.textContent).toBe('a/b/'); + }); + + it('should reverse path parts when shouldReversePathParts is true', () => { + settings.shouldReversePathParts = true; + settings.shouldDisplayParentPathOnSeparateLine = false; + const file = createMockFile('a/b/note.md'); + const container = callGenerateBacklinkTitle(file); + const parentPathSpan = container.shadowRoot?.querySelector('[part="parent-path"]'); + expect(parentPathSpan?.textContent).toBe(' \u2190 b \u2190 a'); + }); + + it('should display parent path on separate line without separator', () => { + settings.shouldDisplayParentPathOnSeparateLine = true; + settings.shouldReversePathParts = false; + const file = createMockFile('folder/note.md'); + const container = callGenerateBacklinkTitle(file); + const parentPathSpan = container.shadowRoot?.querySelector('[part="parent-path"]'); + expect(parentPathSpan?.textContent).toBe('folder'); + }); + + it('should set dataset attributes for highlighting', () => { + settings.shouldHighlightFileName = true; + settings.shouldDisplayParentPathOnSeparateLine = false; + const file = createMockFile('note.md'); + const container = callGenerateBacklinkTitle(file); + expect(container.dataset['shouldHighlightFileName']).toBe('true'); + expect(container.dataset['shouldDisplayParentPathOnSeparateLine']).toBe('false'); + }); + + it('should include full-path span with file path', () => { + const file = createMockFile('folder/note.md'); + const container = callGenerateBacklinkTitle(file); + const fullPathSpan = container.querySelector('.full-path'); + expect(fullPathSpan?.textContent).toBe('folder/note.md'); + }); + + it('should prepend parent path when not reversed and not on separate line', () => { + settings.shouldReversePathParts = false; + settings.shouldDisplayParentPathOnSeparateLine = false; + const file = createMockFile('folder/note.md'); + const container = callGenerateBacklinkTitle(file); + const firstChild = container.shadowRoot?.firstElementChild; + expect(firstChild?.getAttribute('part')).toBe('parent-path'); + }); + + it('should append parent path when reversed and not on separate line', () => { + settings.shouldReversePathParts = true; + settings.shouldDisplayParentPathOnSeparateLine = false; + const file = createMockFile('folder/note.md'); + const container = callGenerateBacklinkTitle(file); + const lastChild = container.shadowRoot?.lastElementChild; + expect(lastChild?.getAttribute('part')).toBe('parent-path'); + }); + + it('should not prepend when displaying on separate line', () => { + settings.shouldReversePathParts = false; + settings.shouldDisplayParentPathOnSeparateLine = true; + const file = createMockFile('folder/note.md'); + const container = callGenerateBacklinkTitle(file); + const lastChild = container.shadowRoot?.lastElementChild; + expect(lastChild?.getAttribute('part')).toBe('parent-path'); + }); + }); + + describe('getBacklinkView', () => { + it('should return null when no backlink leaf exists', async () => { + const { plugin } = createTestPlugin(); + + const getBacklinkView = privateMethod(plugin, 'getBacklinkView'); + const view = await getBacklinkView(); + expect(view).toBeNull(); + }); + + it('should return the view when a backlink leaf exists', async () => { + const { plugin } = createTestPlugin(); + + const mockView = mockProxy({}); + const mockLeaf = mockProxy({ + loadIfDeferred: vi.fn().mockResolvedValue(undefined), + view: mockView + }); + + Reflect.set(plugin.app.workspace, 'getLeavesOfType', vi.fn().mockReturnValue([mockLeaf])); + + const getBacklinkView = privateMethod(plugin, 'getBacklinkView'); + const view = await getBacklinkView(); + + expect(mockLeaf.loadIfDeferred).toHaveBeenCalled(); + expect(view).toBe(mockView); + }); + }); + + describe('refreshBacklinkPanels', () => { + it('should call reloadBacklinksView', async () => { + const { plugin } = createTestPlugin(); + + const reloadBacklinksView = vi.fn().mockResolvedValue(undefined); + Reflect.set(plugin, 'reloadBacklinksView', reloadBacklinksView); + + const refreshBacklinkPanels = privateMethod(plugin, 'refreshBacklinkPanels'); + await refreshBacklinkPanels(); + + expect(reloadBacklinksView).toHaveBeenCalled(); + }); + }); + + describe('reloadBacklinksView', () => { + it('should do nothing when no backlink view exists', async () => { + const { plugin } = createTestPlugin(); + + const getBacklinkView = vi.fn().mockResolvedValue(null); + Reflect.set(plugin, 'getBacklinkView', getBacklinkView); + + const reloadBacklinksView = privateMethod(plugin, 'reloadBacklinksView'); + await reloadBacklinksView(); + + expect(getBacklinkView).toHaveBeenCalled(); + }); + + it('should not recompute when backlink view has no file', async () => { + const { plugin } = createTestPlugin(); + + const recomputeBacklink = vi.fn(); + const backlinkView = mockProxy({ + backlink: mockProxy({ recomputeBacklink }), + file: null + }); + + Reflect.set(plugin, 'getBacklinkView', vi.fn().mockResolvedValue(backlinkView)); + + const reloadBacklinksView = privateMethod(plugin, 'reloadBacklinksView'); + await reloadBacklinksView(); + + expect(recomputeBacklink).not.toHaveBeenCalled(); + }); + + it('should recompute when backlink view has a file', async () => { + const { plugin } = createTestPlugin(); + + const mockFile = createMockFile('note.md'); + const recomputeBacklink = vi.fn(); + const backlinkView = mockProxy({ + backlink: mockProxy({ recomputeBacklink }), + file: mockFile + }); + + Reflect.set(plugin, 'getBacklinkView', vi.fn().mockResolvedValue(backlinkView)); + + const reloadBacklinksView = privateMethod(plugin, 'reloadBacklinksView'); + await reloadBacklinksView(); + + expect(recomputeBacklink).toHaveBeenCalledWith(mockFile); + }); + }); + + describe('onBacklinksCorePluginEnable', () => { + it('should invoke patchBacklinksPane asynchronously', () => { + const { plugin } = createTestPlugin(); + + const patchBacklinksPane = vi.fn().mockResolvedValue(undefined); + Reflect.set(plugin, 'patchBacklinksPane', patchBacklinksPane); + + const onBacklinksCorePluginEnable = privateMethod(plugin, 'onBacklinksCorePluginEnable'); + onBacklinksCorePluginEnable(); + + expect(patchBacklinksPane).toHaveBeenCalled(); + }); + }); + + describe('patchBacklinksPane', () => { + it('should return early when no backlink view exists', async () => { + const { plugin } = createTestPlugin(); + + const getBacklinkView = vi.fn().mockResolvedValue(null); + Reflect.set(plugin, 'getBacklinkView', getBacklinkView); + + const patchBacklinksPane = privateMethod(plugin, 'patchBacklinksPane'); + await patchBacklinksPane(); + + expect(getBacklinkView).toHaveBeenCalled(); + }); + + it('should patch addResult on backlinkDom when view exists', async () => { + const { plugin } = createTestPlugin(); + markPluginLoaded(plugin); + + const originalAddResult = vi.fn().mockImplementation(() => createMockResultDomItem(true)); + const backlinkDomProto = { addResult: originalAddResult }; + const backlinkView = mockProxy({ + backlink: mockProxy({ + backlinkDom: Object.create(backlinkDomProto) as object + }) + }); + + Reflect.set(plugin, 'getBacklinkView', vi.fn().mockResolvedValue(backlinkView)); + + const patchBacklinksPane = privateMethod(plugin, 'patchBacklinksPane'); + await patchBacklinksPane(); + + // The patchedAddResult is now on the prototype; call it to exercise the callback + const file = createMockFile('folder/note.md'); + const result = mockProxy({}); + const mockResultDom = Object.create(backlinkDomProto) as object; + const patchedAddResult = backlinkDomProto.addResult as ( + file: TFile, + result: ResultDomResult, + content: string, + shouldShowTitle?: boolean + ) => ResultDomItem; + const item = patchedAddResult.call(mockResultDom, file, result, 'content', true); + + expect(item).toBeDefined(); + }); + }); + + describe('refreshBacklinkPanels with markdown leaves', () => { + it('should skip leaves that are not MarkdownView instances', async () => { + const { plugin } = createTestPlugin(); + + Reflect.set(plugin, 'reloadBacklinksView', vi.fn().mockResolvedValue(undefined)); + + const nonMarkdownLeaf = mockProxy({ + view: mockProxy({}) + }); + + Reflect.set( + plugin.app.workspace, + 'getLeavesOfType', + vi.fn().mockImplementation((type: string) => { + if (type === 'markdown') { + return [nonMarkdownLeaf]; + } + return []; + }) + ); + + const refreshBacklinkPanels = privateMethod(plugin, 'refreshBacklinkPanels'); + await refreshBacklinkPanels(); + }); + + it('should skip MarkdownView leaves without backlinks', async () => { + const { app, plugin } = createTestPlugin(); + + Reflect.set(plugin, 'reloadBacklinksView', vi.fn().mockResolvedValue(undefined)); + + const leaf = WorkspaceLeaf.create2__(app); + const mdView = MarkdownView.create2__(leaf); + Reflect.set(bypassStrictProxy(mdView), 'backlinks', undefined); + Reflect.set(bypassStrictProxy(leaf), 'view', mdView); + + Reflect.set( + plugin.app.workspace, + 'getLeavesOfType', + vi.fn().mockImplementation((type: string) => { + if (type === 'markdown') { + return [leaf.asOriginalType__()]; + } + return []; + }) + ); + + const refreshBacklinkPanels = privateMethod(plugin, 'refreshBacklinkPanels'); + await refreshBacklinkPanels(); + }); + + it('should recompute backlinks for MarkdownView leaves with backlinks', async () => { + const { app, plugin } = createTestPlugin(); + + Reflect.set(plugin, 'reloadBacklinksView', vi.fn().mockResolvedValue(undefined)); + + const leaf = WorkspaceLeaf.create2__(app); + const mdView = MarkdownView.create2__(leaf); + const mockFile = createMockFile('test2.md'); + const recomputeBacklink = vi.fn(); + Reflect.set(bypassStrictProxy(mdView), 'backlinks', { file: mockFile, recomputeBacklink }); + Reflect.set(bypassStrictProxy(leaf), 'view', mdView); + + Reflect.set( + plugin.app.workspace, + 'getLeavesOfType', + vi.fn().mockImplementation((type: string) => { + if (type === 'markdown') { + return [leaf.asOriginalType__()]; + } + return []; + }) + ); + + const refreshBacklinkPanels = privateMethod(plugin, 'refreshBacklinkPanels'); + await refreshBacklinkPanels(); + + expect(recomputeBacklink).toHaveBeenCalledWith(mockFile); + }); + }); }); diff --git a/src/plugin.ts b/src/plugin.ts index 58f2828..87e91d2 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -4,11 +4,6 @@ * Main plugin class for the Backlink Full Path plugin. */ -import type { - App, - PluginManifest, - TFile -} from 'obsidian'; import type { BacklinkPlugin, BacklinkView, @@ -16,21 +11,27 @@ import type { ResultDomItem, ResultDomResult } from '@obsidian-typings/obsidian-public-latest'; +import type { + App, + PluginManifest, + TFile +} from 'obsidian'; +import { + InternalPluginName, + ViewType +} from '@obsidian-typings/obsidian-public-latest/implementations'; import { MarkdownView, setTooltip } from 'obsidian'; import { invokeAsyncSafely } from 'obsidian-dev-utils/async'; import { getPrototypeOf } from 'obsidian-dev-utils/object-utils'; -import { registerPatch } from 'obsidian-dev-utils/obsidian/monkey-around'; +import { MonkeyAroundComponent } from 'obsidian-dev-utils/obsidian/components/monkey-around-component'; import { PluginSettingsTabComponent } from 'obsidian-dev-utils/obsidian/components/plugin-settings-tab-component'; -import { PluginBase } from 'obsidian-dev-utils/obsidian/plugin/plugin'; import { PluginDataHandler } from 'obsidian-dev-utils/obsidian/data-handler'; -import { - InternalPluginName, - ViewType -} from '@obsidian-typings/obsidian-public-latest/implementations'; +import { PluginBase } from 'obsidian-dev-utils/obsidian/plugin/plugin'; +import { PluginEventSourceImpl } from 'obsidian-dev-utils/obsidian/plugin/plugin-event-source'; import { PluginSettingsComponent } from './plugin-settings-component.ts'; import { PluginSettingsTab } from './plugin-settings-tab.ts'; @@ -55,39 +56,45 @@ export class Plugin extends PluginBase { public constructor(app: App, manifest: PluginManifest) { super(app, manifest); - this.pluginSettingsComponent = this.addChild(new PluginSettingsComponent(new PluginDataHandler(this))); - - this.addChild(new PluginSettingsTabComponent({ - plugin: this, - pluginSettingsTab: new PluginSettingsTab({ - plugin: this, - settingsComponent: this.pluginSettingsComponent + this.pluginSettingsComponent = this.addChild( + new PluginSettingsComponent({ + dataHandler: new PluginDataHandler(this), + pluginEventSource: new PluginEventSourceImpl(this) }) - })); + ); + + this.addChild( + new PluginSettingsTabComponent({ + plugin: this, + pluginSettingsTab: new PluginSettingsTab({ + plugin: this, + pluginSettingsComponent: this.pluginSettingsComponent + }) + }) + ); } /** * Called after pre-loaded components are initialized. */ - protected override async onloadImpl(): Promise { - await super.onloadImpl(); - - this.pluginSettingsComponent.on('saveSettings', () => { - invokeAsyncSafely(() => this.refreshBacklinkPanels()); + public override onload(): void { + this.pluginSettingsComponent.on('saveSettings', async () => { + await this.refreshBacklinkPanels(); }); } /** * Called when the workspace layout is ready. */ - protected override async onLayoutReady(): Promise { + protected async onLayoutReady(): Promise { const backlinksCorePlugin = this.app.internalPlugins.getPluginById(InternalPluginName.Backlink); if (!backlinksCorePlugin) { return; } const that = this; - registerPatch(this, getPrototypeOf(backlinksCorePlugin.instance), { + const patch = this.addChild(new MonkeyAroundComponent()); + patch.registerPatch(getPrototypeOf(backlinksCorePlugin.instance), { onUserEnable: (next: () => void) => { return function onUserEnablePatched(this: BacklinkPlugin): void { next.call(this); @@ -204,7 +211,8 @@ export class Plugin extends PluginBase { } const that = this; - registerPatch(this, getPrototypeOf(backlinkView.backlink.backlinkDom), { + const patch = this.addChild(new MonkeyAroundComponent()); + patch.registerPatch(getPrototypeOf(backlinkView.backlink.backlinkDom), { addResult: (next: AddResultFn): AddResultFn => { return function addResultPatched(this: ResultDom, file: TFile, result: ResultDomResult, content: string, shouldShowTitle?: boolean): ResultDomItem { return that.addResult(next, this, file, result, content, shouldShowTitle); diff --git a/tsconfig.json b/tsconfig.json index 1aa3ee7..8b6142f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,7 @@ "inlineSources": true, "lib": [ "DOM", - "DOM.Iterable", + "DOM.Iterable", "ES2024" ], "libReplacement": true,