From 3fd4a2ff9752b528f174fd1542eb62c8ae4d273e Mon Sep 17 00:00:00 2001 From: Netajam Date: Mon, 27 Apr 2026 20:36:59 +0200 Subject: [PATCH] Introduce Vitest test suite for UID generation and command behavior Sets up Vitest with an aliased obsidian stub so tests can load production modules without the real Electron-bound API, and a fake-app builder (in-memory vault, frontmatter, processFrontMatter) for testing commands that touch the Obsidian app object. Coverage: - uidUtils: generator selection (UUID/NanoID/ULID), NanoID separator injection, collision retry, getUIDFromFile/setUID/removeUID across edge cases (custom uidKey, legacy key cleanup, error paths). - commands: settings-driven decisions (autoGenerateUid toggle, scope=vault vs folder, exclusions including multi-entry / whitespace / blank / trailing-slash variants), bulk vs per-file parity, copy/clear flows, and degenerate "everything excluded" combinations. Includes one intentionally failing it() at commands.test.ts:225 that documents the inconsistency between handleClearUIDsInFolder (treats "/" as the whole vault) and the exclusion matcher (treats "/" as inert). The build's tsc step now excludes tests/ and *.test.ts; Vitest type-checks test code via esbuild at run time. Run with: npm test (or npm run test:watch). --- package-lock.json | 2392 +++++++++++++++++++++++++--------------- package.json | 5 +- src/commands.test.ts | 816 ++++++++++++++ src/uidUtils.test.ts | 597 ++++++++++ tests/fakes/app.ts | 178 +++ tests/obsidian-stub.ts | 45 + tsconfig.json | 8 +- vitest.config.ts | 14 + 8 files changed, 3181 insertions(+), 874 deletions(-) create mode 100644 src/commands.test.ts create mode 100644 src/uidUtils.test.ts create mode 100644 tests/fakes/app.ts create mode 100644 tests/obsidian-stub.ts create mode 100644 vitest.config.ts diff --git a/package-lock.json b/package-lock.json index 0e0cca2..5f8c396 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "note_uid_generator", - "version": "1.1.0", + "version": "1.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "note_uid_generator", - "version": "1.1.0", + "version": "1.2.1", "license": "MIT", "dependencies": { "nanoid": "^3.3.7", @@ -21,31 +21,8 @@ "esbuild": "^0.25.2", "obsidian": "latest", "tslib": "2.4.0", - "typescript": "4.7.4" - } - }, - "node_modules/@codemirror/state": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", - "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@marijn/find-cluster-break": "^1.0.0" - } - }, - "node_modules/@codemirror/view": { - "version": "6.36.5", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.5.tgz", - "integrity": "sha512-cd+FZEUlu3GQCYnguYm3EkhJ8KJVisqqUsCOKedBoAt/d9c76JUUap6U0UrpElln5k6VyrEOYliMuDAKIeDQLg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@codemirror/state": "^6.5.0", - "style-mod": "^4.1.0", - "w3c-keyname": "^2.2.4" + "typescript": "4.7.4", + "vitest": "^1.6.1" } }, "node_modules/@esbuild/aix-ppc64": { @@ -473,111 +450,23 @@ "node": ">=18" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", - "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.4.3" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@marijn/find-cluster-break": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", - "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, @@ -619,6 +508,363 @@ "node": ">= 8" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/codemirror": { "version": "5.60.8", "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", @@ -630,9 +876,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, @@ -700,7 +946,6 @@ "integrity": "sha512-ruKWTv+x0OOxbzIw9nW5oWlUopvP/IQDjB5ZqmTglLIoDTctLlAJpAQFpNPJP/ZI7hTT9sARBosEfaKbcFuECw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.29.0", "@typescript-eslint/types": "5.29.0", @@ -853,20 +1098,115 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "node_modules/@vitest/expect": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", + "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", + "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "1.6.1", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", + "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", + "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -874,66 +1214,19 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" + "acorn": "^8.11.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=0.4.0" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -944,22 +1237,14 @@ "node": ">=8" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": "*" } }, "node_modules/braces": { @@ -988,57 +1273,52 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/chai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=4" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", "dev": true, "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "get-func-name": "^2.0.2" }, "engines": { - "node": ">=7.0.0" + "node": "*" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", "dev": true, "license": "MIT" }, @@ -1075,12 +1355,28 @@ } } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "node_modules/deep-eql": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, "node_modules/dir-glob": { "version": "3.0.1", @@ -1095,19 +1391,6 @@ "node": ">=8" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/esbuild": { "version": "0.25.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", @@ -1149,77 +1432,6 @@ "@esbuild/win32-x64": "0.25.2" } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -1276,74 +1488,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -1377,22 +1521,39 @@ "node": ">=4.0" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } }, "node_modules/fast-glob": { "version": "3.3.3", @@ -1424,20 +1585,6 @@ "node": ">= 6" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -1448,19 +1595,6 @@ "reusify": "^1.0.4" } }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1474,52 +1608,21 @@ "node": ">=8" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", @@ -1527,52 +1630,24 @@ "dev": true, "license": "MIT" }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" + "node": "*" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -1599,21 +1674,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, - "license": "MIT" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": ">=16.17.0" } }, "node_modules/ignore": { @@ -1626,52 +1694,6 @@ "node": ">= 4" } }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1705,14 +1727,17 @@ "node": ">=0.12.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/isexe": { @@ -1722,84 +1747,54 @@ "dev": true, "license": "ISC" }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", "dev": true, "license": "MIT" }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", "dev": true, "license": "MIT", "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true, "license": "MIT" }, @@ -1827,19 +1822,39 @@ "node": ">=8.6" } }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/mlly/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", @@ -1875,12 +1890,34 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/obsidian": { "version": "1.8.7", @@ -1897,99 +1934,22 @@ "@codemirror/view": "^6.0.0" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", "dev": true, "license": "MIT", "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" + "mimic-fn": "^4.0.0" }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2010,6 +1970,30 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -2023,24 +2007,80 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.8.0" + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" } }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "node_modules/pkg-types/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/queue-microtask": { @@ -2064,6 +2104,13 @@ ], "license": "MIT" }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -2077,16 +2124,6 @@ "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -2098,21 +2135,49 @@ "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "node_modules/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "glob": "^7.1.3" + "@types/estree": "1.0.8" }, "bin": { - "rimraf": "bin.js" + "rollup": "dist/bin/rollup" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" } }, "node_modules/run-parallel": { @@ -2175,6 +2240,26 @@ "node": ">=8" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -2185,59 +2270,83 @@ "node": ">=8" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, + "license": "BSD-3-Clause", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/style-mod": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", - "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", - "dev": true, - "license": "MIT" - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "js-tokens": "^9.0.1" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, "license": "MIT" }, + "node_modules/tinypool": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2281,30 +2390,14 @@ "dev": true, "license": "0BSD" }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", "dev": true, "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, "node_modules/typescript": { @@ -2313,7 +2406,6 @@ "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -2322,6 +2414,13 @@ "node": ">=4.2.0" } }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "dev": true, + "license": "MIT" + }, "node_modules/ulid": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/ulid/-/ulid-3.0.2.tgz", @@ -2331,16 +2430,6 @@ "ulid": "dist/cli.js" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/uuid": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", @@ -2354,12 +2443,584 @@ "uuid": "dist/esm/bin/uuid" } }, - "node_modules/w3c-keyname": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", - "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", + "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitest": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", + "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "1.6.1", + "@vitest/runner": "1.6.1", + "@vitest/snapshot": "1.6.1", + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.1", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.6.1", + "@vitest/ui": "1.6.1", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } }, "node_modules/which": { "version": "2.0.2", @@ -2377,34 +3038,21 @@ "node": ">= 8" } }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" } } } diff --git a/package.json b/package.json index 52be04a..6cb5abf 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "scripts": { "dev": "node esbuild.config.mjs", "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", + "test": "vitest run", + "test:watch": "vitest", "version": "node version-bump.mjs && git add manifest.json versions.json" }, "keywords": [], @@ -19,7 +21,8 @@ "esbuild": "^0.25.2", "obsidian": "latest", "tslib": "2.4.0", - "typescript": "4.7.4" + "typescript": "4.7.4", + "vitest": "^1.6.1" }, "dependencies": { "nanoid": "^3.3.7", diff --git a/src/commands.test.ts b/src/commands.test.ts new file mode 100644 index 0000000..2af7d2b --- /dev/null +++ b/src/commands.test.ts @@ -0,0 +1,816 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { TFolder } from 'obsidian'; +import { + handleAutoGenerateUid, + handleAddMissingUidsInScope, + handleClearUIDsInFolder, + handleCopyTitleUid, + handleGenerateUpdateUid, + handleCreateUidIfMissing, + handleRemoveUid, + handleCopyUid, + handleCopyTitlesAndUidsFromFolder, + handleCopyTitlesAndUidsForMultipleFiles, +} from './commands'; +import { makeFakeApp, installFakeClipboard } from '../tests/fakes/app'; +import type { UIDGeneratorSettings } from './settings'; + +const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + +describe('handleAutoGenerateUid — settings-driven decisions', () => { + type Case = { + name: string; + settings: Partial; + filePath: string; + extension?: string; + existingUid?: string; + shouldGenerate: boolean; + }; + + const cases: Case[] = [ + { + name: 'autoGenerateUid=false → no-op even on a plain markdown file', + settings: { autoGenerateUid: false }, + filePath: 'foo.md', + shouldGenerate: false, + }, + { + name: 'non-markdown file → no-op', + settings: { autoGenerateUid: true }, + filePath: 'image.png', + extension: 'png', + shouldGenerate: false, + }, + { + name: 'autoGen=on, scope=vault, no exclusions → generates', + settings: { autoGenerateUid: true, autoGenerationScope: 'vault' }, + filePath: 'foo.md', + shouldGenerate: true, + }, + { + name: 'autoGen=on, file already has uid → no-op', + settings: { autoGenerateUid: true, autoGenerationScope: 'vault' }, + filePath: 'foo.md', + existingUid: 'pre-existing', + shouldGenerate: false, + }, + { + name: 'scope=folder, file inside scope → generates', + settings: { + autoGenerateUid: true, + autoGenerationScope: 'folder', + autoGenerationFolder: 'Notes', + }, + filePath: 'Notes/foo.md', + shouldGenerate: true, + }, + { + name: 'scope=folder, file in nested subfolder of scope → generates', + settings: { + autoGenerateUid: true, + autoGenerationScope: 'folder', + autoGenerationFolder: 'Notes', + }, + filePath: 'Notes/sub/deep/foo.md', + shouldGenerate: true, + }, + { + name: 'scope=folder, file outside scope → no-op', + settings: { + autoGenerateUid: true, + autoGenerationScope: 'folder', + autoGenerationFolder: 'Notes', + }, + filePath: 'OtherFolder/foo.md', + shouldGenerate: false, + }, + { + name: 'scope=folder with empty folder setting → no-op (defensive)', + settings: { + autoGenerateUid: true, + autoGenerationScope: 'folder', + autoGenerationFolder: '', + }, + filePath: 'foo.md', + shouldGenerate: false, + }, + { + name: 'scope=vault with exclusion match → no-op', + settings: { + autoGenerateUid: true, + autoGenerationScope: 'vault', + autoGenerationExclusions: ['Drafts'], + }, + filePath: 'Drafts/foo.md', + shouldGenerate: false, + }, + { + name: 'scope=vault with exclusion match in subfolder → no-op', + settings: { + autoGenerateUid: true, + autoGenerationScope: 'vault', + autoGenerationExclusions: ['Drafts'], + }, + filePath: 'Drafts/sub/foo.md', + shouldGenerate: false, + }, + { + name: 'scope=vault, exclusion does not match this file → generates', + settings: { + autoGenerateUid: true, + autoGenerationScope: 'vault', + autoGenerationExclusions: ['Drafts'], + }, + filePath: 'Notes/foo.md', + shouldGenerate: true, + }, + { + name: 'scope=folder with nested exclusion → exclusion wins', + settings: { + autoGenerateUid: true, + autoGenerationScope: 'folder', + autoGenerationFolder: 'Notes', + autoGenerationExclusions: ['Notes/private'], + }, + filePath: 'Notes/private/secret.md', + shouldGenerate: false, + }, + { + name: 'scope=folder with sibling not under exclusion → generates', + settings: { + autoGenerateUid: true, + autoGenerationScope: 'folder', + autoGenerationFolder: 'Notes', + autoGenerationExclusions: ['Notes/private'], + }, + filePath: 'Notes/public/ok.md', + shouldGenerate: true, + }, + ]; + + for (const c of cases) { + it(c.name, async () => { + const fm = c.existingUid ? { uid: c.existingUid } : {}; + const app = makeFakeApp({ + files: [{ path: c.filePath, extension: c.extension, frontmatter: fm }], + settings: c.settings, + }); + const file = app.files[0]; + + await handleAutoGenerateUid(app.plugin, file); + + const after = app.frontmatterByPath.get(c.filePath) ?? {}; + if (c.shouldGenerate) { + expect(after.uid).toMatch(UUID_RE); + expect(app.plugin.uidCache.has(after.uid as string)).toBe(true); + } else if (c.existingUid) { + expect(after.uid).toBe(c.existingUid); + } else { + expect(after.uid).toBeUndefined(); + } + }); + } +}); + +describe('handleAutoGenerateUid — exclusion list edge cases', () => { + async function runOn( + filePath: string, + exclusions: string[], + ): Promise { + const app = makeFakeApp({ + files: [{ path: filePath }], + settings: { + autoGenerateUid: true, + autoGenerationScope: 'vault', + autoGenerationExclusions: exclusions, + }, + }); + await handleAutoGenerateUid(app.plugin, app.files[0]); + return app.frontmatterByPath.get(filePath)?.uid as string | undefined; + } + + it('honours every entry in a multi-folder exclusion list', async () => { + expect(await runOn('Drafts/a.md', ['Drafts', 'Templates'])).toBeUndefined(); + expect(await runOn('Templates/b.md', ['Drafts', 'Templates'])).toBeUndefined(); + expect(await runOn('Notes/c.md', ['Drafts', 'Templates'])).toMatch(UUID_RE); + }); + + it('still applies later exclusions when an earlier one does not match', async () => { + // `.some()` short-circuits on the first match; this proves a non-match + // at the head of the list does not cause subsequent entries to be skipped. + expect(await runOn('Templates/b.md', ['Drafts', 'Templates'])).toBeUndefined(); + }); + + it('treats a trailing slash on an exclusion path as equivalent', async () => { + expect(await runOn('Drafts/a.md', ['Drafts/'])).toBeUndefined(); + }); + + it('strips surrounding whitespace from exclusion entries', async () => { + expect(await runOn('Drafts/a.md', [' Drafts '])).toBeUndefined(); + }); + + it('ignores blank/whitespace-only exclusion entries instead of matching everything', async () => { + // If an empty string accidentally matched as a prefix, every file would be excluded. + // The guard `normEx && (...)` should keep blank entries inert. + expect(await runOn('Notes/a.md', ['', ' '])).toMatch(UUID_RE); + }); + + it('combines blank entries with real ones without losing the real exclusion', async () => { + expect(await runOn('Drafts/a.md', ['', 'Drafts', ' '])).toBeUndefined(); + expect(await runOn('Notes/b.md', ['', 'Drafts', ' '])).toMatch(UUID_RE); + }); + + it('"/" excludes every file (consistent with handleClearUIDsInFolder)', async () => { + // EXPECTED behavior — currently fails. handleClearUIDsInFolder treats "/" + // as "the whole vault"; the exclusion matcher should follow the same + // convention so users get consistent semantics across settings. + // Fix lives in commands.ts: special-case normEx === '/' to match every path. + expect(await runOn('note.md', ['/'])).toBeUndefined(); + expect(await runOn('Notes/sub/deep.md', ['/'])).toBeUndefined(); + // Mixed with another entry, "/" should still short-circuit every file. + expect(await runOn('Notes/b.md', ['/', 'Drafts'])).toBeUndefined(); + }); +}); + +describe('settings that effectively exclude every file', () => { + // There's no single "exclude the whole vault" setting — the toggle for that + // is `autoGenerateUid: false`. These tests cover degenerate combinations + // that produce the same effect, to make sure nothing crashes or surprises. + + it('per-file: scope=folder pointing at a non-existent path skips every file', async () => { + const app = makeFakeApp({ + files: [ + { path: 'a.md' }, + { path: 'Notes/b.md' }, + { path: 'Notes/sub/c.md' }, + ], + settings: { + autoGenerateUid: true, + autoGenerationScope: 'folder', + autoGenerationFolder: 'NonExistent', + }, + }); + + for (const file of app.files) { + await handleAutoGenerateUid(app.plugin, file); + } + + for (const path of ['a.md', 'Notes/b.md', 'Notes/sub/c.md']) { + expect(app.frontmatterByPath.get(path)?.uid).toBeUndefined(); + } + expect(app.processFrontMatter).not.toHaveBeenCalled(); + }); + + it('per-file: scope=folder + that same folder excluded skips every file', async () => { + const app = makeFakeApp({ + files: [ + { path: 'Notes/a.md' }, + { path: 'Notes/sub/b.md' }, + { path: 'Other/c.md' }, + ], + settings: { + autoGenerateUid: true, + autoGenerationScope: 'folder', + autoGenerationFolder: 'Notes', + autoGenerationExclusions: ['Notes'], // cancels out the scope + }, + }); + + for (const file of app.files) { + await handleAutoGenerateUid(app.plugin, file); + } + + for (const path of ['Notes/a.md', 'Notes/sub/b.md', 'Other/c.md']) { + expect(app.frontmatterByPath.get(path)?.uid).toBeUndefined(); + } + }); + + it('bulk: handleAddMissingUidsInScope completes cleanly when every file is out of scope', async () => { + const app = makeFakeApp({ + files: [ + { path: 'a.md' }, + { path: 'Notes/b.md' }, + { path: 'Notes/sub/c.md' }, + ], + settings: { + autoGenerationScope: 'folder', + autoGenerationFolder: 'NonExistent', + }, + }); + + await expect(handleAddMissingUidsInScope(app.plugin)).resolves.toBeUndefined(); + + for (const path of ['a.md', 'Notes/b.md', 'Notes/sub/c.md']) { + expect(app.frontmatterByPath.get(path)?.uid).toBeUndefined(); + } + expect(app.processFrontMatter).not.toHaveBeenCalled(); + }); + + it('bulk: handleAddMissingUidsInScope completes cleanly when every file is excluded', async () => { + const app = makeFakeApp({ + files: [ + { path: 'Drafts/a.md' }, + { path: 'Templates/b.md' }, + { path: 'Templates/sub/c.md' }, + ], + settings: { + autoGenerationScope: 'vault', + autoGenerationExclusions: ['Drafts', 'Templates'], + }, + }); + + await expect(handleAddMissingUidsInScope(app.plugin)).resolves.toBeUndefined(); + + for (const path of ['Drafts/a.md', 'Templates/b.md', 'Templates/sub/c.md']) { + expect(app.frontmatterByPath.get(path)?.uid).toBeUndefined(); + } + expect(app.processFrontMatter).not.toHaveBeenCalled(); + }); +}); + +describe('parity — handleAutoGenerateUid vs handleAddMissingUidsInScope', () => { + // Both functions implement the same "should this file be skipped?" decision. + // One operates per-file (event-driven), the other in bulk. If the two ever + // drift, users would see inconsistent behavior between auto-gen on file + // create/open vs. the manual "Generate missing UIDs now" button. + type Scenario = { + name: string; + settings: Partial; + files: string[]; + }; + + const scenarios: Scenario[] = [ + { + name: 'vault scope, no exclusions', + settings: { autoGenerateUid: true, autoGenerationScope: 'vault' }, + files: ['root.md', 'Notes/a.md', 'Notes/sub/b.md', 'Drafts/c.md'], + }, + { + name: 'folder scope, no exclusions', + settings: { + autoGenerateUid: true, + autoGenerationScope: 'folder', + autoGenerationFolder: 'Notes', + }, + files: ['root.md', 'Notes/a.md', 'Notes/sub/b.md', 'Other/c.md'], + }, + { + name: 'vault scope with exclusions', + settings: { + autoGenerateUid: true, + autoGenerationScope: 'vault', + autoGenerationExclusions: ['Drafts', 'Templates'], + }, + files: ['Notes/a.md', 'Drafts/b.md', 'Templates/c.md', 'root.md'], + }, + { + name: 'folder scope plus exclusion under that scope', + settings: { + autoGenerateUid: true, + autoGenerationScope: 'folder', + autoGenerationFolder: 'Notes', + autoGenerationExclusions: ['Notes/private'], + }, + files: [ + 'Notes/a.md', + 'Notes/private/secret.md', + 'Notes/public/ok.md', + 'Other/d.md', + ], + }, + ]; + + async function runHandlerPerFile(s: Scenario): Promise> { + const app = makeFakeApp({ + files: s.files.map((path) => ({ path })), + settings: s.settings, + }); + for (const file of app.files) { + await handleAutoGenerateUid(app.plugin, file); + } + return collectUidPaths(app); + } + + async function runBulk(s: Scenario): Promise> { + const app = makeFakeApp({ + files: s.files.map((path) => ({ path })), + settings: s.settings, + }); + await handleAddMissingUidsInScope(app.plugin); + return collectUidPaths(app); + } + + function collectUidPaths(app: ReturnType): Set { + const out = new Set(); + for (const [path, fm] of app.frontmatterByPath) { + if (fm.uid) out.add(path); + } + return out; + } + + for (const s of scenarios) { + it(`makes the same skip/generate decisions: ${s.name}`, async () => { + const perFile = await runHandlerPerFile(s); + const bulk = await runBulk(s); + expect(Array.from(bulk).sort()).toEqual(Array.from(perFile).sort()); + // Sanity check: at least one file should have been generated, otherwise + // the scenario is degenerate and both functions trivially "agree". + expect(perFile.size).toBeGreaterThan(0); + }); + } +}); + +describe('handleAddMissingUidsInScope — bulk decision over many files', () => { + it('adds UIDs only to in-scope, non-excluded, uid-less markdown files', async () => { + const app = makeFakeApp({ + files: [ + { path: 'Notes/a.md' }, // in scope, no uid → add + { path: 'Notes/b.md', frontmatter: { uid: 'keep-me' } }, // in scope, has uid → skip + { path: 'Notes/private/secret.md' }, // excluded → skip + { path: 'Drafts/c.md' }, // out of scope → skip + { path: 'Notes/sub/d.md' }, // in scope (nested) → add + { path: 'image.png', extension: 'png' }, // not markdown → skip (not in getMarkdownFiles) + ], + settings: { + autoGenerationScope: 'folder', + autoGenerationFolder: 'Notes', + autoGenerationExclusions: ['Notes/private'], + }, + }); + + await handleAddMissingUidsInScope(app.plugin); + + const fm = (path: string) => app.frontmatterByPath.get(path) ?? {}; + expect(fm('Notes/a.md').uid).toMatch(UUID_RE); + expect(fm('Notes/b.md').uid).toBe('keep-me'); + expect(fm('Notes/private/secret.md').uid).toBeUndefined(); + expect(fm('Drafts/c.md').uid).toBeUndefined(); + expect(fm('Notes/sub/d.md').uid).toMatch(UUID_RE); + }); + + it('uses the configured uidKey, not "uid"', async () => { + const app = makeFakeApp({ + files: [{ path: 'Notes/a.md' }], + settings: { uidKey: 'note-id', autoGenerationScope: 'vault' }, + }); + + await handleAddMissingUidsInScope(app.plugin); + + const fm = app.frontmatterByPath.get('Notes/a.md') ?? {}; + expect(fm['note-id']).toMatch(UUID_RE); + expect(fm.uid).toBeUndefined(); + }); + + it('uses the configured generator (NanoID)', async () => { + const app = makeFakeApp({ + files: [{ path: 'a.md' }, { path: 'b.md' }], + settings: { + autoGenerationScope: 'vault', + uidGenerator: 'nanoid', + nanoidAlphabet: 'xyz', + nanoidLength: 8, + }, + }); + + await handleAddMissingUidsInScope(app.plugin); + + for (const path of ['a.md', 'b.md']) { + const fm = app.frontmatterByPath.get(path) ?? {}; + expect(fm.uid).toMatch(/^[xyz]{8}$/); + } + }); +}); + +describe('handleClearUIDsInFolder — folder scoping', () => { + it('removes uids only from files inside the target folder', async () => { + const app = makeFakeApp({ + files: [ + { path: 'Notes/a.md', frontmatter: { uid: 'a-uid' } }, + { path: 'Notes/sub/b.md', frontmatter: { uid: 'b-uid' } }, + { path: 'Other/c.md', frontmatter: { uid: 'c-uid' } }, + ], + }); + + await handleClearUIDsInFolder(app.plugin, 'Notes'); + + expect(app.frontmatterByPath.get('Notes/a.md')?.uid).toBeUndefined(); + expect(app.frontmatterByPath.get('Notes/sub/b.md')?.uid).toBeUndefined(); + expect(app.frontmatterByPath.get('Other/c.md')?.uid).toBe('c-uid'); + }); + + it('clears uids from every file when folder path is "/"', async () => { + const app = makeFakeApp({ + files: [ + { path: 'a.md', frontmatter: { uid: 'a-uid' } }, + { path: 'sub/b.md', frontmatter: { uid: 'b-uid' } }, + ], + }); + + await handleClearUIDsInFolder(app.plugin, '/'); + + expect(app.frontmatterByPath.get('a.md')?.uid).toBeUndefined(); + expect(app.frontmatterByPath.get('sub/b.md')?.uid).toBeUndefined(); + }); + + it('does nothing when the folder does not exist', async () => { + const app = makeFakeApp({ + files: [{ path: 'Notes/a.md', frontmatter: { uid: 'keep' } }], + }); + + await handleClearUIDsInFolder(app.plugin, 'NonExistent'); + + expect(app.frontmatterByPath.get('Notes/a.md')?.uid).toBe('keep'); + expect(app.processFrontMatter).not.toHaveBeenCalled(); + }); + + it('honours a custom uidKey when clearing', async () => { + const app = makeFakeApp({ + files: [{ path: 'Notes/a.md', frontmatter: { 'note-id': 'gone', other: 'kept' } }], + settings: { uidKey: 'note-id' }, + }); + + await handleClearUIDsInFolder(app.plugin, 'Notes'); + + const fm = app.frontmatterByPath.get('Notes/a.md') ?? {}; + expect(fm['note-id']).toBeUndefined(); + expect(fm.other).toBe('kept'); + }); +}); + +describe('handleCopyTitleUid — format selection', () => { + let clipboard: ReturnType; + + beforeEach(() => { + clipboard = installFakeClipboard(); + }); + + afterEach(() => { + clipboard.restore(); + }); + + it('uses copyFormatString when the file has a uid', async () => { + const app = makeFakeApp({ + files: [{ path: 'Notes/Hello.md', frontmatter: { uid: 'abc-123' } }], + settings: { copyFormatString: '{title} :: {uid}' }, + }); + + handleCopyTitleUid(app.plugin, app.files[0]); + // The handler kicks off a fire-and-forget promise; flush microtasks + await new Promise((r) => setImmediate(r)); + + expect(clipboard.writes).toEqual(['Hello :: abc-123']); + }); + + it('uses copyFormatStringMissingUid when the file has no uid', async () => { + const app = makeFakeApp({ + files: [{ path: 'Notes/Hello.md' }], + settings: { copyFormatStringMissingUid: '{title} (no {uidKey})' }, + }); + + handleCopyTitleUid(app.plugin, app.files[0]); + await new Promise((r) => setImmediate(r)); + + expect(clipboard.writes).toEqual(['Hello (no uid)']); + }); + + it('substitutes {uidKey} with the configured custom uidKey', async () => { + const app = makeFakeApp({ + files: [{ path: 'Hello.md' }], + settings: { + uidKey: 'note-id', + copyFormatStringMissingUid: '{title} — missing {uidKey}', + }, + }); + + handleCopyTitleUid(app.plugin, app.files[0]); + await new Promise((r) => setImmediate(r)); + + expect(clipboard.writes).toEqual(['Hello — missing note-id']); + }); +}); + +describe('handleGenerateUpdateUid — overwrites the active file uid', () => { + it('replaces an existing uid with a fresh one', async () => { + const app = makeFakeApp({ + files: [{ path: 'note.md', frontmatter: { uid: 'old-value' } }], + }); + app.setActiveFile(app.files[0]); + + await handleGenerateUpdateUid(app.plugin, true); + + const fm = app.frontmatterByPath.get('note.md') ?? {}; + expect(fm.uid).toMatch(UUID_RE); + expect(fm.uid).not.toBe('old-value'); + }); + + it('creates a uid when none exists', async () => { + const app = makeFakeApp({ files: [{ path: 'note.md' }] }); + app.setActiveFile(app.files[0]); + + await handleGenerateUpdateUid(app.plugin, true); + + expect(app.frontmatterByPath.get('note.md')?.uid).toMatch(UUID_RE); + }); + + it('is a no-op when there is no active markdown file', async () => { + const app = makeFakeApp({ files: [{ path: 'note.md' }] }); + // active file deliberately not set + + await handleGenerateUpdateUid(app.plugin, true); + + expect(app.processFrontMatter).not.toHaveBeenCalled(); + expect(app.frontmatterByPath.get('note.md')?.uid).toBeUndefined(); + }); +}); + +describe('handleCreateUidIfMissing — only sets when absent', () => { + it('writes a new uid when the file has none', async () => { + const app = makeFakeApp({ files: [{ path: 'note.md' }] }); + app.setActiveFile(app.files[0]); + + await handleCreateUidIfMissing(app.plugin); + + expect(app.frontmatterByPath.get('note.md')?.uid).toMatch(UUID_RE); + }); + + it('preserves an existing uid (no overwrite)', async () => { + const app = makeFakeApp({ + files: [{ path: 'note.md', frontmatter: { uid: 'keep-me' } }], + }); + app.setActiveFile(app.files[0]); + + await handleCreateUidIfMissing(app.plugin); + + expect(app.frontmatterByPath.get('note.md')?.uid).toBe('keep-me'); + // Short-circuits before processFrontMatter is invoked. + expect(app.processFrontMatter).not.toHaveBeenCalled(); + }); +}); + +describe('handleRemoveUid', () => { + it('removes the uid from the active file when present', async () => { + const app = makeFakeApp({ + files: [{ path: 'note.md', frontmatter: { uid: 'gone-soon' } }], + }); + app.setActiveFile(app.files[0]); + + await handleRemoveUid(app.plugin); + + expect(app.frontmatterByPath.get('note.md')?.uid).toBeUndefined(); + }); + + it('is a no-op when the active file has no uid', async () => { + const app = makeFakeApp({ files: [{ path: 'note.md' }] }); + app.setActiveFile(app.files[0]); + + await handleRemoveUid(app.plugin); + + // removeUID is called but finds nothing to delete; frontmatter stays empty. + expect(app.frontmatterByPath.get('note.md')?.uid).toBeUndefined(); + }); + + it('removes the value at a custom uidKey only', async () => { + const app = makeFakeApp({ + files: [ + { + path: 'note.md', + frontmatter: { 'note-id': 'gone', tag: 'kept' }, + }, + ], + settings: { uidKey: 'note-id' }, + }); + app.setActiveFile(app.files[0]); + + await handleRemoveUid(app.plugin); + + const fm = app.frontmatterByPath.get('note.md') ?? {}; + expect(fm['note-id']).toBeUndefined(); + expect(fm.tag).toBe('kept'); + }); +}); + +describe('handleCopyUid — copies the raw uid (no formatting)', () => { + let clipboard: ReturnType; + + beforeEach(() => { + clipboard = installFakeClipboard(); + }); + + afterEach(() => { + clipboard.restore(); + }); + + it('writes only the uid value, not title or key', async () => { + const app = makeFakeApp({ + files: [{ path: 'Hello.md', frontmatter: { uid: 'just-me' } }], + }); + app.setActiveFile(app.files[0]); + + handleCopyUid(app.plugin); + await new Promise((r) => setImmediate(r)); + + expect(clipboard.writes).toEqual(['just-me']); + }); +}); + +describe('handleCopyTitlesAndUidsFromFolder — bulk copy with format selection', () => { + let clipboard: ReturnType; + + beforeEach(() => { + clipboard = installFakeClipboard(); + }); + + afterEach(() => { + clipboard.restore(); + }); + + it('mixes the two format strings per file based on uid presence', async () => { + const app = makeFakeApp({ + files: [ + { path: 'Notes/A.md', frontmatter: { uid: 'a-uid' } }, + { path: 'Notes/B.md' }, + { path: 'Notes/sub/C.md', frontmatter: { uid: 'c-uid' } }, + { path: 'Other/D.md', frontmatter: { uid: 'd-uid' } }, + ], + settings: { + copyFormatString: '{title}={uid}', + copyFormatStringMissingUid: '{title}=missing', + }, + }); + const folder = app.folders.get('Notes') as TFolder; + + await handleCopyTitlesAndUidsFromFolder(app.plugin, folder); + + expect(clipboard.writes).toHaveLength(1); + const lines = clipboard.writes[0].split('\n').sort(); + expect(lines).toEqual(['A=a-uid', 'B=missing', 'C=c-uid'].sort()); + }); + + it('copies the entire vault when folder path is "/"', async () => { + const app = makeFakeApp({ + files: [ + { path: 'A.md', frontmatter: { uid: 'a' } }, + { path: 'sub/B.md', frontmatter: { uid: 'b' } }, + ], + settings: { copyFormatString: '{title}:{uid}' }, + }); + const root = app.folders.get('/') as TFolder; + + await handleCopyTitlesAndUidsFromFolder(app.plugin, root); + + expect(clipboard.writes).toHaveLength(1); + const lines = clipboard.writes[0].split('\n').sort(); + expect(lines).toEqual(['A:a', 'B:b']); + }); + + it('does not write to the clipboard when the folder is empty', async () => { + const app = makeFakeApp({ + files: [{ path: 'Other/A.md', frontmatter: { uid: 'a' } }], + }); + // Build a folder reference that exists in the tree but has no md children. + const empty = new TFolder(); + empty.path = 'NoMd'; + empty.name = 'NoMd'; + + await handleCopyTitlesAndUidsFromFolder(app.plugin, empty); + + expect(clipboard.writes).toEqual([]); + }); +}); + +describe('handleCopyTitlesAndUidsForMultipleFiles', () => { + let clipboard: ReturnType; + + beforeEach(() => { + clipboard = installFakeClipboard(); + }); + + afterEach(() => { + clipboard.restore(); + }); + + it('joins per-file output with newline and uses the right format per file', async () => { + const app = makeFakeApp({ + files: [ + { path: 'A.md', frontmatter: { uid: 'a-uid' } }, + { path: 'B.md' }, + ], + settings: { + copyFormatString: '{title}={uid}', + copyFormatStringMissingUid: '{title}=missing', + }, + }); + + await handleCopyTitlesAndUidsForMultipleFiles(app.plugin, app.files); + + expect(clipboard.writes).toEqual(['A=a-uid\nB=missing']); + }); + + it('does not write to the clipboard when the input array is empty', async () => { + const app = makeFakeApp({ files: [] }); + + await handleCopyTitlesAndUidsForMultipleFiles(app.plugin, []); + + expect(clipboard.writes).toEqual([]); + }); +}); diff --git a/src/uidUtils.test.ts b/src/uidUtils.test.ts new file mode 100644 index 0000000..32dd32e --- /dev/null +++ b/src/uidUtils.test.ts @@ -0,0 +1,597 @@ +import { describe, it, expect, vi } from 'vitest'; +import { TFile } from 'obsidian'; + +import { + generateUID, + formatCopyString, + getUIDFromFile, + setUID, + removeUID, +} from './uidUtils'; +import type UIDGenerator from './main'; + +type Settings = { + uidKey: string; + uidGenerator: 'uuid' | 'nanoid' | 'ulid'; + nanoidLength: number; + nanoidAlphabet: string; + nanoidSeparators: Array<{ char: string; position: number }>; +}; + +function makePlugin( + overrides: Partial = {}, + cache: Set = new Set(), +): UIDGenerator { + const settings: Settings = { + uidKey: 'uid', + uidGenerator: 'uuid', + nanoidLength: 21, + nanoidAlphabet: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', + nanoidSeparators: [], + ...overrides, + }; + return { settings, uidCache: cache } as unknown as UIDGenerator; +} + +type Frontmatter = Record; + +function makeFile(path: string, extension = 'md'): TFile { + const file = new TFile(); + file.path = path; + file.extension = extension; + return file; +} + +interface PluginHarness { + plugin: UIDGenerator; + frontmatter: Frontmatter; + uidCache: Set; + uidPathMap: Map; + processFrontMatter: ReturnType; +} + +function makePluginWithApp(opts: { + uidKey?: string; + frontmatter?: Frontmatter; + getFileCache?: (file: TFile) => unknown; + processFrontMatterImpl?: ( + file: TFile, + fn: (fm: Frontmatter) => void, + ) => Promise; +} = {}): PluginHarness { + const fm: Frontmatter = opts.frontmatter ?? {}; + const uidCache = new Set(); + const uidPathMap = new Map(); + + const defaultProcess = async ( + _file: TFile, + fn: (frontmatter: Frontmatter) => void, + ) => { + fn(fm); + }; + const processFrontMatter = vi.fn(opts.processFrontMatterImpl ?? defaultProcess); + + const plugin = { + settings: { + uidKey: opts.uidKey ?? 'uid', + uidGenerator: 'uuid', + nanoidLength: 21, + nanoidAlphabet: 'abc', + nanoidSeparators: [], + }, + uidCache, + uidPathMap, + app: { + metadataCache: { + getFileCache: opts.getFileCache ?? (() => ({ frontmatter: fm })), + }, + fileManager: { + processFrontMatter, + }, + }, + } as unknown as UIDGenerator; + + return { plugin, frontmatter: fm, uidCache, uidPathMap, processFrontMatter }; +} + +describe('formatCopyString', () => { + it('substitutes title, uid, and uidKey', () => { + const plugin = makePlugin({ uidKey: 'note-id' }); + expect( + formatCopyString(plugin, '{title} - {uidKey}: {uid}', 'My Note', 'abc123'), + ).toBe('My Note - note-id: abc123'); + }); + + it('replaces missing uid with empty string', () => { + const plugin = makePlugin(); + expect(formatCopyString(plugin, '{title} {uid}', 'Hello', null)).toBe('Hello '); + expect(formatCopyString(plugin, '{title} {uid}', 'Hello', undefined)).toBe('Hello '); + }); + + it('replaces missing title with empty string', () => { + const plugin = makePlugin(); + expect(formatCopyString(plugin, '{title}-{uid}', '', 'x')).toBe('-x'); + }); + + it('replaces every occurrence of a placeholder', () => { + const plugin = makePlugin(); + expect(formatCopyString(plugin, '{uid}/{uid}', '', 'abc')).toBe('abc/abc'); + }); + + it('returns the format string unchanged when no placeholders are present', () => { + const plugin = makePlugin(); + expect(formatCopyString(plugin, 'no placeholders here', 't', 'u')).toBe( + 'no placeholders here', + ); + }); + + it('returns an empty string when the format string is empty', () => { + const plugin = makePlugin(); + expect(formatCopyString(plugin, '', 'title', 'uid')).toBe(''); + }); + + it('substitutes a {uid} literal inside the title (placeholders are processed in order)', () => { + // {title} is replaced before {uid}, so a literal "{uid}" in the title + // will itself be replaced on the next pass. Documenting current behavior. + const plugin = makePlugin(); + expect(formatCopyString(plugin, '{title}', 'has {uid} inside', 'X')).toBe( + 'has X inside', + ); + }); +}); + +describe('generateUID — generator selection', () => { + it('returns a valid UUID v4 by default', () => { + const id = generateUID(makePlugin({ uidGenerator: 'uuid' })); + expect(id).toMatch( + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, + ); + }); + + it('returns a 26-char Crockford Base32 ULID when configured', () => { + const id = generateUID(makePlugin({ uidGenerator: 'ulid' })); + expect(id).toMatch(/^[0-9A-HJKMNP-TV-Z]{26}$/); + }); + + it('returns a NanoID with the configured length and alphabet', () => { + const id = generateUID( + makePlugin({ uidGenerator: 'nanoid', nanoidAlphabet: 'abc', nanoidLength: 10 }), + ); + expect(id).toHaveLength(10); + expect(id).toMatch(/^[abc]+$/); + }); +}); + +describe('generateUID — NanoID separator injection', () => { + const constantNanoid = (length: number) => ({ + uidGenerator: 'nanoid' as const, + nanoidAlphabet: 'a', + nanoidLength: length, + }); + + it('injects a separator at a positive position', () => { + const plugin = makePlugin({ + ...constantNanoid(6), + nanoidSeparators: [{ char: '-', position: 3 }], + }); + expect(generateUID(plugin)).toBe('aaa-aaa'); + }); + + it('injects a separator at a negative position (counted from the end)', () => { + const plugin = makePlugin({ + ...constantNanoid(6), + nanoidSeparators: [{ char: '_', position: -2 }], + }); + expect(generateUID(plugin)).toBe('aaaa_aa'); + }); + + it('clamps positions outside the valid range', () => { + const plugin = makePlugin({ + ...constantNanoid(4), + nanoidSeparators: [ + { char: 'L', position: -100 }, + { char: 'R', position: 100 }, + ], + }); + expect(generateUID(plugin)).toBe('LaaaaR'); + }); + + it('applies multiple separators in the correct order', () => { + const plugin = makePlugin({ + ...constantNanoid(6), + nanoidSeparators: [ + { char: '-', position: 2 }, + { char: '-', position: 4 }, + ], + }); + expect(generateUID(plugin)).toBe('aa-aa-aa'); + }); + + it('ignores separators with empty or multi-char strings', () => { + const plugin = makePlugin({ + ...constantNanoid(4), + nanoidSeparators: [ + { char: '', position: 2 }, + { char: 'xx', position: 1 }, + ], + }); + expect(generateUID(plugin)).toBe('aaaa'); + }); + + it('inserts a separator at position 0 (prepended)', () => { + const plugin = makePlugin({ + ...constantNanoid(4), + nanoidSeparators: [{ char: '<', position: 0 }], + }); + expect(generateUID(plugin)).toBe(' { + const plugin = makePlugin({ + ...constantNanoid(4), + nanoidSeparators: [{ char: '>', position: 4 }], + }); + expect(generateUID(plugin)).toBe('aaaa>'); + }); + + it('inserts a separator at position == -nanoidLength (wraps to position 0)', () => { + const plugin = makePlugin({ + ...constantNanoid(4), + nanoidSeparators: [{ char: '|', position: -4 }], + }); + expect(generateUID(plugin)).toBe('|aaaa'); + }); + + it('handles two separators sharing the same position', () => { + // Both have position 2; sort is stable on ties, so they're inserted at the + // same slice index in iteration order. Each insertion shifts the next. + const plugin = makePlugin({ + ...constantNanoid(4), + nanoidSeparators: [ + { char: 'X', position: 2 }, + { char: 'Y', position: 2 }, + ], + }); + expect(generateUID(plugin)).toMatch(/^aa(XY|YX)aa$/); + }); +}); + +describe('generateUID — collision handling', () => { + it('returns the duplicate after exhausting retries when no unique id is possible', () => { + const cache = new Set(['a']); + const plugin = makePlugin( + { uidGenerator: 'nanoid', nanoidAlphabet: 'a', nanoidLength: 1 }, + cache, + ); + expect(generateUID(plugin)).toBe('a'); + }); + + it('returns a non-cached id when one is available', () => { + const cache = new Set(); // empty cache, no collisions possible + const plugin = makePlugin( + { uidGenerator: 'nanoid', nanoidAlphabet: 'ab', nanoidLength: 8 }, + cache, + ); + const id = generateUID(plugin); + expect(id).toMatch(/^[ab]{8}$/); + expect(cache.has(id)).toBe(false); + }); +}); + +describe('generateUID — NanoID generator caching', () => { + it('reflects an alphabet change immediately (cache is invalidated)', () => { + const plugin = makePlugin({ + uidGenerator: 'nanoid', + nanoidAlphabet: 'a', + nanoidLength: 6, + }); + expect(generateUID(plugin)).toBe('aaaaaa'); + + plugin.settings.nanoidAlphabet = 'b'; + expect(generateUID(plugin)).toBe('bbbbbb'); + }); + + it('reflects a length change immediately (cache is invalidated)', () => { + const plugin = makePlugin({ + uidGenerator: 'nanoid', + nanoidAlphabet: 'a', + nanoidLength: 4, + }); + expect(generateUID(plugin)).toHaveLength(4); + + plugin.settings.nanoidLength = 12; + expect(generateUID(plugin)).toHaveLength(12); + }); +}); + +describe('getUIDFromFile', () => { + it('returns null when file is null', () => { + const { plugin } = makePluginWithApp(); + expect(getUIDFromFile(plugin, null)).toBeNull(); + }); + + it('returns null when the file has no frontmatter', () => { + const { plugin } = makePluginWithApp({ getFileCache: () => ({}) }); + expect(getUIDFromFile(plugin, makeFile('note.md'))).toBeNull(); + }); + + it('returns null when the configured key is missing', () => { + const { plugin } = makePluginWithApp({ + uidKey: 'note-id', + frontmatter: { other: 'value' }, + }); + expect(getUIDFromFile(plugin, makeFile('note.md'))).toBeNull(); + }); + + it('returns the value when the key is present as a string', () => { + const { plugin } = makePluginWithApp({ + frontmatter: { uid: 'abc-123' }, + }); + expect(getUIDFromFile(plugin, makeFile('note.md'))).toBe('abc-123'); + }); + + it('coerces numeric YAML values to a string', () => { + const { plugin } = makePluginWithApp({ + frontmatter: { uid: 12345 }, + }); + expect(getUIDFromFile(plugin, makeFile('note.md'))).toBe('12345'); + }); + + it('returns null for unsupported value types (boolean, array, object)', () => { + const cases: Frontmatter[] = [ + { uid: true }, + { uid: [1, 2, 3] }, + { uid: { nested: 'thing' } }, + ]; + for (const fm of cases) { + const { plugin } = makePluginWithApp({ frontmatter: fm }); + expect(getUIDFromFile(plugin, makeFile('note.md'))).toBeNull(); + } + }); + + it('swallows errors thrown by the metadata cache and returns null', () => { + const { plugin } = makePluginWithApp({ + getFileCache: () => { + throw new Error('boom'); + }, + }); + const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + expect(getUIDFromFile(plugin, makeFile('note.md'))).toBeNull(); + expect(errSpy).toHaveBeenCalled(); + errSpy.mockRestore(); + }); + + it('coerces a numeric 0 to the string "0" (does not treat it as missing)', () => { + const { plugin } = makePluginWithApp({ frontmatter: { uid: 0 } }); + expect(getUIDFromFile(plugin, makeFile('note.md'))).toBe('0'); + }); + + it('returns the empty string when the value is an empty string', () => { + // Empty string is technically "present"; the function returns it verbatim. + // Documenting current behavior — callers must check for falsy themselves. + const { plugin } = makePluginWithApp({ frontmatter: { uid: '' } }); + expect(getUIDFromFile(plugin, makeFile('note.md'))).toBe(''); + }); + + it('returns null when the value is explicitly null', () => { + const { plugin } = makePluginWithApp({ frontmatter: { uid: null } }); + expect(getUIDFromFile(plugin, makeFile('note.md'))).toBeNull(); + }); + + it('returns null when getFileCache returns null', () => { + const { plugin } = makePluginWithApp({ getFileCache: () => null }); + expect(getUIDFromFile(plugin, makeFile('note.md'))).toBeNull(); + }); + + it('reads the configured custom uidKey instead of "uid"', () => { + const { plugin } = makePluginWithApp({ + uidKey: 'note-id', + frontmatter: { uid: 'wrong', 'note-id': 'right' }, + }); + expect(getUIDFromFile(plugin, makeFile('note.md'))).toBe('right'); + }); +}); + +describe('setUID', () => { + it('returns false for a null file', async () => { + const { plugin } = makePluginWithApp(); + expect(await setUID(plugin, null as unknown as TFile, 'x')).toBe(false); + }); + + it('returns false for a non-TFile object', async () => { + const { plugin, processFrontMatter } = makePluginWithApp(); + const fakeFile = { path: 'note.md', extension: 'md' } as unknown as TFile; + expect(await setUID(plugin, fakeFile, 'x')).toBe(false); + expect(processFrontMatter).not.toHaveBeenCalled(); + }); + + it('returns false for non-markdown files', async () => { + const { plugin, processFrontMatter } = makePluginWithApp(); + expect(await setUID(plugin, makeFile('image.png', 'png'), 'x')).toBe(false); + expect(processFrontMatter).not.toHaveBeenCalled(); + }); + + it('returns false when the UID is empty', async () => { + const { plugin, processFrontMatter } = makePluginWithApp(); + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + expect(await setUID(plugin, makeFile('note.md'), '')).toBe(false); + expect(processFrontMatter).not.toHaveBeenCalled(); + warnSpy.mockRestore(); + }); + + it('sets the UID and updates caches when the key is missing', async () => { + const { plugin, frontmatter, uidCache, uidPathMap } = makePluginWithApp({ + frontmatter: {}, + }); + const file = makeFile('note.md'); + expect(await setUID(plugin, file, 'new-uid')).toBe(true); + expect(frontmatter.uid).toBe('new-uid'); + expect(uidCache.has('new-uid')).toBe(true); + expect(uidPathMap.get('note.md')).toBe('new-uid'); + }); + + it('does not overwrite an existing UID when overwrite=false', async () => { + const { plugin, frontmatter, uidCache, uidPathMap } = makePluginWithApp({ + frontmatter: { uid: 'existing' }, + }); + const file = makeFile('note.md'); + expect(await setUID(plugin, file, 'new-uid')).toBe(false); + expect(frontmatter.uid).toBe('existing'); + expect(uidCache.has('new-uid')).toBe(false); + expect(uidPathMap.has('note.md')).toBe(false); + }); + + it('overwrites an existing UID when overwrite=true and the value differs', async () => { + const { plugin, frontmatter, uidCache } = makePluginWithApp({ + frontmatter: { uid: 'old' }, + }); + const file = makeFile('note.md'); + expect(await setUID(plugin, file, 'new', true)).toBe(true); + expect(frontmatter.uid).toBe('new'); + expect(uidCache.has('new')).toBe(true); + }); + + it('reports no change when overwrite=true but the value is identical', async () => { + const { plugin, uidCache } = makePluginWithApp({ + frontmatter: { uid: 'same' }, + }); + const file = makeFile('note.md'); + expect(await setUID(plugin, file, 'same', true)).toBe(false); + expect(uidCache.has('same')).toBe(false); + }); + + it('removes legacy uid/Uid/UID keys when a custom key is configured', async () => { + const { plugin, frontmatter } = makePluginWithApp({ + uidKey: 'note-id', + frontmatter: { uid: 'a', Uid: 'b', UID: 'c' }, + }); + const file = makeFile('note.md'); + expect(await setUID(plugin, file, 'fresh')).toBe(true); + expect(frontmatter['note-id']).toBe('fresh'); + expect(frontmatter.uid).toBeUndefined(); + expect(frontmatter.Uid).toBeUndefined(); + expect(frontmatter.UID).toBeUndefined(); + }); + + it('returns false and logs when processFrontMatter rejects', async () => { + const { plugin } = makePluginWithApp({ + processFrontMatterImpl: async () => { + throw new Error('disk error'); + }, + }); + const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + expect(await setUID(plugin, makeFile('note.md'), 'x')).toBe(false); + expect(errSpy).toHaveBeenCalled(); + errSpy.mockRestore(); + }); + + it('treats an existing empty-string UID as missing and writes the new value', async () => { + const { plugin, frontmatter, uidCache } = makePluginWithApp({ + frontmatter: { uid: '' }, + }); + expect(await setUID(plugin, makeFile('note.md'), 'fresh')).toBe(true); + expect(frontmatter.uid).toBe('fresh'); + expect(uidCache.has('fresh')).toBe(true); + }); + + it('treats an existing null UID as missing and writes the new value', async () => { + const { plugin, frontmatter } = makePluginWithApp({ + frontmatter: { uid: null }, + }); + expect(await setUID(plugin, makeFile('note.md'), 'fresh')).toBe(true); + expect(frontmatter.uid).toBe('fresh'); + }); + + it('preserves the active uid key while still cleaning Uid/UID legacy variants', async () => { + const { plugin, frontmatter } = makePluginWithApp({ + uidKey: 'uid', + frontmatter: { Uid: 'legacy-mixed', UID: 'legacy-upper' }, + }); + expect(await setUID(plugin, makeFile('note.md'), 'new-uid')).toBe(true); + expect(frontmatter.uid).toBe('new-uid'); + expect(frontmatter.Uid).toBeUndefined(); + expect(frontmatter.UID).toBeUndefined(); + }); + + it('rejects non-md extensions like canvas', async () => { + const { plugin, processFrontMatter } = makePluginWithApp(); + expect(await setUID(plugin, makeFile('board.canvas', 'canvas'), 'x')).toBe(false); + expect(processFrontMatter).not.toHaveBeenCalled(); + }); + + it('rejects files with no extension', async () => { + const { plugin, processFrontMatter } = makePluginWithApp(); + expect(await setUID(plugin, makeFile('weird', ''), 'x')).toBe(false); + expect(processFrontMatter).not.toHaveBeenCalled(); + }); +}); + +describe('removeUID', () => { + it('returns false for a null file', async () => { + const { plugin } = makePluginWithApp(); + expect(await removeUID(plugin, null as unknown as TFile)).toBe(false); + }); + + it('returns false for a non-TFile object', async () => { + const { plugin, processFrontMatter } = makePluginWithApp(); + const fakeFile = { path: 'note.md', extension: 'md' } as unknown as TFile; + expect(await removeUID(plugin, fakeFile)).toBe(false); + expect(processFrontMatter).not.toHaveBeenCalled(); + }); + + it('returns false when the UID key is not present', async () => { + const { plugin, frontmatter } = makePluginWithApp({ + frontmatter: { other: 'thing' }, + }); + expect(await removeUID(plugin, makeFile('note.md'))).toBe(false); + expect(frontmatter.other).toBe('thing'); + }); + + it('removes the UID and updates caches when present', async () => { + const { plugin, frontmatter, uidCache, uidPathMap } = makePluginWithApp({ + frontmatter: { uid: 'gone' }, + }); + uidCache.add('gone'); + uidPathMap.set('note.md', 'gone'); + + expect(await removeUID(plugin, makeFile('note.md'))).toBe(true); + expect(frontmatter.uid).toBeUndefined(); + expect(uidCache.has('gone')).toBe(false); + expect(uidPathMap.has('note.md')).toBe(false); + }); + + it('returns false and logs when processFrontMatter rejects', async () => { + const { plugin } = makePluginWithApp({ + processFrontMatterImpl: async () => { + throw new Error('disk error'); + }, + }); + const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + expect(await removeUID(plugin, makeFile('note.md'))).toBe(false); + expect(errSpy).toHaveBeenCalled(); + errSpy.mockRestore(); + }); + + it('removes the value at the configured custom uidKey', async () => { + const { plugin, frontmatter } = makePluginWithApp({ + uidKey: 'note-id', + frontmatter: { 'note-id': 'gone', uid: 'untouched' }, + }); + expect(await removeUID(plugin, makeFile('note.md'))).toBe(true); + expect(frontmatter['note-id']).toBeUndefined(); + expect(frontmatter.uid).toBe('untouched'); + }); + + it('removes a falsy numeric UID and cleans up the cache via its string form', async () => { + const { plugin, frontmatter, uidCache, uidPathMap } = makePluginWithApp({ + frontmatter: { uid: 0 }, + }); + uidCache.add('0'); + uidPathMap.set('note.md', '0'); + + expect(await removeUID(plugin, makeFile('note.md'))).toBe(true); + expect(frontmatter.uid).toBeUndefined(); + expect(uidCache.has('0')).toBe(false); + expect(uidPathMap.has('note.md')).toBe(false); + }); +}); diff --git a/tests/fakes/app.ts b/tests/fakes/app.ts new file mode 100644 index 0000000..c77c484 --- /dev/null +++ b/tests/fakes/app.ts @@ -0,0 +1,178 @@ +import { vi } from 'vitest'; +import { TFile, TFolder, MarkdownView } from 'obsidian'; +import type UIDGenerator from '../../src/main'; +import type { UIDGeneratorSettings } from '../../src/settings'; + +export interface FileSpec { + path: string; + extension?: string; + frontmatter?: Record; +} + +const DEFAULT_SETTINGS: UIDGeneratorSettings = { + uidKey: 'uid', + autoGenerateUid: false, + uidGenerator: 'uuid', + nanoidLength: 21, + nanoidAlphabet: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', + nanoidSeparators: [], + autoGenerationScope: 'vault', + autoGenerationFolder: '', + autoGenerationExclusions: [], + folderToClear: '', + copyFormatString: '{title} - {uidKey}: {uid}', + copyFormatStringMissingUid: '{title} - No {uidKey}', +}; + +function buildTFile(spec: FileSpec, parent: TFolder | null): TFile { + const file = new TFile(); + file.path = spec.path; + file.extension = spec.extension ?? 'md'; + const slash = spec.path.lastIndexOf('/'); + const name = slash >= 0 ? spec.path.slice(slash + 1) : spec.path; + const ext = '.' + file.extension; + file.basename = file.extension && name.endsWith(ext) ? name.slice(0, -ext.length) : name; + file.parent = parent; + return file; +} + +function buildFolderTree(files: TFile[]): Map { + const folders = new Map(); + const root = new TFolder(); + root.path = '/'; + root.name = ''; + folders.set('/', root); + + for (const file of files) { + const parts = file.path.split('/'); + parts.pop(); + let parentPath = '/'; + let parent = root; + for (const part of parts) { + const path = parentPath === '/' ? part : `${parentPath}/${part}`; + let folder = folders.get(path); + if (!folder) { + folder = new TFolder(); + folder.path = path; + folder.name = part; + folders.set(path, folder); + parent.children.push(folder); + } + parent = folder; + parentPath = path; + } + file.parent = parent; + parent.children.push(file); + } + return folders; +} + +export interface FakeApp { + plugin: UIDGenerator; + settings: UIDGeneratorSettings; + files: TFile[]; + folders: Map; + frontmatterByPath: Map>; + processFrontMatter: ReturnType; + clipboardWrites: string[]; + activeFile: TFile | null; + setActiveFile(file: TFile | null): void; +} + +export function makeFakeApp(opts: { + files: FileSpec[]; + settings?: Partial; + uidCache?: Set; + uidPathMap?: Map; +}): FakeApp { + const settings: UIDGeneratorSettings = { ...DEFAULT_SETTINGS, ...opts.settings }; + const uidCache = opts.uidCache ?? new Set(); + const uidPathMap = opts.uidPathMap ?? new Map(); + + const fmByPath = new Map>(); + for (const spec of opts.files) { + fmByPath.set(spec.path, { ...(spec.frontmatter ?? {}) }); + } + + const tFiles = opts.files.map((spec) => buildTFile(spec, null)); + const folders = buildFolderTree(tFiles); + + let activeFile: TFile | null = null; + const activeView: MarkdownView = new MarkdownView(); + + const processFrontMatter = vi.fn( + async (file: TFile, fn: (frontmatter: Record) => void) => { + let fm = fmByPath.get(file.path); + if (!fm) { + fm = {}; + fmByPath.set(file.path, fm); + } + fn(fm); + }, + ); + + const clipboardWrites: string[] = []; + + const plugin = { + settings, + uidCache, + uidPathMap, + app: { + workspace: { + getActiveViewOfType: (_view: unknown) => (activeFile ? activeView : null), + activeLeaf: null, + getLeavesOfType: (_type: string) => [], + }, + vault: { + getMarkdownFiles: () => tFiles.filter((f) => f.extension === 'md'), + getAbstractFileByPath: (path: string) => { + if (folders.has(path)) return folders.get(path) ?? null; + return tFiles.find((f) => f.path === path) ?? null; + }, + }, + metadataCache: { + getFileCache: (file: TFile) => ({ frontmatter: fmByPath.get(file.path) }), + }, + fileManager: { + processFrontMatter, + }, + }, + } as unknown as UIDGenerator; + + return { + plugin, + settings, + files: tFiles, + folders, + frontmatterByPath: fmByPath, + processFrontMatter, + clipboardWrites, + get activeFile() { + return activeFile; + }, + setActiveFile(file: TFile | null) { + activeFile = file; + activeView.file = file; + }, + }; +} + +export function installFakeClipboard(): { writes: string[]; restore: () => void } { + const writes: string[] = []; + const fakeNavigator = { + clipboard: { + writeText: vi.fn(async (text: string) => { + writes.push(text); + }), + }, + }; + // vi.stubGlobal uses Object.defineProperty so it works even when the + // runtime exposes `navigator` as a read-only getter. + vi.stubGlobal('navigator', fakeNavigator); + return { + writes, + restore: () => { + vi.unstubAllGlobals(); + }, + }; +} diff --git a/tests/obsidian-stub.ts b/tests/obsidian-stub.ts new file mode 100644 index 0000000..c91f22f --- /dev/null +++ b/tests/obsidian-stub.ts @@ -0,0 +1,45 @@ +// Test-only stub for the `obsidian` package, which ships types but no runtime entry. +// Vitest aliases imports of `obsidian` to this file so test code can load modules +// that import from `obsidian` without pulling in the real Electron-bound API. +// +// Only the runtime behavior the production code under test relies on is implemented. +// Anything else (full vault traversal, real plugin lifecycle, etc.) is intentionally +// out of scope — tests build their own fakes against this surface. + +export class TAbstractFile { + path = ''; +} + +export class TFile extends TAbstractFile { + extension = ''; + basename = ''; + parent: TFolder | null = null; +} + +export class TFolder extends TAbstractFile { + name = ''; + children: TAbstractFile[] = []; +} + +export class MarkdownView { + file: TFile | null = null; +} + +export class WorkspaceLeaf { + view: unknown = null; +} + +export class Notice { + constructor(_message: string, _timeout?: number) {} + setMessage(_message: string): this { + return this; + } + hide(): void {} +} + +export function normalizePath(input: string): string { + if (!input) return ''; + let p = input.replace(/\\/g, '/').replace(/\/+/g, '/').trim(); + while (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1); + return p; +} diff --git a/tsconfig.json b/tsconfig.json index 8b3d258..76b4dbf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,8 +23,14 @@ "./src/typings" // Your custom typings folder ], "include": [ - "**/*.ts","**/*.d.ts", "src/typings/obsidian.d.ts" + "**/*.ts","**/*.d.ts", "src/typings/obsidian.d.ts" + ], + "exclude": [ + "node_modules", + "tests", + "src/**/*.test.ts", + "vitest.config.ts" ] } diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..d3c0daa --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vitest/config'; +import * as path from 'path'; + +export default defineConfig({ + resolve: { + alias: { + obsidian: path.resolve(__dirname, 'tests/obsidian-stub.ts'), + }, + }, + test: { + include: ['src/**/*.test.ts'], + environment: 'node', + }, +});