diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index e269dbc6..cccb3af4 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -25,6 +25,9 @@ jobs: - name: Run linter run: npm run lint + + - name: Run type check + run: npm run typecheck - name: Run tests run: npm run test:once diff --git a/.vscode/settings.json b/.vscode/settings.json index 0fa3e5a1..f8714b04 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,8 @@ "editor.codeActionsOnSave": { "quickfix.biome": "explicit", "source.organizeImports.biome": "explicit" - } + }, + "editor.tabSize": 2, + "editor.insertSpaces": true, + "editor.detectIndentation": false } diff --git a/biome.json b/biome.json index c2436689..f48fcf15 100644 --- a/biome.json +++ b/biome.json @@ -8,11 +8,12 @@ "files": { "ignoreUnknown": false, "ignore": ["dist"], - "include": ["./src"] + "include": ["./src/**/*.ts", "./tsconfig.json", "./global.d.ts"] }, "formatter": { "enabled": true, - "indentStyle": "space" + "indentStyle": "space", + "indentWidth": 2 }, "organizeImports": { "enabled": true diff --git a/package-lock.json b/package-lock.json index ed35baac..e8e0771e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,25 +1,49 @@ { "name": "vue-gtag", - "version": "2.1.2", + "version": "3.0.0-beta.19", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vue-gtag", - "version": "2.1.2", + "version": "3.0.0-beta.19", "license": "MIT", "devDependencies": { "@biomejs/biome": "1.9.4", + "@types/node": "^22.13.5", + "@vitest/coverage-v8": "^3.0.5", "flush-promises": "^1.0.2", "jsdom": "^26.0.0", "mockdate": "^3.0.5", + "terser": "^5.39.0", "vite": "^6.1.0", + "vite-plugin-dts": "^4.5.0", "vitest": "^3.0.5", - "vue": "^3.0.0", + "vue": "^3.5.13", "vue-router": "^4.5.0" }, "peerDependencies": { - "vue": "^3.0.0" + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "peerDependenciesMeta": { + "vue-router": { + "optional": true + } + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/@asamuzakjp/css-color": { @@ -86,6 +110,16 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@biomejs/biome": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", @@ -366,9 +400,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", - "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", + "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", "cpu": [ "ppc64" ], @@ -383,9 +417,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", - "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", + "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", "cpu": [ "arm" ], @@ -400,9 +434,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", - "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", + "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", "cpu": [ "arm64" ], @@ -417,9 +451,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", - "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", + "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", "cpu": [ "x64" ], @@ -434,9 +468,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", - "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", + "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", "cpu": [ "arm64" ], @@ -451,9 +485,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", - "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", + "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", "cpu": [ "x64" ], @@ -468,9 +502,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", - "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", + "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", "cpu": [ "arm64" ], @@ -485,9 +519,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", - "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", + "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", "cpu": [ "x64" ], @@ -502,9 +536,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", - "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", + "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", "cpu": [ "arm" ], @@ -519,9 +553,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", - "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", + "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", "cpu": [ "arm64" ], @@ -536,9 +570,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", - "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", + "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", "cpu": [ "ia32" ], @@ -553,9 +587,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", - "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", + "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", "cpu": [ "loong64" ], @@ -570,9 +604,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", - "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", + "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", "cpu": [ "mips64el" ], @@ -587,9 +621,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", - "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", + "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", "cpu": [ "ppc64" ], @@ -604,9 +638,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", - "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", + "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", "cpu": [ "riscv64" ], @@ -621,9 +655,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", - "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", + "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", "cpu": [ "s390x" ], @@ -638,9 +672,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", - "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", + "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", "cpu": [ "x64" ], @@ -655,9 +689,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", - "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", + "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", "cpu": [ "arm64" ], @@ -672,9 +706,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", - "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", + "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", "cpu": [ "x64" ], @@ -689,9 +723,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", - "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", + "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", "cpu": [ "arm64" ], @@ -706,9 +740,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", - "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", + "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", "cpu": [ "x64" ], @@ -723,9 +757,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", - "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", + "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", "cpu": [ "x64" ], @@ -740,9 +774,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", - "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", + "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", "cpu": [ "arm64" ], @@ -757,9 +791,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", - "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", + "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", "cpu": [ "ia32" ], @@ -774,9 +808,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", - "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", + "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", "cpu": [ "x64" ], @@ -790,6 +824,80 @@ "node": ">=18" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -797,10 +905,119 @@ "dev": true, "license": "MIT" }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@microsoft/api-extractor": { + "version": "7.50.1", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.50.1.tgz", + "integrity": "sha512-L18vz0ARLNaBLKwWe0DdEf7eijDsb7ERZspgZK7PxclLoQrc+9hJZo8y4OVfCHxNVyxlwVywY2WdE/3pOFViLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/api-extractor-model": "7.30.3", + "@microsoft/tsdoc": "~0.15.1", + "@microsoft/tsdoc-config": "~0.17.1", + "@rushstack/node-core-library": "5.11.0", + "@rushstack/rig-package": "0.5.3", + "@rushstack/terminal": "0.15.0", + "@rushstack/ts-command-line": "4.23.5", + "lodash": "~4.17.15", + "minimatch": "~3.0.3", + "resolve": "~1.22.1", + "semver": "~7.5.4", + "source-map": "~0.6.1", + "typescript": "5.7.3" + }, + "bin": { + "api-extractor": "bin/api-extractor" + } + }, + "node_modules/@microsoft/api-extractor-model": { + "version": "7.30.3", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.30.3.tgz", + "integrity": "sha512-yEAvq0F78MmStXdqz9TTT4PZ05Xu5R8nqgwI5xmUmQjWBQ9E6R2n8HB/iZMRciG4rf9iwI2mtuQwIzDXBvHn1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "~0.15.1", + "@microsoft/tsdoc-config": "~0.17.1", + "@rushstack/node-core-library": "5.11.0" + } + }, + "node_modules/@microsoft/tsdoc": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz", + "integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@microsoft/tsdoc-config": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.17.1.tgz", + "integrity": "sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "0.15.1", + "ajv": "~8.12.0", + "jju": "~1.4.0", + "resolve": "~1.22.2" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz", - "integrity": "sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.35.0.tgz", + "integrity": "sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ==", "cpu": [ "arm" ], @@ -812,9 +1029,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.6.tgz", - "integrity": "sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.35.0.tgz", + "integrity": "sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA==", "cpu": [ "arm64" ], @@ -826,9 +1043,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.6.tgz", - "integrity": "sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.35.0.tgz", + "integrity": "sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q==", "cpu": [ "arm64" ], @@ -840,9 +1057,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.6.tgz", - "integrity": "sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.35.0.tgz", + "integrity": "sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q==", "cpu": [ "x64" ], @@ -854,9 +1071,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.6.tgz", - "integrity": "sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.35.0.tgz", + "integrity": "sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ==", "cpu": [ "arm64" ], @@ -868,9 +1085,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.6.tgz", - "integrity": "sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.35.0.tgz", + "integrity": "sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw==", "cpu": [ "x64" ], @@ -882,9 +1099,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.6.tgz", - "integrity": "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.35.0.tgz", + "integrity": "sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg==", "cpu": [ "arm" ], @@ -896,9 +1113,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.6.tgz", - "integrity": "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.35.0.tgz", + "integrity": "sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A==", "cpu": [ "arm" ], @@ -910,9 +1127,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.6.tgz", - "integrity": "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.35.0.tgz", + "integrity": "sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A==", "cpu": [ "arm64" ], @@ -924,9 +1141,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.6.tgz", - "integrity": "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.35.0.tgz", + "integrity": "sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg==", "cpu": [ "arm64" ], @@ -938,9 +1155,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.6.tgz", - "integrity": "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.35.0.tgz", + "integrity": "sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g==", "cpu": [ "loong64" ], @@ -952,9 +1169,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.6.tgz", - "integrity": "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.35.0.tgz", + "integrity": "sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA==", "cpu": [ "ppc64" ], @@ -966,9 +1183,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.6.tgz", - "integrity": "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.35.0.tgz", + "integrity": "sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g==", "cpu": [ "riscv64" ], @@ -980,9 +1197,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.6.tgz", - "integrity": "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.35.0.tgz", + "integrity": "sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw==", "cpu": [ "s390x" ], @@ -994,9 +1211,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.6.tgz", - "integrity": "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.35.0.tgz", + "integrity": "sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA==", "cpu": [ "x64" ], @@ -1008,9 +1225,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.6.tgz", - "integrity": "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.35.0.tgz", + "integrity": "sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg==", "cpu": [ "x64" ], @@ -1022,9 +1239,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.6.tgz", - "integrity": "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.35.0.tgz", + "integrity": "sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg==", "cpu": [ "arm64" ], @@ -1036,9 +1253,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.6.tgz", - "integrity": "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.35.0.tgz", + "integrity": "sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw==", "cpu": [ "ia32" ], @@ -1050,9 +1267,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.6.tgz", - "integrity": "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.35.0.tgz", + "integrity": "sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw==", "cpu": [ "x64" ], @@ -1063,6 +1280,98 @@ "win32" ] }, + "node_modules/@rushstack/node-core-library": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.11.0.tgz", + "integrity": "sha512-I8+VzG9A0F3nH2rLpPd7hF8F7l5Xb7D+ldrWVZYegXM6CsKkvWc670RlgK3WX8/AseZfXA/vVrh0bpXe2Y2UDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "~8.13.0", + "ajv-draft-04": "~1.0.0", + "ajv-formats": "~3.0.1", + "fs-extra": "~11.3.0", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.22.1", + "semver": "~7.5.4" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/node-core-library/node_modules/ajv": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@rushstack/rig-package": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.3.tgz", + "integrity": "sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "~1.22.1", + "strip-json-comments": "~3.1.1" + } + }, + "node_modules/@rushstack/terminal": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.15.0.tgz", + "integrity": "sha512-vXQPRQ+vJJn4GVqxkwRe+UGgzNxdV8xuJZY2zem46Y0p3tlahucH9/hPmLGj2i9dQnUBFiRnoM9/KW7PYw8F4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rushstack/node-core-library": "5.11.0", + "supports-color": "~8.1.1" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/ts-command-line": { + "version": "4.23.5", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.23.5.tgz", + "integrity": "sha512-jg70HfoK44KfSP3MTiL5rxsZH7X1ktX3cZs9Sl8eDu1/LxJSbPsh0MOFRC710lIuYYSgxWjI5AjbCBAl7u3RxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rushstack/terminal": "0.15.0", + "@types/argparse": "1.0.38", + "argparse": "~1.0.9", + "string-argv": "~0.3.1" + } + }, + "node_modules/@types/argparse": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", + "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -1070,6 +1379,49 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "22.13.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.5.tgz", + "integrity": "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.5.tgz", + "integrity": "sha512-zOOWIsj5fHh3jjGwQg+P+J1FW3s4jBu1Zqga0qW60yutsBtqEqNEJKWYh7cYn1yGD+1bdPsPdC/eL4eVK56xMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "debug": "^4.4.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.8.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "3.0.5", + "vitest": "3.0.5" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.5.tgz", @@ -1183,6 +1535,35 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@volar/language-core": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.11.tgz", + "integrity": "sha512-lN2C1+ByfW9/JRPpqScuZt/4OrUUse57GLI6TbLgTIqBVemdl1wNcZ1qYGEo2+Gw8coYLgCy7SuKqn6IrQcQgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.11" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.11.tgz", + "integrity": "sha512-ZQpmafIGvaZMn/8iuvCFGrW3smeqkq/IIh9F1SdSx9aUl0J4Iurzd6/FhmjNO5g2ejF3rT45dKskgXWiofqlZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.11.tgz", + "integrity": "sha512-2DT+Tdh88Spp5PyPbqhyoYavYCPDsqbHLFwcUI9K1NlY1YgUJvujGdrqUp0zWxnW7KWNTr3xSpMuv2WnaTKDAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.11", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, "node_modules/@vue/compiler-core": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", @@ -1251,6 +1632,17 @@ "@vue/shared": "3.5.13" } }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, "node_modules/@vue/devtools-api": { "version": "6.6.4", "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", @@ -1258,6 +1650,57 @@ "dev": true, "license": "MIT" }, + "node_modules/@vue/language-core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.0.tgz", + "integrity": "sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "~2.4.11", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^0.4.9", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@vue/language-core/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@vue/reactivity": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", @@ -1313,6 +1756,19 @@ "dev": true, "license": "MIT" }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", @@ -1323,24 +1779,142 @@ "node": ">= 14" } }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", "dev": true, - "license": "MIT" - }, - "node_modules/cac": { + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/alien-signals": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.4.14.tgz", + "integrity": "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "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==", @@ -1377,6 +1951,26 @@ "node": ">= 16" } }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "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/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1390,6 +1984,49 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "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==", + "dev": true, + "license": "MIT" + }, + "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" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/cssstyle": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.2.1.tgz", @@ -1425,6 +2062,13 @@ "node": ">=18" } }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -1470,6 +2114,20 @@ "node": ">=0.4.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -1491,9 +2149,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", - "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", + "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1504,31 +2162,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.24.2", - "@esbuild/android-arm": "0.24.2", - "@esbuild/android-arm64": "0.24.2", - "@esbuild/android-x64": "0.24.2", - "@esbuild/darwin-arm64": "0.24.2", - "@esbuild/darwin-x64": "0.24.2", - "@esbuild/freebsd-arm64": "0.24.2", - "@esbuild/freebsd-x64": "0.24.2", - "@esbuild/linux-arm": "0.24.2", - "@esbuild/linux-arm64": "0.24.2", - "@esbuild/linux-ia32": "0.24.2", - "@esbuild/linux-loong64": "0.24.2", - "@esbuild/linux-mips64el": "0.24.2", - "@esbuild/linux-ppc64": "0.24.2", - "@esbuild/linux-riscv64": "0.24.2", - "@esbuild/linux-s390x": "0.24.2", - "@esbuild/linux-x64": "0.24.2", - "@esbuild/netbsd-arm64": "0.24.2", - "@esbuild/netbsd-x64": "0.24.2", - "@esbuild/openbsd-arm64": "0.24.2", - "@esbuild/openbsd-x64": "0.24.2", - "@esbuild/sunos-x64": "0.24.2", - "@esbuild/win32-arm64": "0.24.2", - "@esbuild/win32-ia32": "0.24.2", - "@esbuild/win32-x64": "0.24.2" + "@esbuild/aix-ppc64": "0.25.1", + "@esbuild/android-arm": "0.25.1", + "@esbuild/android-arm64": "0.25.1", + "@esbuild/android-x64": "0.25.1", + "@esbuild/darwin-arm64": "0.25.1", + "@esbuild/darwin-x64": "0.25.1", + "@esbuild/freebsd-arm64": "0.25.1", + "@esbuild/freebsd-x64": "0.25.1", + "@esbuild/linux-arm": "0.25.1", + "@esbuild/linux-arm64": "0.25.1", + "@esbuild/linux-ia32": "0.25.1", + "@esbuild/linux-loong64": "0.25.1", + "@esbuild/linux-mips64el": "0.25.1", + "@esbuild/linux-ppc64": "0.25.1", + "@esbuild/linux-riscv64": "0.25.1", + "@esbuild/linux-s390x": "0.25.1", + "@esbuild/linux-x64": "0.25.1", + "@esbuild/netbsd-arm64": "0.25.1", + "@esbuild/netbsd-x64": "0.25.1", + "@esbuild/openbsd-arm64": "0.25.1", + "@esbuild/openbsd-x64": "0.25.1", + "@esbuild/sunos-x64": "0.25.1", + "@esbuild/win32-arm64": "0.25.1", + "@esbuild/win32-ia32": "0.25.1", + "@esbuild/win32-x64": "0.25.1" } }, "node_modules/estree-walker": { @@ -1551,6 +2209,13 @@ "node": ">=12.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==", + "dev": true, + "license": "MIT" + }, "node_modules/flush-promises": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/flush-promises/-/flush-promises-1.0.2.tgz", @@ -1558,6 +2223,23 @@ "dev": true, "license": "MIT" }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", @@ -1573,6 +2255,21 @@ "node": ">= 6" } }, + "node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1588,6 +2285,103 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "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", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", @@ -1601,6 +2395,13 @@ "node": ">=18" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -1642,70 +2443,266 @@ "node": ">=0.10.0" } }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/jsdom": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.0.0.tgz", - "integrity": "sha512-BZYDGVAIriBWTpIxYzrXjv3E/4u8+/pSG5bQdIYCbNCGOvsPkDQfTVLAIXAf9ETdCpduCVTkDe2NNZ8NIwUVzw==", + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", "dependencies": { - "cssstyle": "^4.2.1", - "data-urls": "^5.0.0", - "decimal.js": "^10.4.3", - "form-data": "^4.0.1", - "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.6", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.16", - "parse5": "^7.2.1", - "rrweb-cssom": "^0.8.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^5.0.0", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.1.0", - "ws": "^8.18.0", - "xml-name-validator": "^5.0.0" + "hasown": "^2.0.2" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "canvas": "^3.0.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/loupe": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", - "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true, "license": "MIT" }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, - "node_modules/magic-string": { - "version": "0.30.17", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.0.0.tgz", + "integrity": "sha512-BZYDGVAIriBWTpIxYzrXjv3E/4u8+/pSG5bQdIYCbNCGOvsPkDQfTVLAIXAf9ETdCpduCVTkDe2NNZ8NIwUVzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.1", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true, + "license": "MIT" + }, + "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": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, @@ -1714,6 +2711,34 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -1737,6 +2762,42 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mlly": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", + "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "pathe": "^2.0.1", + "pkg-types": "^1.3.0", + "ufo": "^1.5.4" + } + }, "node_modules/mockdate": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/mockdate/-/mockdate-3.0.5.tgz", @@ -1751,6 +2812,13 @@ "dev": true, "license": "MIT" }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", @@ -1777,6 +2845,13 @@ "dev": true, "license": "MIT" }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parse5": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", @@ -1790,6 +2865,47 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/pathe": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", @@ -1797,165 +2913,530 @@ "dev": true, "license": "MIT" }, - "node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "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": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "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.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rollup": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.35.0.tgz", + "integrity": "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.35.0", + "@rollup/rollup-android-arm64": "4.35.0", + "@rollup/rollup-darwin-arm64": "4.35.0", + "@rollup/rollup-darwin-x64": "4.35.0", + "@rollup/rollup-freebsd-arm64": "4.35.0", + "@rollup/rollup-freebsd-x64": "4.35.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.35.0", + "@rollup/rollup-linux-arm-musleabihf": "4.35.0", + "@rollup/rollup-linux-arm64-gnu": "4.35.0", + "@rollup/rollup-linux-arm64-musl": "4.35.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.35.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0", + "@rollup/rollup-linux-riscv64-gnu": "4.35.0", + "@rollup/rollup-linux-s390x-gnu": "4.35.0", + "@rollup/rollup-linux-x64-gnu": "4.35.0", + "@rollup/rollup-linux-x64-musl": "4.35.0", + "@rollup/rollup-win32-arm64-msvc": "4.35.0", + "@rollup/rollup-win32-ia32-msvc": "4.35.0", + "@rollup/rollup-win32-x64-msvc": "4.35.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "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/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "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": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { - "node": ">= 14.16" + "node": ">=8" } }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } }, - "node_modules/postcss": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", - "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "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.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "ansi-regex": "^5.0.1" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=8" } }, - "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/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/rollup": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.6.tgz", - "integrity": "sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ==", + "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==", "dev": true, "license": "MIT", - "dependencies": { - "@types/estree": "1.0.6" + "engines": { + "node": ">=8" }, - "bin": { - "rollup": "dist/bin/rollup" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": ">=10" }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.34.6", - "@rollup/rollup-android-arm64": "4.34.6", - "@rollup/rollup-darwin-arm64": "4.34.6", - "@rollup/rollup-darwin-x64": "4.34.6", - "@rollup/rollup-freebsd-arm64": "4.34.6", - "@rollup/rollup-freebsd-x64": "4.34.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.34.6", - "@rollup/rollup-linux-arm-musleabihf": "4.34.6", - "@rollup/rollup-linux-arm64-gnu": "4.34.6", - "@rollup/rollup-linux-arm64-musl": "4.34.6", - "@rollup/rollup-linux-loongarch64-gnu": "4.34.6", - "@rollup/rollup-linux-powerpc64le-gnu": "4.34.6", - "@rollup/rollup-linux-riscv64-gnu": "4.34.6", - "@rollup/rollup-linux-s390x-gnu": "4.34.6", - "@rollup/rollup-linux-x64-gnu": "4.34.6", - "@rollup/rollup-linux-x64-musl": "4.34.6", - "@rollup/rollup-win32-arm64-msvc": "4.34.6", - "@rollup/rollup-win32-ia32-msvc": "4.34.6", - "@rollup/rollup-win32-x64-msvc": "4.34.6", - "fsevents": "~2.3.2" + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true, "license": "MIT" }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "node_modules/terser": { + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", + "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", "dev": true, - "license": "ISC", + "license": "BSD-2-Clause", "dependencies": { - "xmlchars": "^2.2.0" + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" }, "engines": { - "node": ">=v12.22.7" + "node": ">=10" } }, - "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/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==", + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "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.8.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", - "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "node_modules/test-exclude/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "MIT" + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/tinybench": { "version": "2.9.0", @@ -2047,15 +3528,63 @@ "node": ">=18" } }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "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/vite": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.0.tgz", - "integrity": "sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.3.tgz", + "integrity": "sha512-IzwM54g4y9JA/xAeBPNaDXiBF8Jsgl3VBQ2YQ/wOY6fyW3xMdSoltIV3Bo59DErdqdE6RxUfv8W69DvUorE4Eg==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.24.2", - "postcss": "^8.5.1", + "esbuild": "^0.25.0", + "postcss": "^8.5.3", "rollup": "^4.30.1" }, "bin": { @@ -2142,6 +3671,33 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/vite-plugin-dts": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/vite-plugin-dts/-/vite-plugin-dts-4.5.0.tgz", + "integrity": "sha512-M1lrPTdi7gilLYRZoLmGYnl4fbPryVYsehPN9JgaxjJKTs8/f7tuAlvCCvOLB5gRDQTTKnptBcB0ACsaw2wNLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/api-extractor": "^7.49.1", + "@rollup/pluginutils": "^5.1.4", + "@volar/typescript": "^2.4.11", + "@vue/language-core": "2.2.0", + "compare-versions": "^6.1.1", + "debug": "^4.4.0", + "kolorist": "^1.8.0", + "local-pkg": "^0.5.1", + "magic-string": "^0.30.17" + }, + "peerDependencies": { + "typescript": "*", + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, "node_modules/vitest": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.5.tgz", @@ -2212,6 +3768,13 @@ } } }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, "node_modules/vue": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", @@ -2310,6 +3873,22 @@ "node": ">=18" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "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", @@ -2327,6 +3906,104 @@ "node": ">=8" } }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/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" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", @@ -2365,6 +4042,13 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true, "license": "MIT" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" } } } diff --git a/package.json b/package.json index 0c4233d9..26b150c2 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vue-gtag", "description": "Global Site Tag (gtag.js) plugin for Vue", "type": "module", - "version": "2.1.2", + "version": "3.0.0-rc.0", "author": { "name": "Matteo Gabriele", "email": "m.gabriele.dev@gmail.com" @@ -15,9 +15,11 @@ "scripts": { "dev": "vite build --watch", "fix": "biome check --write", - "build": "vite build", - "lint": "biome check", + "lint": "biome lint", + "build": "tsc --noEmit && vite build", + "typecheck": "tsc --noEmit", "test": "vitest", + "test:cov": "vitest run --coverage", "test:once": "vitest run", "prepublishOnly": "npm run lint && npm run test:once && npm run build" }, @@ -34,30 +36,43 @@ "vue", "vuejs" ], - "main": "./dist/vue-gtag.cjs.js", - "module": "./dist/vue-gtag.esm.js", - "unpkg": "./dist/vue-gtag.umd.js", - "jsdelivr": "./dist/vue-gtag.umd.js", - "types": "vue-gtag.d.ts", + "exports": { + ".": { + "types": "./dist/vue-gtag.d.ts", + "import": "./dist/vue-gtag.js" + } + }, + "main": "./dist/vue-gtag.js", + "module": "./dist/vue-gtag.js", + "types": "./dist/vue-gtag.d.ts", "files": [ - "dist", - "vue-gtag.d.ts" + "dist" ], "bugs": { "url": "https://github.com/MatteoGabriele/vue-gtag/issues" }, "homepage": "https://github.com/MatteoGabriele/vue-gtag#readme", "peerDependencies": { - "vue": "^3.0.0" + "vue": "^3.5.13", + "vue-router": "^4.5.0" }, "devDependencies": { "@biomejs/biome": "1.9.4", + "@types/node": "^22.13.5", + "@vitest/coverage-v8": "^3.0.5", "flush-promises": "^1.0.2", "jsdom": "^26.0.0", "mockdate": "^3.0.5", + "terser": "^5.39.0", "vite": "^6.1.0", + "vite-plugin-dts": "^4.5.0", "vitest": "^3.0.5", - "vue": "^3.0.0", + "vue": "^3.5.13", "vue-router": "^4.5.0" + }, + "peerDependenciesMeta": { + "vue-router": { + "optional": true + } } } diff --git a/src/add-configuration.js b/src/add-configuration.js deleted file mode 100644 index d22a32d7..00000000 --- a/src/add-configuration.js +++ /dev/null @@ -1,19 +0,0 @@ -import * as api from "@/api"; -import { getOptions } from "@/options"; - -const mergeDefaultParams = (params) => ({ - send_page_view: false, - ...params, -}); - -export default () => { - const { config, includes } = getOptions(); - - api.query("config", config.id, mergeDefaultParams(config.params)); - - if (Array.isArray(includes)) { - for (const domain of includes) { - api.query("config", domain.id, mergeDefaultParams(domain.params)); - } - } -}; diff --git a/src/add-routes-tracker.js b/src/add-routes-tracker.js deleted file mode 100644 index 75579ac6..00000000 --- a/src/add-routes-tracker.js +++ /dev/null @@ -1,48 +0,0 @@ -import addConfiguration from "@/add-configuration"; -import { getOptions } from "@/options"; -import { getRouter } from "@/router"; -import track from "@/track"; -import { isFn } from "@/utils"; -import { nextTick } from "vue"; - -const isRouteExcluded = (route) => { - const { pageTrackerExcludedRoutes: routes } = getOptions(); - return routes.includes(route.path) || routes.includes(route.name); -}; - -export default () => { - const { onBeforeTrack, onAfterTrack } = getOptions(); - const router = getRouter(); - - router.isReady().then(() => { - nextTick().then(() => { - const { currentRoute } = router; - - addConfiguration(); - - if (isRouteExcluded(currentRoute.value)) { - return; - } - - track(currentRoute.value); - }); - - router.afterEach((to, from) => { - nextTick().then(() => { - if (isRouteExcluded(to)) { - return; - } - - if (isFn(onBeforeTrack)) { - onBeforeTrack(to, from); - } - - track(to, from); - - if (isFn(onAfterTrack)) { - onAfterTrack(to, from); - } - }); - }); - }); -}; diff --git a/src/api/config.js b/src/api/config.js deleted file mode 100644 index ad825578..00000000 --- a/src/api/config.js +++ /dev/null @@ -1,14 +0,0 @@ -import query from "@/api/query"; -import { getOptions } from "@/options"; - -export default (...args) => { - const { config, includes } = getOptions(); - - query("config", config.id, ...args); - - if (Array.isArray(includes)) { - for (const domain of includes) { - query("config", domain.id, ...args); - } - } -}; diff --git a/src/api/config/config.test.ts b/src/api/config/config.test.ts new file mode 100644 index 00000000..3b64d709 --- /dev/null +++ b/src/api/config/config.test.ts @@ -0,0 +1,40 @@ +import { query } from "@/api/query"; +import { updateSettings } from "@/core/settings"; +import { config } from "./config"; + +vi.mock("@/api/query"); + +describe("config", () => { + it("should fire the config event", () => { + updateSettings({ + tagId: "UA-1", + }); + + config({ send_page_view: false }); + + expect(query).toHaveBeenCalledWith("config", "UA-1", { + send_page_view: false, + }); + }); + + it("should fire multiple config event", () => { + updateSettings({ + tagId: "UA-1", + additionalAccounts: [ + { + tagId: "UA-2", + }, + ], + }); + + config({ screen_name: "app" }); + + expect(query).toHaveBeenNthCalledWith(1, "config", "UA-1", { + screen_name: "app", + }); + + expect(query).toHaveBeenNthCalledWith(2, "config", "UA-2", { + screen_name: "app", + }); + }); +}); diff --git a/src/api/config/config.ts b/src/api/config/config.ts new file mode 100644 index 00000000..b623dcd6 --- /dev/null +++ b/src/api/config/config.ts @@ -0,0 +1,21 @@ +import { query } from "@/api/query"; +import { getSettings } from "@/core/settings"; +import type { GtagConfig } from "@/types/gtag"; + +export function config(params: GtagConfig) { + const { tagId, additionalAccounts } = getSettings(); + + if (!tagId) { + return; + } + + query("config", tagId, params); + + if (!additionalAccounts) { + return; + } + + for (const account of additionalAccounts) { + query("config", account.tagId, params); + } +} diff --git a/src/api/config/index.ts b/src/api/config/index.ts new file mode 100644 index 00000000..5c62e04f --- /dev/null +++ b/src/api/config/index.ts @@ -0,0 +1 @@ +export * from "./config"; diff --git a/src/api/consent/consent.test.ts b/src/api/consent/consent.test.ts new file mode 100644 index 00000000..3427113f --- /dev/null +++ b/src/api/consent/consent.test.ts @@ -0,0 +1,62 @@ +import { query } from "@/api/query"; +import { consent, consentDeniedAll, consentGrantedAll } from "./consent"; + +vi.mock("@/api/query"); + +describe("consent", () => { + it("should use the consent event without default values", () => { + consent("update", { + ad_user_data: "granted", + wait_for_update: 1000, + }); + + expect(query).toHaveBeenCalledWith("consent", "update", { + ad_user_data: "granted", + wait_for_update: 1000, + }); + }); + + it("should use the consent default and set all to granted", () => { + consentGrantedAll(); + + expect(query).toHaveBeenCalledWith("consent", "default", { + ad_user_data: "granted", + ad_personalization: "granted", + ad_storage: "granted", + analytics_storage: "granted", + }); + }); + + it("should use the consent update and set all to granted", () => { + consentGrantedAll("update"); + + expect(query).toHaveBeenCalledWith("consent", "update", { + ad_user_data: "granted", + ad_personalization: "granted", + ad_storage: "granted", + analytics_storage: "granted", + }); + }); + + it("should use the consent default and set all to denied", () => { + consentDeniedAll(); + + expect(query).toHaveBeenCalledWith("consent", "default", { + ad_user_data: "denied", + ad_personalization: "denied", + ad_storage: "denied", + analytics_storage: "denied", + }); + }); + + it("should use the consent update and set all to denied", () => { + consentDeniedAll("update"); + + expect(query).toHaveBeenCalledWith("consent", "update", { + ad_user_data: "denied", + ad_personalization: "denied", + ad_storage: "denied", + analytics_storage: "denied", + }); + }); +}); diff --git a/src/api/consent/consent.ts b/src/api/consent/consent.ts new file mode 100644 index 00000000..364a7ca3 --- /dev/null +++ b/src/api/consent/consent.ts @@ -0,0 +1,24 @@ +import { query } from "@/api/query"; +import type { GtagConsentArg, GtagConsentParams } from "@/types/gtag"; + +export function consent(consentArg: GtagConsentArg, params: GtagConsentParams) { + query("consent", consentArg, params); +} + +export function consentGrantedAll(mode: GtagConsentArg = "default") { + consent(mode, { + ad_user_data: "granted", + ad_personalization: "granted", + ad_storage: "granted", + analytics_storage: "granted", + }); +} + +export function consentDeniedAll(mode: GtagConsentArg = "default") { + consent(mode, { + ad_user_data: "denied", + ad_personalization: "denied", + ad_storage: "denied", + analytics_storage: "denied", + }); +} diff --git a/src/api/consent/index.ts b/src/api/consent/index.ts new file mode 100644 index 00000000..f908b31d --- /dev/null +++ b/src/api/consent/index.ts @@ -0,0 +1 @@ +export * from "./consent"; diff --git a/src/api/custom-map.js b/src/api/custom-map.js deleted file mode 100644 index 5c11f0cf..00000000 --- a/src/api/custom-map.js +++ /dev/null @@ -1,7 +0,0 @@ -import config from "@/api/config"; - -export default (map) => { - config({ - custom_map: map, - }); -}; diff --git a/src/api/custom-map/custom-map.test.ts b/src/api/custom-map/custom-map.test.ts new file mode 100644 index 00000000..74fff0ff --- /dev/null +++ b/src/api/custom-map/custom-map.test.ts @@ -0,0 +1,23 @@ +import { query } from "@/api/query"; +import { resetSettings, updateSettings } from "@/core/settings"; +import { customMap } from "./custom-map"; + +vi.mock("@/api/query"); + +describe("custom-map", () => { + beforeEach(resetSettings); + + it("should use the custom_map configuration", () => { + updateSettings({ + tagId: "UA-12345678", + }); + + customMap({ dimension1: "my_username" }); + + expect(query).toHaveBeenCalledWith("config", "UA-12345678", { + custom_map: { + dimension1: "my_username", + }, + }); + }); +}); diff --git a/src/api/custom-map/custom-map.ts b/src/api/custom-map/custom-map.ts new file mode 100644 index 00000000..e5962d25 --- /dev/null +++ b/src/api/custom-map/custom-map.ts @@ -0,0 +1,8 @@ +import { config } from "@/api/config"; +import type { GtagCustomParams } from "@/types/gtag"; + +export function customMap(params: GtagCustomParams) { + config({ + custom_map: params, + }); +} diff --git a/src/api/custom-map/index.ts b/src/api/custom-map/index.ts new file mode 100644 index 00000000..3c5ce318 --- /dev/null +++ b/src/api/custom-map/index.ts @@ -0,0 +1 @@ +export * from "./custom-map"; diff --git a/src/api/disable.js b/src/api/disable.js deleted file mode 100644 index f5563388..00000000 --- a/src/api/disable.js +++ /dev/null @@ -1,22 +0,0 @@ -import { getOptions } from "@/options"; -import { isBrowser } from "@/utils"; - -const assignGlobalProperty = (id, value) => { - if (!isBrowser()) { - return; - } - - window[`ga-disable-${id}`] = value; -}; - -export default (value = true) => { - const { config, includes } = getOptions(); - - assignGlobalProperty(config.id, value); - - if (Array.isArray(includes)) { - for (const domain of includes) { - assignGlobalProperty(domain.id, value); - } - } -}; diff --git a/src/api/ecommerce/ecommerce.test.ts b/src/api/ecommerce/ecommerce.test.ts new file mode 100644 index 00000000..3220c5b3 --- /dev/null +++ b/src/api/ecommerce/ecommerce.test.ts @@ -0,0 +1,16 @@ +import { query } from "@/api/query"; +import { ecommerce } from "./ecommerce"; + +vi.mock("@/api/query"); + +describe("ecommerce", () => { + it("should use the ecommerce event", () => { + ecommerce("refund", { + transaction_id: "transaction_id_value", + }); + + expect(query).toHaveBeenCalledWith("event", "refund", { + transaction_id: "transaction_id_value", + }); + }); +}); diff --git a/src/api/ecommerce/ecommerce.ts b/src/api/ecommerce/ecommerce.ts new file mode 100644 index 00000000..9a2ae904 --- /dev/null +++ b/src/api/ecommerce/ecommerce.ts @@ -0,0 +1,12 @@ +import { event } from "@/api/event"; +import type { + GtagEcommerceEventNames, + GtagEcommerceParams, +} from "@/types/gtag"; + +export function ecommerce( + name: GtagEcommerceEventNames, + params: GtagEcommerceParams, +) { + event(name, params); +} diff --git a/src/api/ecommerce/index.ts b/src/api/ecommerce/index.ts new file mode 100644 index 00000000..d4dcb19b --- /dev/null +++ b/src/api/ecommerce/index.ts @@ -0,0 +1 @@ +export * from "./ecommerce"; diff --git a/src/api/event.js b/src/api/event.js deleted file mode 100644 index 574b5875..00000000 --- a/src/api/event.js +++ /dev/null @@ -1,14 +0,0 @@ -import query from "@/api/query"; -import { getOptions } from "@/options"; - -export default (name, params = {}) => { - const { includes, defaultGroupName } = getOptions(); - - if (params.send_to == null && Array.isArray(includes) && includes.length) { - params.send_to = includes - .map((domain) => domain.id) - .concat(defaultGroupName); - } - - query("event", name, params); -}; diff --git a/src/api/event/event.test.ts b/src/api/event/event.test.ts new file mode 100644 index 00000000..caeaec5a --- /dev/null +++ b/src/api/event/event.test.ts @@ -0,0 +1,58 @@ +import { query } from "@/api/query"; +import { updateSettings } from "@/core/settings"; +import { event } from "./event"; + +vi.mock("../query"); + +describe("event", () => { + it("should query the event command", () => { + event("screen_view", { screen_name: "about" }); + + expect(query).toHaveBeenCalledWith("event", "screen_view", { + screen_name: "about", + }); + }); + + it("should add a group name", () => { + updateSettings({ + additionalAccounts: [{ tagId: "UA-1" }, { tagId: "UA-2" }], + }); + + event("screen_view", { screen_name: "about" }); + + expect(query).toHaveBeenCalledWith("event", "screen_view", { + screen_name: "about", + send_to: "default", + }); + }); + + it("should use a custom group name", () => { + updateSettings({ + additionalAccounts: [{ tagId: "UA-1" }, { tagId: "UA-2" }], + groupName: "custom_group_name", + }); + + event("screen_view", { screen_name: "about" }); + + expect(query).toHaveBeenCalledWith("event", "screen_view", { + screen_name: "about", + send_to: "custom_group_name", + }); + }); + + it("should use the send_to property if already set", () => { + updateSettings({ + additionalAccounts: [{ tagId: "UA-1" }, { tagId: "UA-2" }], + }); + + event("screen_view", { + screen_name: "about", + send_to: "my_custom_send_to", + }); + + expect(query).toHaveBeenCalledWith("event", "screen_view", { + screen_name: "about", + send_to: "my_custom_send_to", + }); + }); +}); diff --git a/src/api/event/event.ts b/src/api/event/event.ts new file mode 100644 index 00000000..83290d41 --- /dev/null +++ b/src/api/event/event.ts @@ -0,0 +1,20 @@ +import { query } from "@/api/query"; +import { getSettings } from "@/core/settings"; +import type { + GtagControlParams, + GtagCustomParams, + GtagEventNames, + GtagEventParams, +} from "@/types/gtag"; + +type EventParams = GtagControlParams & GtagEventParams & GtagCustomParams; + +export function event(name: GtagEventNames, params: EventParams) { + const { groupName, additionalAccounts } = getSettings(); + + if (params.send_to === undefined && additionalAccounts?.length) { + params.send_to = groupName; + } + + query("event", name, params); +} diff --git a/src/api/event/index.ts b/src/api/event/index.ts new file mode 100644 index 00000000..6bcf8f16 --- /dev/null +++ b/src/api/event/index.ts @@ -0,0 +1 @@ +export * from "./event"; diff --git a/src/api/exception.js b/src/api/exception.js deleted file mode 100644 index 85d36d09..00000000 --- a/src/api/exception.js +++ /dev/null @@ -1,5 +0,0 @@ -import event from "@/api/event"; - -export default (...args) => { - event("exception", ...args); -}; diff --git a/src/api/exception/exception.test.ts b/src/api/exception/exception.test.ts new file mode 100644 index 00000000..988b2179 --- /dev/null +++ b/src/api/exception/exception.test.ts @@ -0,0 +1,18 @@ +import { query } from "@/api/query"; +import { exception } from "./exception"; + +vi.mock("../query"); + +describe("exception", () => { + it("should use the exception event", () => { + exception({ + description: "error_description", + fatal: false, + }); + + expect(query).toHaveBeenCalledWith("event", "exception", { + description: "error_description", + fatal: false, + }); + }); +}); diff --git a/src/api/exception/exception.ts b/src/api/exception/exception.ts new file mode 100644 index 00000000..6f1a8003 --- /dev/null +++ b/src/api/exception/exception.ts @@ -0,0 +1,13 @@ +import { event } from "@/api/event"; +import type { GtagCustomParams } from "@/types/gtag"; + +type ExceptionParams = + | { + description?: string; + fatal?: boolean; + } + | GtagCustomParams; + +export function exception(params: ExceptionParams) { + event("exception", params); +} diff --git a/src/api/exception/index.ts b/src/api/exception/index.ts new file mode 100644 index 00000000..95d53087 --- /dev/null +++ b/src/api/exception/index.ts @@ -0,0 +1 @@ +export * from "./exception"; diff --git a/src/api/index.js b/src/api/index.js deleted file mode 100644 index 211cd1ad..00000000 --- a/src/api/index.js +++ /dev/null @@ -1,14 +0,0 @@ -export { default as query } from "@/api/query"; -export { default as config } from "@/api/config"; -export { default as optOut } from "@/api/opt-out"; -export { default as optIn } from "@/api/opt-in"; -export { default as pageview } from "@/api/pageview"; -export { default as screenview } from "@/api/screenview"; -export { default as exception } from "@/api/exception"; -export { default as linker } from "@/api/linker"; -export { default as time } from "@/api/time"; -export { default as set } from "@/api/set"; -export { default as refund } from "@/api/refund"; -export { default as purchase } from "@/api/purchase"; -export { default as customMap } from "@/api/custom-map"; -export { default as event } from "@/api/event"; diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 00000000..d9e0990c --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,13 @@ +export * from "./config"; +export * from "./consent"; +export * from "./custom-map"; +export * from "./ecommerce"; +export * from "./event"; +export * from "./exception"; +export * from "./linker"; +export * from "./opt"; +export * from "./pageview"; +export * from "./query"; +export * from "./screenview"; +export * from "./set"; +export * from "./time"; diff --git a/src/api/linker.js b/src/api/linker.js deleted file mode 100644 index 4fdde2b0..00000000 --- a/src/api/linker.js +++ /dev/null @@ -1,5 +0,0 @@ -import config from "./config"; - -export default (params) => { - config("linker", params); -}; diff --git a/src/api/linker/index.ts b/src/api/linker/index.ts new file mode 100644 index 00000000..0f3fcde5 --- /dev/null +++ b/src/api/linker/index.ts @@ -0,0 +1 @@ +export * from "./linker"; diff --git a/src/api/linker/linker.test.ts b/src/api/linker/linker.test.ts new file mode 100644 index 00000000..c28a750a --- /dev/null +++ b/src/api/linker/linker.test.ts @@ -0,0 +1,16 @@ +import { query } from "@/api/query"; +import { linker } from "./linker"; + +vi.mock("@/api/query"); + +describe("linker", () => { + it("should use the linker event", () => { + linker({ + domains: ["domain1.com", "domain2.com"], + }); + + expect(query).toHaveBeenCalledWith("set", "linker", { + domains: ["domain1.com", "domain2.com"], + }); + }); +}); diff --git a/src/api/linker/linker.ts b/src/api/linker/linker.ts new file mode 100644 index 00000000..98b40916 --- /dev/null +++ b/src/api/linker/linker.ts @@ -0,0 +1,12 @@ +import { query } from "@/api/query"; + +export type LinkerParams = { + accept_incoming?: boolean; + decorate_forms?: boolean; + domains: string[]; + url_position?: "query" | "fragment"; +}; + +export function linker(params: LinkerParams) { + query("set", "linker", params); +} diff --git a/src/api/opt-in.js b/src/api/opt-in.js deleted file mode 100644 index 1a4899bf..00000000 --- a/src/api/opt-in.js +++ /dev/null @@ -1,5 +0,0 @@ -import disable from "@/api/disable"; - -export default () => { - disable(false); -}; diff --git a/src/api/opt-out.js b/src/api/opt-out.js deleted file mode 100644 index 5d860947..00000000 --- a/src/api/opt-out.js +++ /dev/null @@ -1,5 +0,0 @@ -import disable from "@/api/disable"; - -export default () => { - disable(true); -}; diff --git a/src/api/opt/index.ts b/src/api/opt/index.ts new file mode 100644 index 00000000..b3419aa2 --- /dev/null +++ b/src/api/opt/index.ts @@ -0,0 +1 @@ +export * from "./opt"; diff --git a/src/api/opt/opt.test.ts b/src/api/opt/opt.test.ts new file mode 100644 index 00000000..599ca1e3 --- /dev/null +++ b/src/api/opt/opt.test.ts @@ -0,0 +1,74 @@ +import { resetSettings, updateSettings } from "@/core/settings"; +import { optIn, optOut } from "./opt"; + +describe("opt", () => { + beforeEach(() => { + resetSettings(); + window["ga-disable-UA-1"] = undefined; + window["ga-disable-UA-2"] = undefined; + }); + + it("should opt out", () => { + updateSettings({ + tagId: "UA-1", + }); + + optOut(); + + expect(window["ga-disable-UA-1"]).toBe(true); + }); + + it("should opt in", () => { + updateSettings({ + tagId: "UA-1", + }); + + optOut(); + optIn(); + + expect(window["ga-disable-UA-1"]).toBeUndefined(); + }); + + describe("custom tagId", () => { + it("should opt out", () => { + optOut("UA-1"); + + expect(window["ga-disable-UA-1"]).toBe(true); + }); + + it("should opt in", () => { + optOut("UA-1"); + optIn("UA-1"); + + expect(window["ga-disable-UA-1"]).toBeUndefined(); + }); + }); + + describe("use additional account", () => { + beforeEach(() => { + updateSettings({ + tagId: "UA-1", + additionalAccounts: [ + { + tagId: "UA-2", + }, + ], + }); + }); + + it("should opt out", () => { + optOut(); + + expect(window["ga-disable-UA-1"]).toBe(true); + expect(window["ga-disable-UA-2"]).toBe(true); + }); + + it("should opt in", () => { + optOut(); + optIn(); + + expect(window["ga-disable-UA-1"]).toBeUndefined(); + expect(window["ga-disable-UA-2"]).toBeUndefined(); + }); + }); +}); diff --git a/src/api/opt/opt.ts b/src/api/opt/opt.ts new file mode 100644 index 00000000..aac70676 --- /dev/null +++ b/src/api/opt/opt.ts @@ -0,0 +1,48 @@ +import { type TagId, getSettings } from "@/core/settings"; +import { isServer } from "@/utils"; + +function updateProperty(propertyName: string, value: T) { + if (value) { + window[propertyName] = value; + } else { + delete window[propertyName]; + } +} + +function assignProperty(tagId?: TagId, value?: boolean) { + const { tagId: settingsTagId, additionalAccounts } = getSettings(); + + if (isServer()) { + return; + } + + updateProperty(`ga-disable-${tagId ?? settingsTagId}`, value); + + if (!additionalAccounts?.length || tagId) { + return; + } + + for (const account of additionalAccounts) { + updateProperty(`ga-disable-${account.tagId}`, value); + } +} + +/** + * Disable tracking + + * By default uses the provided tagId and all additional accounts in the plugin configuration. + * If a tagId is provided, it will only disable that account. + */ +export function optOut(tagId?: TagId) { + assignProperty(tagId, true); +} + +/** + * Enable tracking + * + * By default uses the provided tagId and all additional accounts in the plugin configuration. + * If a tagId is provided, it will only enable that account. + */ +export function optIn(tagId?: TagId) { + assignProperty(tagId, undefined); +} diff --git a/src/api/pageview.js b/src/api/pageview.js deleted file mode 100644 index a9bf4f7d..00000000 --- a/src/api/pageview.js +++ /dev/null @@ -1,43 +0,0 @@ -import event from "@/api/event"; -import { getOptions } from "@/options"; -import { getRouter } from "@/router"; -import { getPathWithBase, isBrowser } from "@/utils"; - -export default (param) => { - if (!isBrowser()) { - return; - } - - let template; - - if (typeof param === "string") { - template = { - page_path: param, - }; - } else if (param.path || param.fullPath) { - const { - pageTrackerUseFullPath: useFullPath, - pageTrackerPrependBase: useBase, - } = getOptions(); - const router = getRouter(); - const base = router?.options.base; - const path = useFullPath ? param.fullPath : param.path; - - template = { - ...(param.name && { page_title: param.name }), - page_path: useBase ? getPathWithBase(path, base) : path, - }; - } else { - template = param; - } - - if (template.page_location == null) { - template.page_location = window.location.href; - } - - if (template.send_page_view == null) { - template.send_page_view = true; - } - - event("page_view", template); -}; diff --git a/src/api/pageview/index.ts b/src/api/pageview/index.ts new file mode 100644 index 00000000..7c4295e6 --- /dev/null +++ b/src/api/pageview/index.ts @@ -0,0 +1 @@ +export * from "./pageview"; diff --git a/src/api/pageview/pageview.test.ts b/src/api/pageview/pageview.test.ts new file mode 100644 index 00000000..c4a8ee90 --- /dev/null +++ b/src/api/pageview/pageview.test.ts @@ -0,0 +1,177 @@ +import { query } from "@/api/query"; +import { resetSettings, updateSettings } from "@/core/settings"; +import { type Router, createRouter, createWebHistory } from "vue-router"; +import { pageview } from "./pageview"; + +vi.mock("@/api/query"); + +describe("pageview", () => { + let router: Router; + + beforeEach(async () => { + resetSettings(); + + router = createRouter({ + history: createWebHistory("/base-path"), + routes: [ + { path: "/about", name: "about", component: { template: "
" } }, + ], + }); + + vi.spyOn(router, "isReady").mockResolvedValue(); + + await router.isReady(); + await router.push({ name: "about", query: { id: 1 }, hash: "#title" }); + }); + + it("should track a page path", async () => { + pageview("/about"); + + expect(query).toHaveBeenCalledWith( + "event", + "page_view", + expect.objectContaining({ + page_path: "/about", + }), + ); + }); + + it("should track a page path using a route", async () => { + pageview(router.currentRoute.value); + + expect(query).toHaveBeenCalledWith( + "event", + "page_view", + expect.objectContaining({ + page_path: "/about", + }), + ); + }); + + it("should add the page_location property by default", async () => { + pageview("/about"); + + expect(query).toHaveBeenCalledWith( + "event", + "page_view", + expect.objectContaining({ + page_location: "http://localhost:3000/base-path/about?id=1#title", + }), + ); + }); + + it("should add the send_page_view property by default", async () => { + pageview("/about"); + + expect(query).toHaveBeenCalledWith( + "event", + "page_view", + expect.objectContaining({ + send_page_view: true, + }), + ); + }); + + it("should track a page title using a route", async () => { + pageview(router.currentRoute.value); + + expect(query).toHaveBeenCalledWith( + "event", + "page_view", + expect.objectContaining({ + page_title: "about", + }), + ); + }); + + it("should use custom parameters", async () => { + pageview({ + page_path: "/about", + page_location: "custom_page_location", + send_page_view: false, + }); + + expect(query).toHaveBeenCalledWith("event", "page_view", { + page_path: "/about", + page_location: "custom_page_location", + send_page_view: false, + }); + }); + + describe("pageTracker enabled", () => { + beforeEach(() => { + updateSettings({ + pageTracker: { router }, + }); + }); + + it("should track a page path using a route", () => { + pageview(router.currentRoute.value); + + expect(query).toHaveBeenCalledWith( + "event", + "page_view", + expect.objectContaining({ + page_path: "/about", + }), + ); + }); + + it("should track a page full path using a route", async () => { + updateSettings({ + pageTracker: { + router, + useRouteFullPath: true, + }, + }); + + pageview(router.currentRoute.value); + + expect(query).toHaveBeenCalledWith( + "event", + "page_view", + expect.objectContaining({ + page_path: "/about?id=1#title", + }), + ); + }); + + it("should track a page path with base using a route", async () => { + updateSettings({ + pageTracker: { + router, + useRouterBasePath: true, + }, + }); + + pageview(router.currentRoute.value); + + expect(query).toHaveBeenCalledWith( + "event", + "page_view", + expect.objectContaining({ + page_path: "/base-path/about", + }), + ); + }); + + it("should change the default send_page_view value", async () => { + updateSettings({ + pageTracker: { + router, + sendPageView: false, + }, + }); + + pageview(router.currentRoute.value); + + expect(query).toHaveBeenCalledWith( + "event", + "page_view", + expect.objectContaining({ + send_page_view: false, + }), + ); + }); + }); +}); diff --git a/src/api/pageview/pageview.ts b/src/api/pageview/pageview.ts new file mode 100644 index 00000000..a00463f2 --- /dev/null +++ b/src/api/pageview/pageview.ts @@ -0,0 +1,48 @@ +import { query } from "@/api/query"; +import { type Route, getSettings } from "@/core/settings"; +import type { GtagConfigParams } from "@/types/gtag"; + +export type Pageview = GtagConfigParams; + +export type PageviewParams = string | Route | Pageview; + +function getPathWithBase(path: string, base: string): string { + const normalizedBase = base.endsWith("/") ? base : `${base}/`; + const normalizedPath = path.startsWith("/") ? path.substring(1) : path; + + return `${normalizedBase}${normalizedPath}`; +} + +export function pageview(params: PageviewParams) { + const { pageTracker } = getSettings(); + + let template: PageviewParams | undefined; + + if (typeof params === "string") { + template = { + page_path: params, + }; + } else if ("path" in params) { + const base = pageTracker?.router.options.history.base ?? ""; + const path = pageTracker?.useRouteFullPath ? params.fullPath : params.path; + + template = { + ...(params.name ? { page_title: params.name as string } : {}), + page_path: pageTracker?.useRouterBasePath + ? getPathWithBase(path, base) + : path, + }; + } else { + template = params; + } + + if (template.page_location === undefined) { + template.page_location = window.location.href; + } + + if (template.send_page_view === undefined) { + template.send_page_view = pageTracker?.sendPageView ?? true; + } + + query("event", "page_view", template); +} diff --git a/src/api/purchase.js b/src/api/purchase.js deleted file mode 100644 index 131fc80f..00000000 --- a/src/api/purchase.js +++ /dev/null @@ -1,5 +0,0 @@ -import event from "@/api/event"; - -export default (params) => { - event("purchase", params); -}; diff --git a/src/api/query.js b/src/api/query.js deleted file mode 100644 index 2b6bd5cb..00000000 --- a/src/api/query.js +++ /dev/null @@ -1,12 +0,0 @@ -import { getOptions } from "@/options"; -import { isBrowser } from "@/utils"; - -export default (...args) => { - const { globalObjectName } = getOptions(); - - if (!isBrowser() || typeof window[globalObjectName] === "undefined") { - return; - } - - window[globalObjectName](...args); -}; diff --git a/src/api/query/index.ts b/src/api/query/index.ts new file mode 100644 index 00000000..55e90a3e --- /dev/null +++ b/src/api/query/index.ts @@ -0,0 +1 @@ +export * from "./query"; diff --git a/src/api/query/query.test.ts b/src/api/query/query.test.ts new file mode 100644 index 00000000..40f19519 --- /dev/null +++ b/src/api/query/query.test.ts @@ -0,0 +1,16 @@ +import { getSettings } from "@/core/settings"; +import { query } from "./query"; + +describe("query", () => { + it("should pass events to the gtag library", () => { + const { dataLayerName } = getSettings(); + + query("config", "UA-12345678", { send_page_view: true }); + + expect(window[dataLayerName][0][0]).toEqual("config"); + expect(window[dataLayerName][0][1]).toEqual("UA-12345678"); + expect(window[dataLayerName][0][2]).toEqual({ + send_page_view: true, + }); + }); +}); diff --git a/src/api/query/query.ts b/src/api/query/query.ts new file mode 100644 index 00000000..3e2fb4bd --- /dev/null +++ b/src/api/query/query.ts @@ -0,0 +1,30 @@ +import { getSettings } from "@/core/settings"; +import type { Gtag } from "@/types/gtag"; +import { isServer } from "@/utils"; + +export type QueryParams = Parameters; + +declare global { + interface Window { + // biome-ignore lint/suspicious/noExplicitAny: + [key: string]: any | any[]; + } +} + +export function query(...args: QueryParams) { + const { dataLayerName, gtagName } = getSettings(); + + if (isServer()) { + return; + } + + window[dataLayerName] = window[dataLayerName] || []; + + // biome-ignore lint/complexity/useArrowFunction: + window[gtagName] = function () { + // biome-ignore lint/style/noArguments: + window[dataLayerName].push(arguments); + }; + + window[gtagName](...args); +} diff --git a/src/api/refund.js b/src/api/refund.js deleted file mode 100644 index 2830a17a..00000000 --- a/src/api/refund.js +++ /dev/null @@ -1,5 +0,0 @@ -import event from "@/api/event"; - -export default (...args) => { - event("refund", ...args); -}; diff --git a/src/api/screenview.js b/src/api/screenview.js deleted file mode 100644 index 7e927575..00000000 --- a/src/api/screenview.js +++ /dev/null @@ -1,24 +0,0 @@ -import event from "@/api/event"; -import { getOptions } from "@/options"; - -export default (param) => { - const { appName } = getOptions(); - - if (!param) { - return; - } - - let template; - - if (typeof param === "string") { - template = { - screen_name: param, - }; - } else { - template = param; - } - - template.app_name = template.app_name || appName; - - event("screen_view", template); -}; diff --git a/src/api/screenview/index.ts b/src/api/screenview/index.ts new file mode 100644 index 00000000..894beba2 --- /dev/null +++ b/src/api/screenview/index.ts @@ -0,0 +1 @@ +export * from "./screenview"; diff --git a/src/api/screenview/screenview.test.ts b/src/api/screenview/screenview.test.ts new file mode 100644 index 00000000..6470099b --- /dev/null +++ b/src/api/screenview/screenview.test.ts @@ -0,0 +1,85 @@ +import { query } from "@/api/query"; +import { resetSettings, updateSettings } from "@/core/settings"; +import { type Router, createRouter, createWebHistory } from "vue-router"; +import { screenview } from "./screenview"; + +vi.mock("@/api/query"); + +describe("screenview", () => { + let router: Router; + + beforeEach(async () => { + resetSettings(); + + router = createRouter({ + history: createWebHistory("/base-path"), + routes: [ + { path: "/no-name", component: { template: "
" } }, + { path: "/about", name: "about", component: { template: "
" } }, + ], + }); + + vi.spyOn(router, "isReady").mockResolvedValue(); + + await router.isReady(); + await router.push({ name: "about", query: { id: 1 }, hash: "#title" }); + }); + + it("should track screenview with a string", async () => { + screenview("about"); + + expect(query).toHaveBeenCalledWith("event", "screen_view", { + screen_name: "about", + }); + }); + + it("should track a screenview with the app_name", () => { + screenview({ + app_name: "MyApp", + screen_name: "about", + }); + + expect(query).toHaveBeenCalledWith("event", "screen_view", { + app_name: "MyApp", + screen_name: "about", + }); + }); + + describe("pageTracker", () => { + it("should track screenview with the app_name", async () => { + updateSettings({ + appName: "myapp", + pageTracker: { + router, + }, + }); + + screenview(router.currentRoute.value); + + expect(query).toHaveBeenCalledWith( + "event", + "screen_view", + expect.objectContaining({ + app_name: "myapp", + }), + ); + }); + + it("should track screenview with a route name", async () => { + screenview(router.currentRoute.value); + + expect(query).toHaveBeenCalledWith("event", "screen_view", { + screen_name: "about", + }); + }); + + it("should track screenview with a route and page_path only", async () => { + await router.push("/no-name"); + screenview(router.currentRoute.value); + + expect(query).toHaveBeenCalledWith("event", "screen_view", { + screen_name: "/no-name", + }); + }); + }); +}); diff --git a/src/api/screenview/screenview.ts b/src/api/screenview/screenview.ts new file mode 100644 index 00000000..d52786ee --- /dev/null +++ b/src/api/screenview/screenview.ts @@ -0,0 +1,29 @@ +import { query } from "@/api/query"; +import { type Route, getSettings } from "@/core/settings"; + +export type Screenview = { + app_name?: string; + screen_name?: string; +}; + +export type ScreenviewParams = string | Route | Screenview; + +export function screenview(params: ScreenviewParams) { + const { appName } = getSettings(); + + let template: Screenview = {}; + + if (typeof params === "string") { + template.screen_name = params; + } else if ("path" in params) { + template.screen_name = (params.name ?? params.path) as string; + } else { + template = params; + } + + if (appName && template?.app_name === undefined) { + template.app_name = appName; + } + + query("event", "screen_view", template); +} diff --git a/src/api/set.js b/src/api/set.js deleted file mode 100644 index 57be6e3f..00000000 --- a/src/api/set.js +++ /dev/null @@ -1,5 +0,0 @@ -import query from "@/api/query"; - -export default (...args) => { - query("set", ...args); -}; diff --git a/src/api/set/index.ts b/src/api/set/index.ts new file mode 100644 index 00000000..4bf573a9 --- /dev/null +++ b/src/api/set/index.ts @@ -0,0 +1 @@ +export * from "./set"; diff --git a/src/api/set/set.test.ts b/src/api/set/set.test.ts new file mode 100644 index 00000000..a70c5d11 --- /dev/null +++ b/src/api/set/set.test.ts @@ -0,0 +1,14 @@ +import { query } from "@/api/query"; +import set from "./set"; + +vi.mock("@/api/query"); + +describe("set", () => { + it("should use the set command", () => { + set({ parameter: "value" }); + + expect(query).toHaveBeenCalledWith("set", { + parameter: "value", + }); + }); +}); diff --git a/src/api/set/set.ts b/src/api/set/set.ts new file mode 100644 index 00000000..15c62fd2 --- /dev/null +++ b/src/api/set/set.ts @@ -0,0 +1,8 @@ +import { query } from "@/api/query"; +import type { GtagCommands } from "@/types/gtag"; + +type SetParams = GtagCommands["set"]; + +export default function set(...args: SetParams) { + query("set", ...args); +} diff --git a/src/api/time.js b/src/api/time.js deleted file mode 100644 index a338144e..00000000 --- a/src/api/time.js +++ /dev/null @@ -1,5 +0,0 @@ -import event from "@/api/event"; - -export default (params) => { - event("timing_complete", params); -}; diff --git a/src/api/time/index.ts b/src/api/time/index.ts new file mode 100644 index 00000000..09a9b01b --- /dev/null +++ b/src/api/time/index.ts @@ -0,0 +1 @@ +export * from "./time"; diff --git a/src/api/time/time.test.ts b/src/api/time/time.test.ts new file mode 100644 index 00000000..1cff3b62 --- /dev/null +++ b/src/api/time/time.test.ts @@ -0,0 +1,16 @@ +import { query } from "@/api/query"; +import time from "./time"; + +vi.mock("@/api/query"); + +describe("time", () => { + it("should use the timing_complete event", () => { + time({ + event_category: "event_category_value", + }); + + expect(query).toHaveBeenCalledWith("event", "timing_complete", { + event_category: "event_category_value", + }); + }); +}); diff --git a/src/api/time/time.ts b/src/api/time/time.ts new file mode 100644 index 00000000..6e17b46e --- /dev/null +++ b/src/api/time/time.ts @@ -0,0 +1,6 @@ +import type { GtagEventParams } from "../../types/gtag"; +import { event } from "../event"; + +export default function time(params: GtagEventParams) { + event("timing_complete", params); +} diff --git a/src/attach-api.js b/src/attach-api.js deleted file mode 100644 index 8cfbeb82..00000000 --- a/src/attach-api.js +++ /dev/null @@ -1,7 +0,0 @@ -import * as api from "@/api"; - -const attachApi = (app) => { - app.config.globalProperties.$gtag = api; -}; - -export default attachApi; diff --git a/src/bootstrap.js b/src/bootstrap.js deleted file mode 100644 index ae97eacc..00000000 --- a/src/bootstrap.js +++ /dev/null @@ -1,52 +0,0 @@ -import addConfiguration from "@/add-configuration"; -import addRoutesTracker from "@/add-routes-tracker"; -import { getOptions } from "@/options"; -import registerGlobals from "@/register-globals"; -import { getRouter } from "@/router"; -import { load } from "@/utils"; - -export default () => { - const { - onReady, - onError, - globalObjectName, - globalDataLayerName, - config, - customResourceURL, - customPreconnectOrigin, - deferScriptLoad, - pageTrackerEnabled, - disableScriptLoad, - } = getOptions(); - - const isPageTrackerEnabled = Boolean(pageTrackerEnabled && getRouter()); - - registerGlobals(); - - if (isPageTrackerEnabled) { - addRoutesTracker(); - } else { - addConfiguration(); - } - - if (disableScriptLoad) { - return; - } - - return load(`${customResourceURL}?id=${config.id}&l=${globalDataLayerName}`, { - preconnectOrigin: customPreconnectOrigin, - defer: deferScriptLoad, - }) - .then(() => { - if (onReady) { - onReady(window[globalObjectName]); - } - }) - .catch((error) => { - if (onError) { - onError(error); - } - - return error; - }); -}; diff --git a/src/composables/index.ts b/src/composables/index.ts new file mode 100644 index 00000000..0a049eab --- /dev/null +++ b/src/composables/index.ts @@ -0,0 +1 @@ +export * from "./use-consent"; diff --git a/src/composables/use-consent/index.ts b/src/composables/use-consent/index.ts new file mode 100644 index 00000000..0a049eab --- /dev/null +++ b/src/composables/use-consent/index.ts @@ -0,0 +1 @@ +export * from "./use-consent"; diff --git a/src/composables/use-consent/use-consent.test.ts b/src/composables/use-consent/use-consent.test.ts new file mode 100644 index 00000000..736c0765 --- /dev/null +++ b/src/composables/use-consent/use-consent.test.ts @@ -0,0 +1,77 @@ +import { consent, consentDeniedAll, consentGrantedAll } from "@/api/consent"; +import { addGtag } from "@/core/add-gtag"; +import { resetSettings } from "@/core/settings"; +import flushPromises from "flush-promises"; +import { useConsent } from "./use-consent"; + +vi.mock("@/api/consent"); +vi.mock("@/core/add-gtag"); + +describe("useConsent", () => { + beforeEach(() => { + resetSettings(); + vi.stubGlobal("location", { + ...window.location, + reload: vi.fn(), + }); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it("should load and accept all consent", async () => { + const { acceptAll } = useConsent(); + + acceptAll(); + + await flushPromises(); + + expect(consentGrantedAll).toHaveBeenCalledWith("update"); + expect(window.location.reload).toHaveBeenCalled(); + expect(addGtag).toHaveBeenCalled(); + }); + + it("should load and accept custom consent properties", async () => { + const { acceptCustom } = useConsent(); + + acceptCustom({ + ad_storage: "denied", + ad_user_data: "granted", + }); + + await flushPromises(); + + expect(consent).toHaveBeenCalledWith("update", { + ad_storage: "denied", + ad_user_data: "granted", + }); + expect(window.location.reload).toHaveBeenCalled(); + expect(addGtag).toHaveBeenCalled(); + }); + + it("should reject all", () => { + const { rejectAll } = useConsent(); + + rejectAll(); + + expect(consentDeniedAll).toHaveBeenCalledWith("update"); + expect(window.location.reload).not.toHaveBeenCalled(); + expect(addGtag).not.toHaveBeenCalled(); + }); + + it("should not have consent", async () => { + const { hasConsent } = useConsent(); + + expect(hasConsent.value).toEqual(false); + expect(addGtag).not.toHaveBeenCalled(); + }); + + it("should have consent", async () => { + document.cookie = "_ga=1234"; + const { hasConsent } = useConsent(); + + expect(hasConsent.value).toEqual(true); + expect(addGtag).toHaveBeenCalled(); + }); +}); diff --git a/src/composables/use-consent/use-consent.ts b/src/composables/use-consent/use-consent.ts new file mode 100644 index 00000000..bf68b4b0 --- /dev/null +++ b/src/composables/use-consent/use-consent.ts @@ -0,0 +1,51 @@ +import { consent, consentDeniedAll, consentGrantedAll } from "@/api/consent"; +import { addGtag } from "@/core/add-gtag"; +import type { GtagConsentParams } from "@/types/gtag"; +import { isServer } from "@/utils"; +import { type Ref, ref } from "vue"; + +export type UseWithConsentReturn = { + hasConsent: Ref; + acceptAll: () => void; + rejectAll: () => void; + acceptCustom: (params: GtagConsentParams) => void; +}; + +const GA_COOKIE_VALUE = "_ga"; + +/** + * Provides functionality to manage user consent. + * @remark Make sure to set `initMode` to `manual` + */ +export function useConsent(): UseWithConsentReturn { + const hasConsent = ref( + isServer() ? false : document.cookie.includes(GA_COOKIE_VALUE), + ); + + const acceptAll = async () => { + await addGtag(); + consentGrantedAll("update"); + window.location.reload(); + }; + + const rejectAll = () => { + consentDeniedAll("update"); + }; + + const acceptCustom = async (params: GtagConsentParams) => { + await addGtag(); + consent("update", params); + window.location.reload(); + }; + + if (hasConsent.value) { + addGtag(); + } + + return { + hasConsent, + acceptAll, + rejectAll, + acceptCustom, + }; +} diff --git a/src/core/add-configuration/add-configuration.test.ts b/src/core/add-configuration/add-configuration.test.ts new file mode 100644 index 00000000..c0e07553 --- /dev/null +++ b/src/core/add-configuration/add-configuration.test.ts @@ -0,0 +1,152 @@ +import { query } from "@/api/query"; +import { resetSettings, updateSettings } from "@/core/settings"; +import mockdate from "mockdate"; +import { addConfiguration } from "./add-configuration"; + +mockdate.set("2025-02-27"); + +vi.mock("@/api/query"); + +describe("addConfiguration", () => { + beforeEach(resetSettings); + + it("should query with initial configuration", () => { + updateSettings({ + tagId: "UA-12345678", + }); + + addConfiguration(); + + expect(query).toHaveBeenNthCalledWith(1, "js", new Date()); + expect(query).toHaveBeenNthCalledWith(2, "config", "UA-12345678", { + send_page_view: false, + anonymize_ip: true, + }); + }); + + it("should query multiple domains", () => { + updateSettings({ + tagId: "UA-1", + additionalAccounts: [ + { tagId: "UA-2" }, + { tagId: "UA-3", config: { currency: "USD" } }, + { tagId: "UA-4", config: { send_page_view: true } }, + ], + }); + + addConfiguration(); + + expect(query).toHaveBeenNthCalledWith(1, "js", new Date()); + expect(query).toHaveBeenNthCalledWith(2, "config", "UA-1", { + send_page_view: false, + anonymize_ip: true, + }); + + expect(query).toHaveBeenNthCalledWith(3, "config", "UA-2", { + send_page_view: false, + anonymize_ip: true, + groups: "default", + }); + + expect(query).toHaveBeenNthCalledWith(4, "config", "UA-3", { + currency: "USD", + send_page_view: false, + anonymize_ip: true, + groups: "default", + }); + + expect(query).toHaveBeenNthCalledWith(5, "config", "UA-4", { + send_page_view: true, + anonymize_ip: true, + groups: "default", + }); + }); + + it("should add the linker", () => { + updateSettings({ + tagId: "UA-1", + linker: { + domains: ["domain.com"], + }, + }); + + addConfiguration(); + + expect(query).toHaveBeenNthCalledWith(1, "set", "linker", { + domains: ["domain.com"], + }); + expect(query).toHaveBeenNthCalledWith(2, "js", new Date()); + }); + + describe("hooks", () => { + it("should fire the config:init:before hook", () => { + const spyConfigBefore = vi.fn(); + + updateSettings({ + tagId: "UA-1", + hooks: { + "config:init:before": spyConfigBefore, + }, + }); + + addConfiguration(); + + expect(spyConfigBefore).toHaveBeenCalled(); + expect(query).toHaveBeenCalledAfter(spyConfigBefore); + }); + + it("should fire the config:init:after hook", () => { + const spyConfigAfter = vi.fn(); + + updateSettings({ + tagId: "UA-1", + hooks: { + "config:init:after": spyConfigAfter, + }, + }); + + addConfiguration(); + + expect(spyConfigAfter).toHaveBeenCalled(); + expect(query).toHaveBeenCalledBefore(spyConfigAfter); + }); + }); + + describe("consent mode", () => { + it("should set all granted", () => { + updateSettings({ + tagId: "UA-12345678", + consentMode: "granted", + }); + + addConfiguration(); + + expect(query).toHaveBeenNthCalledWith(1, "consent", "default", { + ad_user_data: "granted", + ad_personalization: "granted", + ad_storage: "granted", + analytics_storage: "granted", + }); + + expect(query).toHaveBeenNthCalledWith(2, "js", new Date()); + }); + + it("should set all denied", () => { + updateSettings({ + tagId: "UA-12345678", + consentMode: "denied", + }); + + addConfiguration(); + + expect(query).toHaveBeenNthCalledWith(1, "consent", "default", { + ad_user_data: "denied", + ad_personalization: "denied", + ad_storage: "denied", + analytics_storage: "denied", + }); + + expect(query).toHaveBeenNthCalledWith(2, "js", new Date()); + }); + }); +}); diff --git a/src/core/add-configuration/add-configuration.ts b/src/core/add-configuration/add-configuration.ts new file mode 100644 index 00000000..d9185672 --- /dev/null +++ b/src/core/add-configuration/add-configuration.ts @@ -0,0 +1,59 @@ +import { consentDeniedAll, consentGrantedAll } from "@/api/consent"; +import { linker } from "@/api/linker"; +import { query } from "@/api/query"; +import { getSettings } from "@/core/settings"; +import type { GtagConfig } from "@/types/gtag"; + +function mergeDefaults(config: GtagConfig = {}): GtagConfig { + return { + send_page_view: false, + anonymize_ip: true, + ...config, + }; +} + +export function addConfiguration() { + const { + tagId, + config, + groupName, + linker: linkerOptions, + additionalAccounts, + hooks, + consentMode, + } = getSettings(); + + if (!tagId) { + return; + } + + hooks?.["config:init:before"]?.(); + + if (consentMode === "granted") { + consentGrantedAll(); + } else if (consentMode === "denied") { + consentDeniedAll(); + } + + if (linkerOptions) { + linker(linkerOptions); + } + + query("js", new Date()); + query("config", tagId, mergeDefaults(config)); + + if (additionalAccounts) { + for (const account of additionalAccounts) { + query( + "config", + account.tagId, + mergeDefaults({ + groups: groupName, + ...account.config, + }), + ); + } + } + + hooks?.["config:init:after"]?.(); +} diff --git a/src/core/add-configuration/index.ts b/src/core/add-configuration/index.ts new file mode 100644 index 00000000..e4df19d7 --- /dev/null +++ b/src/core/add-configuration/index.ts @@ -0,0 +1 @@ +export * from "./add-configuration"; diff --git a/src/core/add-gtag/add-gtag.test.ts b/src/core/add-gtag/add-gtag.test.ts new file mode 100644 index 00000000..2017bcfb --- /dev/null +++ b/src/core/add-gtag/add-gtag.test.ts @@ -0,0 +1,196 @@ +import { addConfiguration } from "@/core/add-configuration"; +import { addRouterTracking } from "@/core/add-router-tracking"; +import { resetSettings, updateSettings } from "@/core/settings"; +import * as utils from "@/utils"; +import { createRouter, createWebHistory } from "vue-router"; +import { addGtag } from "./add-gtag"; + +vi.mock("@/utils", async () => ({ + ...(await vi.importActual("@/utils.ts")), + injectScript: vi.fn(), +})); + +vi.mock("@/core/add-configuration"); +vi.mock("@/core/add-router-tracking"); +vi.mock("@/core/api/query"); + +describe("addGtag", () => { + beforeEach(() => { + document.body.innerHTML = ""; + resetSettings(); + }); + + it("should download the gtag.js library", async () => { + updateSettings({ + tagId: "UA-12345678", + }); + + await addGtag(); + + const resource = "https://www.googletagmanager.com/gtag/js"; + const id = "UA-12345678"; + const dataLayer = "dataLayer"; + const url = `${resource}?id=${id}&l=${dataLayer}`; + + expect(utils.injectScript).toHaveBeenCalledWith(url, expect.anything()); + }); + + it("should avoid downloading gtag if already exists in the DOM", async () => { + const spyOnResolved = vi.fn(); + + const script = document.createElement("script"); + script.src = "https://www.googletagmanager.com/gtag/js?id=12345678"; + document.body.appendChild(script); + + updateSettings({ + tagId: "UA-12345678", + hooks: { + "script:loaded": spyOnResolved, + }, + }); + + await addGtag(); + + expect(spyOnResolved).toHaveBeenCalled(); + expect(utils.injectScript).not.toHaveBeenCalled(); + }); + + it("should download a custom version of the gtag.js library", async () => { + updateSettings({ + tagId: "UA-12345678", + resource: { + url: "custom_resource_url", + }, + }); + + await addGtag(); + + const resource = "custom_resource_url"; + const id = "UA-12345678"; + const dataLayer = "dataLayer"; + const url = `${resource}?id=${id}&l=${dataLayer}`; + + expect(utils.injectScript).toHaveBeenCalledWith(url, expect.anything()); + }); + + it("should fire callback when plugin is ready", async () => { + const spyOnResolved = vi.fn(); + + updateSettings({ + tagId: "UA-12345678", + hooks: { + "script:loaded": spyOnResolved, + }, + }); + + await addGtag(); + + expect(spyOnResolved).toHaveBeenCalled(); + }); + + it("should fire callback when downloading library throws an error", async () => { + const spyOnError = vi.fn(); + + vi.spyOn(utils, "injectScript").mockRejectedValue(new Error()); + + updateSettings({ + tagId: "UA-12345678", + hooks: { + "script:error": spyOnError, + }, + }); + + await addGtag(); + + expect(spyOnError).toHaveBeenCalled(); + }); + + it("should add initial gtag config call", async () => { + updateSettings({ + tagId: "UA-12345678", + }); + + await addGtag(); + + expect(addConfiguration).toHaveBeenCalled(); + }); + + it("should initialize router tracking", async () => { + const router = createRouter({ + history: createWebHistory(), + routes: [{ path: "/", component: { template: "
" } }], + }); + + updateSettings({ + tagId: "UA-12345678", + pageTracker: { + router, + }, + }); + + await addGtag(); + + expect(addRouterTracking).toHaveBeenCalled(); + expect(addConfiguration).toHaveBeenCalled(); + }); + + it("should not bootstrap gtag if tagId is missing", async () => { + await addGtag(); + + expect(addConfiguration).not.toHaveBeenCalled(); + }); + + it("should preconnect the script origin", async () => { + updateSettings({ + tagId: "UA-12345678", + resource: { + preconnect: true, + }, + }); + + await addGtag(); + + expect(utils.injectScript).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + preconnect: true, + }), + ); + }); + + it("should add the defer attribute to the script", async () => { + updateSettings({ + tagId: "UA-12345678", + resource: { + defer: true, + }, + }); + + await addGtag(); + + expect(utils.injectScript).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + defer: true, + }), + ); + }); + + it("should add the nonce attribute to the script", async () => { + updateSettings({ + tagId: "UA-12345678", + resource: { + nonce: "abcd", + }, + }); + + await addGtag(); + + expect(utils.injectScript).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + nonce: "abcd", + }), + ); + }); +}); diff --git a/src/core/add-gtag/add-gtag.ts b/src/core/add-gtag/add-gtag.ts new file mode 100644 index 00000000..cd82a491 --- /dev/null +++ b/src/core/add-gtag/add-gtag.ts @@ -0,0 +1,39 @@ +import { addConfiguration } from "@/core/add-configuration"; +import { addRouterTracking } from "@/core/add-router-tracking"; +import { getSettings } from "@/core/settings"; +import { hasGtag, injectScript } from "@/utils"; + +/** + * Adds the Google Analytics gtag script to the application and initializes router + * auto-tracking when enabled. + */ +export async function addGtag(): Promise { + const { resource, dataLayerName, tagId, pageTracker, hooks } = getSettings(); + + if (!tagId) { + return; + } + + addConfiguration(); + + if (pageTracker?.router) { + addRouterTracking(); + } + + if (hasGtag()) { + hooks?.["script:loaded"]?.(); + return; + } + + try { + await injectScript(`${resource.url}?id=${tagId}&l=${dataLayerName}`, { + preconnect: resource.preconnect, + defer: resource.defer, + nonce: resource.nonce, + }); + + hooks?.["script:loaded"]?.(); + } catch (error) { + hooks?.["script:error"]?.(error); + } +} diff --git a/src/core/add-gtag/index.ts b/src/core/add-gtag/index.ts new file mode 100644 index 00000000..5ecd3ec2 --- /dev/null +++ b/src/core/add-gtag/index.ts @@ -0,0 +1 @@ +export * from "./add-gtag"; diff --git a/src/core/add-router-tracking/add-router-tracking.test.ts b/src/core/add-router-tracking/add-router-tracking.test.ts new file mode 100644 index 00000000..250f1712 --- /dev/null +++ b/src/core/add-router-tracking/add-router-tracking.test.ts @@ -0,0 +1,92 @@ +import { resetSettings, updateSettings } from "@/core/settings"; +import { trackRoute } from "@/core/track-route"; +import { createRouter, createWebHistory } from "vue-router"; +import { addRouterTracking } from "./add-router-tracking"; + +vi.mock("@/core/track-route"); + +const routes = [ + { path: "/", component: { template: "
" } }, + { path: "/about", component: { template: "
" } }, +]; + +describe("add-router-tracking", () => { + beforeEach(resetSettings); + + it("should track once the router is ready", async () => { + const router = createRouter({ + history: createWebHistory(), + routes, + }); + + vi.spyOn(router, "isReady").mockResolvedValue(); + + updateSettings({ pageTracker: { router } }); + + await addRouterTracking(); + + expect(trackRoute).toHaveBeenCalledWith( + expect.objectContaining({ + path: "/", + }), + ); + }); + + it("should track after route change", async () => { + const router = createRouter({ + history: createWebHistory(), + routes, + }); + + vi.spyOn(router, "isReady").mockResolvedValue(); + + updateSettings({ pageTracker: { router } }); + + await addRouterTracking(); + + await router.push("/about"); + + expect(trackRoute).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + path: "/about", + }), + ); + }); + + it("should skip tracking identical paths", async () => { + const router = createRouter({ + history: createWebHistory(), + routes, + }); + + vi.spyOn(router, "isReady").mockResolvedValue(); + + updateSettings({ + pageTracker: { + router, + }, + }); + + await addRouterTracking(); + + await router.push("/about"); + await router.push("/about"); + + expect(trackRoute).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + path: "/", + }), + ); + + expect(trackRoute).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + path: "/about", + }), + ); + + expect(trackRoute).toHaveBeenCalledTimes(2); + }); +}); diff --git a/src/core/add-router-tracking/add-router-tracking.ts b/src/core/add-router-tracking/add-router-tracking.ts new file mode 100644 index 00000000..027e7680 --- /dev/null +++ b/src/core/add-router-tracking/add-router-tracking.ts @@ -0,0 +1,24 @@ +import { getSettings } from "@/core/settings"; +import { trackRoute } from "@/core/track-route"; + +export async function addRouterTracking(): Promise { + const { pageTracker } = getSettings(); + + if (!pageTracker?.router) { + return; + } + + const { router } = pageTracker; + + await router.isReady(); + + trackRoute(router.currentRoute.value); + + router.afterEach((to, from) => { + if (to.path === from.path) { + return; + } + + trackRoute(to); + }); +} diff --git a/src/core/add-router-tracking/index.ts b/src/core/add-router-tracking/index.ts new file mode 100644 index 00000000..087e41b6 --- /dev/null +++ b/src/core/add-router-tracking/index.ts @@ -0,0 +1 @@ +export * from "./add-router-tracking"; diff --git a/src/core/create-gtag/create-gtag.test.ts b/src/core/create-gtag/create-gtag.test.ts new file mode 100644 index 00000000..028ccfd1 --- /dev/null +++ b/src/core/create-gtag/create-gtag.test.ts @@ -0,0 +1,30 @@ +import * as api from "@/api/index"; +import { addGtag } from "@/core/add-gtag"; +import { resetSettings } from "@/core/settings"; +import { createApp } from "vue"; +import { createGtag } from "./create-gtag"; + +vi.mock("@/core/add-gtag"); + +describe("createGtag", () => { + beforeEach(resetSettings); + + it("should initialize gtag", () => { + createGtag({ tagId: "UA-123456789" }); + expect(addGtag).toHaveBeenCalled(); + }); + + it("should add global properties", () => { + const app = createApp({}); + const gtag = createGtag({ tagId: "UA-123456789" }); + + app.use(gtag); + + expect(app.config.globalProperties.$gtag).toEqual(api); + }); + + it("should avoid initialization using initMode in manual mode", () => { + createGtag({ tagId: "UA-123456789", initMode: "manual" }); + expect(addGtag).not.toHaveBeenCalled(); + }); +}); diff --git a/src/core/create-gtag/create-gtag.ts b/src/core/create-gtag/create-gtag.ts new file mode 100644 index 00000000..75de0718 --- /dev/null +++ b/src/core/create-gtag/create-gtag.ts @@ -0,0 +1,38 @@ +import * as api from "@/api/index"; +import { addGtag } from "@/core/add-gtag"; +import { + type PluginSettings, + getSettings, + updateSettings, +} from "@/core/settings"; +import type { App } from "vue"; + +type GtagAPI = typeof api; + +declare module "vue" { + interface ComponentCustomProperties { + $gtag: GtagAPI; + } +} + +type CreateGtagReturn = (app: App) => void; + +function handleGtag() { + const { initMode } = getSettings(); + + if (initMode === "manual") { + return; + } + + addGtag(); +} + +/** Creates and initializes the `gtag` function for use within a Vue application. */ +export function createGtag(settings: PluginSettings): CreateGtagReturn { + updateSettings(settings); + handleGtag(); + + return (app: App) => { + app.config.globalProperties.$gtag = api; + }; +} diff --git a/src/core/create-gtag/index.ts b/src/core/create-gtag/index.ts new file mode 100644 index 00000000..d71d3e12 --- /dev/null +++ b/src/core/create-gtag/index.ts @@ -0,0 +1 @@ +export * from "./create-gtag"; diff --git a/src/core/index.ts b/src/core/index.ts new file mode 100644 index 00000000..eadfd09f --- /dev/null +++ b/src/core/index.ts @@ -0,0 +1,2 @@ +export * from "./add-gtag"; +export * from "./create-gtag"; diff --git a/src/core/settings/index.ts b/src/core/settings/index.ts new file mode 100644 index 00000000..c388e2db --- /dev/null +++ b/src/core/settings/index.ts @@ -0,0 +1,2 @@ +export * from "./settings"; +export * from "./types"; diff --git a/src/core/settings/settings.test.ts b/src/core/settings/settings.test.ts new file mode 100644 index 00000000..c221fadf --- /dev/null +++ b/src/core/settings/settings.test.ts @@ -0,0 +1,27 @@ +import { getSettings, updateSettings } from "./settings"; + +describe("config", () => { + it("should return the configuration", () => { + expect(getSettings()).toEqual({ + resource: { url: "https://www.googletagmanager.com/gtag/js" }, + dataLayerName: "dataLayer", + gtagName: "gtag", + groupName: "default", + initMode: "auto", + }); + }); + + it("should update the default configuration", () => { + updateSettings({ + tagId: "UA-1234567", + gtagName: "foo", + }); + + expect(getSettings()).toEqual( + expect.objectContaining({ + tagId: "UA-1234567", + gtagName: "foo", + }), + ); + }); +}); diff --git a/src/core/settings/settings.ts b/src/core/settings/settings.ts new file mode 100644 index 00000000..5202de62 --- /dev/null +++ b/src/core/settings/settings.ts @@ -0,0 +1,24 @@ +import { deepMerge } from "@/utils"; +import type { Settings } from "./types"; + +const defaultSettings: Readonly = { + resource: { url: "https://www.googletagmanager.com/gtag/js" }, + dataLayerName: "dataLayer", + gtagName: "gtag", + groupName: "default", + initMode: "auto", +}; + +let settings: Settings = { ...defaultSettings }; + +export function getSettings(): Settings { + return settings; +} + +export function resetSettings(): void { + settings = { ...defaultSettings }; +} + +export function updateSettings(configParams: Partial): void { + settings = deepMerge(settings, configParams); +} diff --git a/src/core/settings/types.ts b/src/core/settings/types.ts new file mode 100644 index 00000000..befdcf6f --- /dev/null +++ b/src/core/settings/types.ts @@ -0,0 +1,205 @@ +import type { LinkerParams } from "@/api/linker"; +import type { Pageview } from "@/api/pageview"; +import type { Screenview } from "@/api/screenview"; +import type { GtagConfig } from "@/types/gtag"; +import type { + RouteLocationNormalizedGeneric as VueRouterRoute, + Router as VueRouterRouter, +} from "vue-router"; + +export type Router = VueRouterRouter; +export type Route = VueRouterRoute; + +export type PageTrackerParams = Pageview | Screenview; + +export type PageTrackerTemplate = + | PageTrackerParams + | ((route: Route) => PageTrackerParams); + +export type PageTrackerExclude = + | Array<{ path?: string; name?: string }> + | ((route: Route) => boolean); + +export type PageTracker = { + /** + * Vue Router instance used for tracking navigation events. + */ + router: Router; + + /** + * Custom template for generating route tracking events. + */ + template?: PageTrackerTemplate; + + /** + * Use `screen_view` instead of the default `page_view` event. + * @default false + */ + useScreenview?: boolean; + + /** + * Defines routes to exclude from tracking. + * - Can be an array of route objects identified by `path` or `name`. + * - Can also be a function that returns `true` to exclude the route from tracking. + */ + exclude?: PageTrackerExclude; + + /** + * Set `send_page_view` for each route change. + * @default true + */ + sendPageView?: boolean; + + /** + * Use the router base path option + */ + useRouterBasePath?: boolean; + + /** + * Sets the `page_path` equal to the route `fullPath` instead of `path` property + */ + useRouteFullPath?: boolean; +}; + +export type Hooks = { + /** + * Triggered before a route tracking event is fired. + * @param route - The current route being tracked. + */ + "router:track:before"?: (route: Route) => void; + + /** + * Triggered after a route tracking event is fired. + * @param route - The current route that was tracked. + */ + "router:track:after"?: (route: Route) => void; + + /** + * Triggered before the initial configuration request is sent. + */ + "config:init:before"?: () => void; + + /** + * Triggered after the initial configuration request is sent. + */ + "config:init:after"?: () => void; + + /** + * Called when the gtag.js script successfully loads. + */ + "script:loaded"?: () => void; + + /** + * Called when the gtag.js script fails to load. + * @param error - The error encountered during script loading. + */ + "script:error"?: (error: unknown) => void; +}; + +export type Resource = { + /** + * URL of the gtag.js script. + * @default "https://www.googletagmanager.com/gtag/js" + */ + url?: string; + + /** + * Enable preconnecting to the script's domain for faster loading. + * @default false + */ + preconnect?: boolean; + + /** + * Load the script with the `defer` attribute. + * @default false + */ + defer?: boolean; + + /** + * A nonce value for the script tag, useful for enforcing Content Security Policy (CSP). + */ + nonce?: string; +}; + +export type TagId = string; + +export type Settings = { + /** + * Primary Google Tag Manager or Google Analytics tag ID. + */ + tagId?: TagId; + + /** + * Configuration settings for the main `tagId`. + */ + config?: GtagConfig; + + /** + * Additional tag IDs and their configurations to be tracked alongside the main `tagId`. + */ + additionalAccounts?: Array<{ tagId: TagId; config?: GtagConfig }>; + + /** + * Configuration for loading the gtag.js script. + */ + resource: Resource; + + /** + * Custom global variable name for the data layer. + * @default "dataLayer" + */ + dataLayerName: string; + + /** + * Custom global function name for `gtag`. + * @default "gtag" + */ + gtagName: string; + + /** + * Settings for automatic route tracking. + * Make sure to disable pageview tracking from the "Enhanced measurement" in your Data Stream to avoid + * possible double-tracking of your routes. + */ + pageTracker?: PageTracker; + + /** + * Configuration for cross-domain tracking. + */ + linker?: LinkerParams; + + /** + * Custom analytics group name. + * @default "default" + */ + groupName: string; + + /** + * Collection of lifecycle hooks and event callbacks for tracking and configuration. + */ + hooks?: Hooks; + + /** + * Default consent mode applied during initialization. + */ + consentMode?: "denied" | "granted"; + + /** + * Whether to initialize the Google tag script immediately after the page has loaded. + * + * @remarks + * - Set this to `manual` to delay the initialization until you call the `addGtag` function manually. + * - Set this to `manual` if you want to use the `useConsent` composable. + * + * @default 'auto' + */ + initMode?: "auto" | "manual"; + + /** + * Default value for `app_name` when using the `screen_view` tracking method. + */ + appName?: string; +}; + +export type PluginSettings = Partial & + Required>; diff --git a/src/core/track-route/index.ts b/src/core/track-route/index.ts new file mode 100644 index 00000000..b9ea55d1 --- /dev/null +++ b/src/core/track-route/index.ts @@ -0,0 +1 @@ +export * from "./track-route"; diff --git a/src/core/track-route/track-route.test.ts b/src/core/track-route/track-route.test.ts new file mode 100644 index 00000000..65276dd4 --- /dev/null +++ b/src/core/track-route/track-route.test.ts @@ -0,0 +1,221 @@ +import { query } from "@/api/query"; +import { resetSettings, updateSettings } from "@/core/settings"; +import { type Router, createRouter, createWebHistory } from "vue-router"; +import { trackRoute } from "./track-route"; + +vi.mock("@/api/query"); + +describe("track-route", () => { + let router: Router; + + beforeEach(async () => { + resetSettings(); + + router = createRouter({ + history: createWebHistory(), + routes: [ + { + path: "/", + name: "home", + component: { template: "
" }, + }, + { + path: "/about", + name: "about", + component: { template: "
" }, + }, + ], + }); + + vi.spyOn(router, "isReady").mockResolvedValue(); + + await router.isReady(); + }); + + it("should track screenviews", async () => { + updateSettings({ + appName: "MyApp", + pageTracker: { + router, + useScreenview: true, + }, + }); + + await router.isReady(); + await router.push("/about"); + + trackRoute(router.currentRoute.value); + + expect(query).toHaveBeenCalledWith("event", "screen_view", { + screen_name: "about", + app_name: "MyApp", + }); + }); + + it("should track pageviews", async () => { + await router.isReady(); + await router.push("/about"); + + trackRoute(router.currentRoute.value); + + expect(query).toHaveBeenCalledWith("event", "page_view", { + page_path: "/about", + page_location: "http://localhost:3000/about", + page_title: "about", + send_page_view: true, + }); + }); + + it("should avoid tracking excluded routes using path", async () => { + updateSettings({ + pageTracker: { + router, + exclude: [{ path: "/" }], + }, + }); + + await router.isReady(); + await router.push("/"); + + trackRoute(router.currentRoute.value); + + expect(query).not.toHaveBeenCalled(); + }); + + it("should avoid tracking excluded routes using name", async () => { + updateSettings({ + pageTracker: { + router, + exclude: [{ name: "home" }], + }, + }); + + await router.isReady(); + await router.push("/"); + + trackRoute(router.currentRoute.value); + + expect(query).not.toHaveBeenCalled(); + }); + + it("should avoid tracking excluded routes using a custom function", async () => { + updateSettings({ + pageTracker: { + router, + exclude: ({ name }) => name === "home", + }, + }); + + await router.isReady(); + await router.push("/"); + + trackRoute(router.currentRoute.value); + + expect(query).not.toHaveBeenCalled(); + }); + + it("should fire a callback before tracking", async () => { + const spyOnBeforeTrack = vi.fn(); + + updateSettings({ + pageTracker: { + router, + }, + hooks: { + "router:track:before": spyOnBeforeTrack, + }, + }); + + await router.isReady(); + await router.push("/"); + + trackRoute(router.currentRoute.value); + + expect(spyOnBeforeTrack).toHaveBeenCalledWith(router.currentRoute.value); + }); + + it("should fire a callback after tracking", async () => { + const spyOnAfterTrack = vi.fn(); + + updateSettings({ + pageTracker: { + router, + }, + hooks: { + "router:track:after": spyOnAfterTrack, + }, + }); + + await router.isReady(); + await router.push("/"); + + trackRoute(router.currentRoute.value); + + expect(spyOnAfterTrack).toHaveBeenCalledWith(router.currentRoute.value); + }); + + it("should track using a custom template", async () => { + updateSettings({ + pageTracker: { + router, + template: { + page_path: "/custom-template-path", + }, + }, + }); + + await router.isReady(); + await router.push("/"); + + trackRoute(router.currentRoute.value); + + expect(query).toHaveBeenCalledWith("event", "page_view", { + page_path: "/custom-template-path", + page_location: "http://localhost:3000/", + send_page_view: true, + }); + }); + + it("should track using a custom template function", async () => { + updateSettings({ + pageTracker: { + router, + template: (to) => ({ + page_path: `${to.path}_custom-template-path`, + }), + }, + }); + + await router.isReady(); + await router.push("/about"); + + trackRoute(router.currentRoute.value); + + expect(query).toHaveBeenCalledWith("event", "page_view", { + page_path: "/about_custom-template-path", + page_location: "http://localhost:3000/about", + send_page_view: true, + }); + }); + + it("should track screen views using a custom template", async () => { + updateSettings({ + pageTracker: { + router, + useScreenview: true, + template: { + screen_name: "about", + }, + }, + }); + + await router.isReady(); + await router.push("/"); + + trackRoute(router.currentRoute.value); + + expect(query).toHaveBeenCalledWith("event", "screen_view", { + screen_name: "about", + }); + }); +}); diff --git a/src/core/track-route/track-route.ts b/src/core/track-route/track-route.ts new file mode 100644 index 00000000..dd969f6e --- /dev/null +++ b/src/core/track-route/track-route.ts @@ -0,0 +1,56 @@ +import { pageview } from "@/api/pageview"; +import { screenview } from "@/api/screenview"; +import { + type PageTrackerParams, + type Route, + getSettings, +} from "@/core/settings"; + +function isRouteExcluded(route: Route): boolean { + const { pageTracker } = getSettings(); + + if (!pageTracker?.exclude) { + return false; + } + + if (typeof pageTracker.exclude === "function") { + return pageTracker.exclude(route); + } + + return pageTracker.exclude?.some(({ name, path } = {}) => { + return (name && name === route.name) || (path && path === route.path); + }); +} + +export function trackRoute(route: Route) { + const { pageTracker, hooks } = getSettings(); + + if (isRouteExcluded(route)) { + return; + } + + hooks?.["router:track:before"]?.(route); + + let template: PageTrackerParams | undefined; + + if (pageTracker?.template) { + template = + typeof pageTracker.template === "function" + ? pageTracker.template(route) + : pageTracker.template; + } + + if (pageTracker?.useScreenview) { + const screenviewParams = + template && "screen_name" in template ? template : route; + + screenview(screenviewParams); + } else { + const pageviewParams = + template && "page_path" in template ? template : route; + + pageview(pageviewParams); + } + + hooks?.["router:track:after"]?.(route); +} diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 310d7b17..00000000 --- a/src/index.js +++ /dev/null @@ -1,39 +0,0 @@ -import attachApi from "@/attach-api"; -import bootstrap from "@/bootstrap"; -import { getOptions, setOptions } from "@/options"; -import { setRouter } from "@/router"; - -const install = (app, options, router) => { - attachApi(app); - setOptions(options); - setRouter(router); - - if (getOptions().bootstrap) { - bootstrap(); - } -}; - -export { default as query } from "@/api/query"; -export { default as config } from "@/api/config"; -export { default as optOut } from "@/api/opt-out"; -export { default as optIn } from "@/api/opt-in"; -export { default as pageview } from "@/api/pageview"; -export { default as screenview } from "@/api/screenview"; -export { default as exception } from "@/api/exception"; -export { default as linker } from "@/api/linker"; -export { default as time } from "@/api/time"; -export { default as set } from "@/api/set"; -export { default as refund } from "@/api/refund"; -export { default as purchase } from "@/api/purchase"; -export { default as customMap } from "@/api/custom-map"; -export { default as event } from "@/api/event"; - -export { default as bootstrap } from "@/bootstrap"; -export { default as addRoutesTracker } from "@/add-routes-tracker"; - -export { setOptions } from "@/options"; -export { setRouter } from "@/router"; - -export { install }; - -export default install; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..fe2b5b22 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,3 @@ +export * from "@/api/index"; +export * from "@/composables/index"; +export * from "@/core/index"; diff --git a/src/options.js b/src/options.js deleted file mode 100644 index c0a2a914..00000000 --- a/src/options.js +++ /dev/null @@ -1,43 +0,0 @@ -import { mergeDeep } from "@/utils"; - -export const getDefaultParams = () => ({ - bootstrap: true, - onReady: null, - onError: null, - onBeforeTrack: null, - onAfterTrack: null, - pageTrackerTemplate: null, - customResourceURL: "https://www.googletagmanager.com/gtag/js", - customPreconnectOrigin: "https://www.googletagmanager.com", - deferScriptLoad: false, - pageTrackerExcludedRoutes: [], - pageTrackerEnabled: true, - enabled: true, - disableScriptLoad: false, - pageTrackerScreenviewEnabled: false, - appName: null, - pageTrackerUseFullPath: false, - pageTrackerPrependBase: true, - pageTrackerSkipSamePath: true, - globalDataLayerName: "dataLayer", - globalObjectName: "gtag", - defaultGroupName: "default", - includes: null, - config: { - id: null, - params: { - send_page_view: false, - }, - }, -}); - -let params = {}; - -export const setOptions = (options = {}) => { - const defaultParams = getDefaultParams(); - params = mergeDeep(defaultParams, options); -}; - -export const getOptions = () => { - return params; -}; diff --git a/src/register-globals.js b/src/register-globals.js deleted file mode 100644 index 67a9e26b..00000000 --- a/src/register-globals.js +++ /dev/null @@ -1,26 +0,0 @@ -import * as api from "@/api"; -import { getOptions } from "@/options"; -import { isBrowser } from "@/utils"; - -export default () => { - if (!isBrowser()) { - return; - } - - const { enabled, globalObjectName, globalDataLayerName } = getOptions(); - - if (window[globalObjectName] == null) { - window[globalDataLayerName] = window[globalDataLayerName] || []; - window[globalObjectName] = function (){ - window[globalDataLayerName].push(arguments); - }; - } - - window[globalObjectName]("js", new Date()); - - if (!enabled) { - api.optOut(); - } - - return window[globalObjectName]; -}; diff --git a/src/router.js b/src/router.js deleted file mode 100644 index 34ba8aaa..00000000 --- a/src/router.js +++ /dev/null @@ -1,9 +0,0 @@ -let router; - -export const setRouter = (instance) => { - router = instance; -}; - -export const getRouter = () => router; - -export default router; diff --git a/src/track.js b/src/track.js deleted file mode 100644 index b61e7dc2..00000000 --- a/src/track.js +++ /dev/null @@ -1,34 +0,0 @@ -import * as api from "@/api"; -import { getOptions } from "@/options"; -import { isFn, validateScreenviewShape } from "@/utils"; - -export default (to = {}, from = {}) => { - const { - appName, - pageTrackerTemplate: proxy, - pageTrackerScreenviewEnabled: useScreenview, - pageTrackerSkipSamePath: skipSamePath, - } = getOptions(); - - if (skipSamePath && to.path === from.path) { - return; - } - - let template = to; - - if (isFn(proxy)) { - template = proxy(to, from); - } else if (useScreenview) { - template = validateScreenviewShape({ - app_name: appName, - screen_name: to.name, - }); - } - - if (useScreenview) { - api.screenview(template); - return; - } - - api.pageview(template); -}; diff --git a/src/types/gtag.ts b/src/types/gtag.ts new file mode 100644 index 00000000..4076d3f9 --- /dev/null +++ b/src/types/gtag.ts @@ -0,0 +1,348 @@ +export type GtagConfig = + | GtagControlParams + | GtagEventParams + | GtagConfigParams + | GtagCustomParams; + +/** + * Defines the available Google Tag (gtag) commands and their respective parameters. + */ +export type GtagCommands = { + /** Configures the Google Tag with an optional set of parameters. */ + config: [ + /** The tracking ID or measurement ID. */ + targetId: string, + /** Optional configuration parameters. */ + config?: GtagConfig, + ]; + + /** Sets custom parameters or updates existing values. */ + set: + | [targetId: string, config: GtagCustomParams | boolean | string] + | [config: GtagCustomParams]; + + /** Initializes the Google Tag script. */ + js: [ + /** The date when the script was initialized. */ + config: Date, + ]; + + /** Logs an event with optional parameters. */ + event: [ + /** The name of the event being logged. */ + eventName: GtagEventNames | (string & {}), + /** Optional parameters associated with the event. */ + eventParams?: GtagControlParams | GtagEventParams | GtagCustomParams, + ]; + + /** Retrieves values from the Google Tag system. */ + get: [ + /** The tracking ID or measurement ID. */ + targetId: string, + /** The field name to retrieve. */ + fieldName: GtagFieldNames | string, + /** Callback function to handle the retrieved value. */ + // biome-ignore lint/suspicious/noExplicitAny: + callback?: (field?: string | GtagCustomParams) => any, + ]; + + /** Manages user consent settings. */ + consent: [ + /** The consent action being performed. */ + consentArg: GtagConsentArg | (string & {}), + /** The consent parameters being updated. */ + consentParams: GtagConsentParams, + ]; +}; + +export type Gtag = ( + command: Command, + ...args: GtagCommands[Command] +) => void; + +// biome-ignore lint/suspicious/noExplicitAny: +export type GtagCustomParams = Record; + +/** + * Parameters for configuring Google Analytics tracking. + */ +export type GtagConfigParams = { + /** The title of the page being tracked. */ + page_title?: string; + /** The full URL of the page being tracked. */ + page_location?: string; + /** The path of the page being tracked. */ + page_path?: string; + /** Whether to send a page view event automatically. */ + send_page_view?: boolean; +}; + +/** + * Control parameters for Google Tag events. + */ +export type GtagControlParams = { + /** The groups to which the event belongs. */ + groups?: string | string[]; + /** The tracking IDs to which the event should be sent. */ + send_to?: string | string[]; + /** Callback function executed after the event is sent. */ + event_callback?: () => void; + /** The timeout in milliseconds before the event callback is executed. */ + event_timeout?: number; +}; + +export type GtagEcommerceEventNames = + | "refund" + | "select_item" + | "view_item" + | "add_to_cart" + | "view_item_list" + | "add_shipping_info" + | "add_payment_info" + | "add_to_wishlist" + | "view_cart" + | "remove_from_cart" + | "begin_checkout" + | "purchase" + | "view_promotion" + | "select_promotion"; + +export type GtagGeneralEventNames = + | "checkout_progress" + | "earn_virtual_currency" + | "exception" + | "generate_lead" + | "join_group" + | "level_end" + | "level_start" + | "level_up" + | "login" + | "page_view" + | "post_score" + | "screen_view" + | "search" + | "select_content" + | "set_checkout_option" + | "share" + | "sign_up" + | "spend_virtual_currency" + | "tutorial_begin" + | "tutorial_complete" + | "unlock_achievement" + | "timing_complete" + | "view_search_results"; + +export type GtagEventNames = GtagEcommerceEventNames | GtagGeneralEventNames; + +/** + * Parameters associated with Google Analytics events. + */ +export type GtagEventParams = { + /** The option chosen during checkout. */ + checkout_option?: string; + /** The step number in the checkout process. */ + checkout_step?: number; + /** The unique identifier of the content. */ + content_id?: string; + /** The type of content being interacted with. */ + content_type?: string; + /** A discount coupon code. */ + coupon?: string; + /** The currency used for the transaction. */ + currency?: string; + /** A description of the event or product. */ + description?: string; + /** Indicates if the event was fatal. */ + fatal?: boolean; + /** The items associated with the event. */ + items?: GtagItem[]; + /** The payment or shipping method used. */ + method?: string; + /** A unique number associated with the transaction. */ + number?: string; + /** The promotions associated with the event. */ + promotions?: GtagPromotion[]; + /** The screen name where the event occurred. */ + screen_name?: string; + /** The search term used. */ + search_term?: string; + /** The shipping cost. */ + shipping?: GtagCurrency; + /** The tax amount. */ + tax?: GtagCurrency; + /** The transaction ID. */ + transaction_id?: string; + /** The event value. */ + value?: number; + /** The label for the event. */ + event_label?: string; + /** The category for the event. */ + event_category?: string; +}; + +type GtagCurrency = string | number; + +type GtagItem = { + /** The ID of the item. */ + item_id?: string; + /** The name of the item. */ + item_name?: string; + /** A product affiliation to designate a supplying company or brick and mortar store location. */ + affiliation?: string; + /** The coupon name/code associated with the item. */ + coupon?: string; + /** The unit monetary discount value associated with the item. */ + discount?: number; + /** The index/position of the item in a list. */ + index?: number; + /** The brand of the item. */ + item_brand?: string; + /** + * The category of the item. + * If used as part of a category hierarchy or taxonomy then this will be the first category. + */ + item_category?: string; + /** The second category hierarchy or additional taxonomy for the item. */ + item_category2?: string; + /** The third category hierarchy or additional taxonomy for the item. */ + item_category3?: string; + /** The fourth category hierarchy or additional taxonomy for the item. */ + item_category4?: string; + /** The fifth category hierarchy or additional taxonomy for the item. */ + item_category5?: string; + /** + * The ID of the list in which the item was presented to the user. + * If set, event-level item_list_id is ignored. + * If not set, event-level item_list_id is used, if present. + */ + item_list_id?: string; + /** + * The name of the list in which the item was presented to the user. + * If set, event-level item_list_name is ignored. + * If not set, event-level item_list_name is used, if present. + */ + item_list_name?: string; + /** The item variant or unique code or description for additional item details/options. */ + item_variant?: string; + /** + * The physical location associated with the item (e.g. the physical store location). + * Recommended to use Google Place ID. Custom IDs are allowed. + * Note: `location_id` is only available at the item-scope. + */ + location_id?: string; + /** + * The monetary unit price of the item, in units of the specified currency. + * If a discount applies, set this to the discounted unit price and specify the discount separately. + */ + price?: number; + /** Item quantity. Defaults to 1 if not set. */ + quantity?: number; + /** + * The name of the promotional creative slot associated with the item. + * If set, event-level creative_slot is ignored. + */ + creative_slot?: string; + /** + * The name of the promotional creative. + * If set, event-level creative_name is ignored. + */ + creative_name?: string; + /** + * The ID of the promotion associated with the item. + * If set, event-level promotion_id is ignored. + */ + promotion_id?: string; + /** + * The name of the promotion associated with the item. + * If set, event-level promotion_name is ignored. + */ + promotion_name?: string; +}; + +export type GtagEcommerceParams = { + /** The unique transaction ID (order ID). */ + transaction_id?: string; + /** The total monetary value of the event. */ + value?: number; + /** The currency of the transaction, in ISO 4217 3-letter format (e.g., "USD"). */ + currency?: string; + /** The coupon name/code applied to the event. */ + coupon?: string; + /** The shipping cost associated with the transaction. */ + shipping?: number; + /** The total tax amount associated with the transaction. */ + tax?: number; + /** Array of items associated with the event. */ + items?: GtagItem[]; + /** Payment method used for the transaction (e.g., "Credit Card", "PayPal"). */ + payment_type?: string; + /** + * The name of the promotional creative associated with the event. + * Ignored if set at the item-level. + */ + creative_name?: string; + /** + * The creative slot name associated with the event. + * Ignored if set at the item-level. + */ + creative_slot?: string; + /** + * The ID of the promotion associated with the event. + * Ignored if set at the item-level. + */ + promotion_id?: string; + /** + * The name of the promotion associated with the event. + * Ignored if set at the item-level. + */ + promotion_name?: string; +}; + +type GtagPromotion = { + /** + * The name of the promotional creative. + * If set, event-level creative_name is ignored. + */ + creative_name?: string; + /** + * The name of the promotional creative slot associated with the item. + * If set, event-level creative_slot is ignored. + */ + creative_slot?: string; + /** + * The ID of the promotion associated with the item. + * If set, event-level promotion_id is ignored. + */ + promotion_id?: string; + /** + * The name of the promotion associated with the item. + * If set, event-level promotion_name is ignored. + */ + promotion_name?: string; +}; + +export type GtagFieldNames = "client_id" | "session_id" | "gclid"; + +export type GtagConsentMode = "granted" | "denied"; +export type GtagConsentArg = "default" | "update"; + +export type GtagConsentParams = { + /** Consent for ad personalization. */ + ad_personalization?: GtagConsentMode; + /** Consent for ad user data. */ + ad_user_data?: GtagConsentMode; + /** Consent for ad storage. */ + ad_storage?: GtagConsentMode; + /** Consent for analytics storage. */ + analytics_storage?: GtagConsentMode; + /** Consent for functionality storage. */ + functionality_storage?: GtagConsentMode; + /** Consent for personalization storage. */ + personalization_storage?: GtagConsentMode; + /** Consent for security storage. */ + security_storage?: GtagConsentMode; + /** Time to wait for consent update in milliseconds. */ + wait_for_update?: number; + /** The regions where the consent applies. */ + region?: string[]; +}; diff --git a/src/utils.js b/src/utils.js deleted file mode 100644 index c36a4d58..00000000 --- a/src/utils.js +++ /dev/null @@ -1,102 +0,0 @@ -export const load = (url, options = {}) => { - return new Promise((resolve, reject) => { - if (typeof document === "undefined") { - return; - } - - const head = document.head || document.getElementsByTagName("head")[0]; - const script = document.createElement("script"); - - script.async = true; - script.src = url; - script.defer = options.defer; - - if (options.preconnectOrigin) { - const link = document.createElement("link"); - - link.href = options.preconnectOrigin; - link.rel = "preconnect"; - - head.appendChild(link); - } - - head.appendChild(script); - - script.onload = resolve; - script.onerror = reject; - }); -}; - -export const isFn = (fn) => typeof fn === "function"; - -export const isObject = (item) => { - return item && typeof item === "object" && !Array.isArray(item); -}; - -export const mergeDeep = (target, ...sources) => { - if (!sources.length) { - return target; - } - - const source = sources.shift(); - - if (!isObject(target) || !isObject(source)) { - return; - } - - for (const key in source) { - if (isObject(source[key])) { - if (!target[key]) { - Object.assign(target, { [key]: {} }); - } - - mergeDeep(target[key], source[key]); - } else { - Object.assign(target, { [key]: source[key] }); - } - } - - return mergeDeep(target, ...sources); -}; - -export const isBrowser = () => { - if (typeof window === "undefined" || typeof document === "undefined") { - return false; - } - - return true; -}; - -export const warn = (text, shouldLog = true) => { - if (!isBrowser() || process.env.NODE_ENV === "production") { - return; - } - - if (!shouldLog) { - return; - } - - console.warn(`[vue-gtag] ${text}`); -}; - -export const validateScreenviewShape = (obj = {}) => { - warn( - `Missing "appName" property inside the plugin options.`, - obj.app_name == null, - ); - - warn(`Missing "name" property in the route.`, obj.screen_name == null); - - return obj; -}; - -export function getPathWithBase(path = "", base = "") { - const pathAsArray = path.split("/"); - const baseAsArray = base.split("/"); - - if (pathAsArray[0] === "" && base[base.length - 1] === "/") { - pathAsArray.shift(); - } - - return baseAsArray.join("/") + pathAsArray.join("/"); -} diff --git a/src/utils.test.ts b/src/utils.test.ts new file mode 100644 index 00000000..a8c96a39 --- /dev/null +++ b/src/utils.test.ts @@ -0,0 +1,59 @@ +import flushPromises from "flush-promises"; +import * as utils from "./utils"; + +describe("utils", () => { + describe("injectScript", () => { + afterEach(() => { + document.getElementsByTagName("html")[0].innerHTML = ""; + }); + + it("should create a script tag", async () => { + utils.injectScript("foo"); + + await flushPromises(); + + const scripts = document.getElementsByTagName("script"); + + expect(scripts.length).toEqual(1); + expect(scripts[0].getAttribute("src")).toEqual("foo"); + expect(scripts[0].defer).toEqual(false); + }); + + it("should create a script tag with the defer attribute", async () => { + utils.injectScript("foo", { + defer: true, + }); + + await flushPromises(); + + const scripts = document.getElementsByTagName("script"); + + expect(scripts[0].defer).toEqual(true); + }); + + it("should create a link for domain preconnect", async () => { + utils.injectScript("https://www.google.com/something", { + preconnect: true, + }); + + await flushPromises(); + + const links = document.getElementsByTagName("link"); + + expect(links[0].getAttribute("href")).toEqual("https://www.google.com"); + expect(links[0].getAttribute("rel")).toEqual("preconnect"); + }); + + it("should accept the nonce attribute", async () => { + utils.injectScript("https://www.google.com/something", { + nonce: "abcd", + }); + + await flushPromises(); + + const scripts = document.getElementsByTagName("script"); + + expect(scripts[0].getAttribute("nonce")).toEqual("abcd"); + }); + }); +}); diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 00000000..d261cc14 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,90 @@ +export function isServer(): boolean { + return typeof window === "undefined" || typeof document === "undefined"; +} + +export async function injectScript( + url: string, + options?: { + preconnect?: boolean; + defer?: boolean; + nonce?: string; + }, +): Promise { + return new Promise((resolve, reject) => { + if (isServer()) { + return resolve(); + } + + const head = document.head; + const script = document.createElement("script"); + + script.async = true; + script.src = url; + + if (options?.defer) { + script.defer = true; + } + + if (options?.nonce) { + script.setAttribute("nonce", options.nonce); + } + + if (options?.preconnect) { + const link = document.createElement("link"); + + const resource = new URL(url); + + link.href = resource.origin; + link.rel = "preconnect"; + + head.appendChild(link); + } + + head.appendChild(script); + + script.onload = () => resolve(); + script.onerror = reject; + }); +} + +export function hasGtag(): boolean { + if (isServer()) { + return false; + } + + const match = document.querySelector( + "script[src*='googletagmanager.com/gtag/js']", + ); + + return match !== null; +} + +type DeepMergeable = { + [key: string]: unknown; +}; + +function isObject(obj: unknown): obj is DeepMergeable { + return obj !== null && typeof obj === "object" && !Array.isArray(obj); +} + +export function deepMerge( + target: T, + source: U, +): T & U { + const output: DeepMergeable = { ...target }; + + for (const key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + const sourceValue = source[key]; + const targetValue = target[key]; + + if (isObject(sourceValue) && isObject(targetValue)) { + output[key] = deepMerge(targetValue, sourceValue); + } else { + output[key] = sourceValue; + } + } + } + + return output as T & U; +} diff --git a/test/__snapshots__/options.spec.js.snap b/test/__snapshots__/options.spec.js.snap deleted file mode 100644 index 47db901a..00000000 --- a/test/__snapshots__/options.spec.js.snap +++ /dev/null @@ -1,67 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`options > getDefaultParams 1`] = ` -{ - "appName": null, - "bootstrap": true, - "config": { - "id": null, - "params": { - "send_page_view": false, - }, - }, - "customPreconnectOrigin": "https://www.googletagmanager.com", - "customResourceURL": "https://www.googletagmanager.com/gtag/js", - "defaultGroupName": "default", - "deferScriptLoad": false, - "disableScriptLoad": false, - "enabled": true, - "globalDataLayerName": "dataLayer", - "globalObjectName": "gtag", - "includes": null, - "onAfterTrack": null, - "onBeforeTrack": null, - "onError": null, - "onReady": null, - "pageTrackerEnabled": true, - "pageTrackerExcludedRoutes": [], - "pageTrackerPrependBase": true, - "pageTrackerScreenviewEnabled": false, - "pageTrackerSkipSamePath": true, - "pageTrackerTemplate": null, - "pageTrackerUseFullPath": false, -} -`; - -exports[`options > set and get options 1`] = ` -{ - "appName": null, - "bootstrap": true, - "config": { - "id": 1, - "params": { - "send_page_view": false, - }, - }, - "customPreconnectOrigin": "https://www.googletagmanager.com", - "customResourceURL": "https://www.googletagmanager.com/gtag/js", - "defaultGroupName": "default", - "deferScriptLoad": false, - "disableScriptLoad": false, - "enabled": true, - "globalDataLayerName": "dataLayer", - "globalObjectName": "gtag", - "includes": null, - "onAfterTrack": null, - "onBeforeTrack": null, - "onError": null, - "onReady": null, - "pageTrackerEnabled": true, - "pageTrackerExcludedRoutes": [], - "pageTrackerPrependBase": true, - "pageTrackerScreenviewEnabled": false, - "pageTrackerSkipSamePath": true, - "pageTrackerTemplate": null, - "pageTrackerUseFullPath": false, -} -`; diff --git a/test/__snapshots__/utils.spec.js.snap b/test/__snapshots__/utils.spec.js.snap deleted file mode 100644 index 5ca45051..00000000 --- a/test/__snapshots__/utils.spec.js.snap +++ /dev/null @@ -1,30 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`load > should create a link for domain preconnect 1`] = ` - - -