diff --git a/CRUSH.md b/CRUSH.md
deleted file mode 120000
index 47dc3e3d8..000000000
--- a/CRUSH.md
+++ /dev/null
@@ -1 +0,0 @@
-AGENTS.md
\ No newline at end of file
diff --git a/desktop/package.json b/desktop/package.json
index 765d91054..1aaabf2c2 100644
--- a/desktop/package.json
+++ b/desktop/package.json
@@ -61,8 +61,8 @@
}
},
"devDependencies": {
- "electron": "40.10.3",
- "electron-builder": "26.15.2",
+ "electron": "40.10.4",
+ "electron-builder": "26.15.3",
"unzipper": "0.12.3"
},
"dependencies": {
@@ -74,11 +74,13 @@
],
"overrides": {
"minimatch": "^10.2.3",
- "tar": "^7.5.11",
+ "tar": ">=7.5.16",
"@tootallnate/once": "^3.0.1",
"picomatch": ">=4.0.4",
- "tmp": ">=0.2.6",
- "ip-address": ">=10.1.1"
+ "tmp": ">=0.2.7",
+ "ip-address": ">=10.1.1",
+ "form-data": ">=4.0.6",
+ "js-yaml": ">=4.2.0"
}
}
}
diff --git a/desktop/pnpm-lock.yaml b/desktop/pnpm-lock.yaml
index 77a74681b..248ad6337 100644
--- a/desktop/pnpm-lock.yaml
+++ b/desktop/pnpm-lock.yaml
@@ -6,11 +6,13 @@ settings:
overrides:
minimatch: ^10.2.3
- tar: ^7.5.11
+ tar: '>=7.5.16'
'@tootallnate/once': ^3.0.1
picomatch: '>=4.0.4'
- tmp: '>=0.2.6'
+ tmp: '>=0.2.7'
ip-address: '>=10.1.1'
+ form-data: '>=4.0.6'
+ js-yaml: '>=4.2.0'
importers:
@@ -21,11 +23,11 @@ importers:
version: 5.2.1
devDependencies:
electron:
- specifier: 40.10.3
- version: 40.10.3
+ specifier: 40.10.4
+ version: 40.10.4
electron-builder:
- specifier: 26.15.2
- version: 26.15.2(electron-builder-squirrel-windows@24.13.3)
+ specifier: 26.15.3
+ version: 26.15.3(electron-builder-squirrel-windows@24.13.3)
unzipper:
specifier: 0.12.3
version: 0.12.3
@@ -234,12 +236,12 @@ packages:
dmg-builder: 24.13.3
electron-builder-squirrel-windows: 24.13.3
- app-builder-lib@26.15.2:
- resolution: {integrity: sha512-3mYfKOjr/ZY7gFESOcq8kylBMgGPpmlQYnpBVit4p6zIg0t/8bkWBILdMMtnjFyN2jllyBf225T8dLlz3D6oBQ==}
+ app-builder-lib@26.15.3:
+ resolution: {integrity: sha512-2VnyWkqsP5v5XbBhL3tD5Syx8iNPBYsoU7kY4S2fz7wg8Rj/nztWKCUzGKaFRTv0Xwf3/H058CR1Kvtd/3lRow==}
engines: {node: '>=14.0.0'}
peerDependencies:
- dmg-builder: 26.15.2
- electron-builder-squirrel-windows: 26.15.2
+ dmg-builder: 26.15.3
+ electron-builder-squirrel-windows: 26.15.3
archiver-utils@2.1.0:
resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==}
@@ -329,8 +331,8 @@ packages:
builder-util@24.13.1:
resolution: {integrity: sha512-NhbCSIntruNDTOVI9fdXz0dihaqX2YuE1D6zZMrwiErzH4ELZHE6mdiB40wEgZNprDia+FghRFgKoAqMZRRjSA==}
- builder-util@26.15.0:
- resolution: {integrity: sha512-dUx+HxVbiNsNQ4mGe1PyoC/tBmsHwBNDLdBuqWCj+rhHFE9lHgrXiGYKAM1uNlznhAaUSyMlms84VeSSr3gOBA==}
+ builder-util@26.15.3:
+ resolution: {integrity: sha512-q2hn7Mbo2nFNkVekPiHFx6Nfo3hURmES3tfBn+k5Pqxl2RkmP3QGqZUhH/q9Pch/4G05NRhPjDlVj1O8q4Txvw==}
engines: {node: '>=14.0.0'}
bytes@3.1.2:
@@ -483,8 +485,8 @@ packages:
dir-compare@4.2.0:
resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==}
- dmg-builder@26.15.2:
- resolution: {integrity: sha512-fMkjRqKyPtsz4Kzu/qGP0BGjqzMCIgp+/7kw/u6YH6lvn/8hvL3c0TXhoFayBoYdpPCnEinnCHztd4bW7/jetA==}
+ dmg-builder@26.15.3:
+ resolution: {integrity: sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==}
dotenv-expand@11.0.6:
resolution: {integrity: sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==}
@@ -522,19 +524,19 @@ packages:
electron-builder-squirrel-windows@24.13.3:
resolution: {integrity: sha512-oHkV0iogWfyK+ah9ZIvMDpei1m9ZRpdXcvde1wTpra2U8AFDNNpqJdnin5z+PM1GbQ5BoaKCWas2HSjtR0HwMg==}
- electron-builder@26.15.2:
- resolution: {integrity: sha512-veKM9+dCljaC5A74Pwc0ZWQ9arOHREXWh9hUIf8NGg49ch7x+IB4QhbMzIrV5ONZIXM2OEkaxW11cAPjPtoi4A==}
+ electron-builder@26.15.3:
+ resolution: {integrity: sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA==}
engines: {node: '>=14.0.0'}
hasBin: true
electron-publish@24.13.1:
resolution: {integrity: sha512-2ZgdEqJ8e9D17Hwp5LEq5mLQPjqU3lv/IALvgp+4W8VeNhryfGhYEQC/PgDPMrnWUp+l60Ou5SJLsu+k4mhQ8A==}
- electron-publish@26.15.1:
- resolution: {integrity: sha512-BMgMHOyexWn0UnOC+Afffw0DMrr0yfLp4U8YsLXwoJ3Da7LS7WUnz21teYZqO0gaApE1KgsjREWmbPqvF5JcPg==}
+ electron-publish@26.15.3:
+ resolution: {integrity: sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q==}
- electron@40.10.3:
- resolution: {integrity: sha512-DdWRsHm4j5wH9TMcfnB2Dqx44G/6BgLKSG/oeRe9kS60pfqCUwzUkHk0ClwvZzBVXtJ1kcdkHVRrJsl1ooKp+g==}
+ electron@40.10.4:
+ resolution: {integrity: sha512-ouNZrXXmdPL/wiTQ+xzXpb7B/BHg+j7XARig0SE7azFO3bjbYUd6lFjIAAiDQ02Pl/Oj7MUk+4C0hdf9yFtA1A==}
engines: {node: '>= 22.12.0'}
hasBin: true
@@ -632,8 +634,8 @@ packages:
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
engines: {node: '>=14'}
- form-data@4.0.5:
- resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
+ form-data@4.0.6:
+ resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==}
engines: {node: '>= 6'}
forwarded@0.2.0:
@@ -736,6 +738,10 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
+ hasown@2.0.4:
+ resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
+ engines: {node: '>= 0.4'}
+
hosted-git-info@4.1.0:
resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==}
engines: {node: '>=10'}
@@ -830,8 +836,8 @@ packages:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
- js-yaml@4.1.1:
- resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
+ js-yaml@4.2.0:
+ resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==}
hasBin: true
json-buffer@3.0.1:
@@ -1298,8 +1304,8 @@ packages:
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
engines: {node: '>=6'}
- tar@7.5.15:
- resolution: {integrity: sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==}
+ tar@7.5.16:
+ resolution: {integrity: sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==}
engines: {node: '>=18'}
temp-file@3.4.0:
@@ -1315,8 +1321,8 @@ packages:
tmp-promise@3.0.3:
resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==}
- tmp@0.2.6:
- resolution: {integrity: sha512-5sJPdPjfI5Kx+qbrDesxkglRBxW//g7hCsqspEjwkewGvBMGIKMOTKzLt1hFVJzyadba3lDUN20O9qhvbQUSTA==}
+ tmp@0.2.7:
+ resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==}
engines: {node: '>=14.14'}
toidentifier@1.0.1:
@@ -1711,7 +1717,7 @@ snapshots:
app-builder-bin@4.0.0: {}
- app-builder-lib@24.13.3(dmg-builder@26.15.2)(electron-builder-squirrel-windows@24.13.3):
+ app-builder-lib@24.13.3(dmg-builder@26.15.3)(electron-builder-squirrel-windows@24.13.3):
dependencies:
'@develar/schema-utils': 2.6.5
'@electron/notarize': 2.2.1
@@ -1725,27 +1731,27 @@ snapshots:
builder-util-runtime: 9.2.4
chromium-pickle-js: 0.2.0
debug: 4.4.3
- dmg-builder: 26.15.2(electron-builder-squirrel-windows@24.13.3)
+ dmg-builder: 26.15.3(electron-builder-squirrel-windows@24.13.3)
ejs: 3.1.10
- electron-builder-squirrel-windows: 24.13.3(dmg-builder@26.15.2)
+ electron-builder-squirrel-windows: 24.13.3(dmg-builder@26.15.3)
electron-publish: 24.13.1
- form-data: 4.0.5
+ form-data: 4.0.6
fs-extra: 10.1.0
hosted-git-info: 4.1.0
is-ci: 3.0.1
isbinaryfile: 5.0.7
- js-yaml: 4.1.1
+ js-yaml: 4.2.0
lazy-val: 1.0.5
minimatch: 10.2.5
read-config-file: 6.3.2
sanitize-filename: 1.6.4
semver: 7.8.1
- tar: 7.5.15
+ tar: 7.5.16
temp-file: 3.4.0
transitivePeerDependencies:
- supports-color
- app-builder-lib@26.15.2(dmg-builder@26.15.2)(electron-builder-squirrel-windows@24.13.3):
+ app-builder-lib@26.15.3(dmg-builder@26.15.3)(electron-builder-squirrel-windows@24.13.3):
dependencies:
'@electron/asar': 3.4.1
'@electron/fuses': 1.8.0
@@ -1761,22 +1767,22 @@ snapshots:
ajv: 8.20.0
asn1js: 3.0.10
async-exit-hook: 2.0.1
- builder-util: 26.15.0
+ builder-util: 26.15.3
builder-util-runtime: 9.7.0
chromium-pickle-js: 0.2.0
ci-info: 4.3.1
debug: 4.4.3
- dmg-builder: 26.15.2(electron-builder-squirrel-windows@24.13.3)
+ dmg-builder: 26.15.3(electron-builder-squirrel-windows@24.13.3)
dotenv: 16.4.5
dotenv-expand: 11.0.6
ejs: 3.1.10
- electron-builder-squirrel-windows: 24.13.3(dmg-builder@26.15.2)
- electron-publish: 26.15.1
+ electron-builder-squirrel-windows: 24.13.3(dmg-builder@26.15.3)
+ electron-publish: 26.15.3
fs-extra: 10.1.0
hosted-git-info: 4.1.0
isbinaryfile: 5.0.7
jiti: 2.6.1
- js-yaml: 4.1.1
+ js-yaml: 4.2.0
json5: 2.2.3
lazy-val: 1.0.5
minimatch: 10.2.5
@@ -1785,7 +1791,7 @@ snapshots:
proper-lockfile: 4.1.2
resedit: 1.7.2
semver: 7.7.4
- tar: 7.5.15
+ tar: 7.5.16
temp-file: 3.4.0
tiny-async-pool: 1.3.0
unzipper: 0.12.3
@@ -1923,14 +1929,14 @@ snapshots:
http-proxy-agent: 5.0.0
https-proxy-agent: 5.0.1
is-ci: 3.0.1
- js-yaml: 4.1.1
+ js-yaml: 4.2.0
source-map-support: 0.5.21
stat-mode: 1.0.0
temp-file: 3.4.0
transitivePeerDependencies:
- supports-color
- builder-util@26.15.0:
+ builder-util@26.15.3:
dependencies:
'@types/debug': 4.1.13
builder-util-runtime: 9.7.0
@@ -1940,7 +1946,7 @@ snapshots:
fs-extra: 10.1.0
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.5
- js-yaml: 4.1.1
+ js-yaml: 4.2.0
sanitize-filename: 1.6.4
source-map-support: 0.5.21
stat-mode: 1.0.0
@@ -2088,12 +2094,12 @@ snapshots:
minimatch: 10.2.5
p-limit: 3.1.0
- dmg-builder@26.15.2(electron-builder-squirrel-windows@24.13.3):
+ dmg-builder@26.15.3(electron-builder-squirrel-windows@24.13.3):
dependencies:
- app-builder-lib: 26.15.2(dmg-builder@26.15.2)(electron-builder-squirrel-windows@24.13.3)
- builder-util: 26.15.0
+ app-builder-lib: 26.15.3(dmg-builder@26.15.3)(electron-builder-squirrel-windows@24.13.3)
+ builder-util: 26.15.3
fs-extra: 10.1.0
- js-yaml: 4.1.1
+ js-yaml: 4.2.0
transitivePeerDependencies:
- electron-builder-squirrel-windows
- supports-color
@@ -2126,9 +2132,9 @@ snapshots:
dependencies:
jake: 10.8.7
- electron-builder-squirrel-windows@24.13.3(dmg-builder@26.15.2):
+ electron-builder-squirrel-windows@24.13.3(dmg-builder@26.15.3):
dependencies:
- app-builder-lib: 24.13.3(dmg-builder@26.15.2)(electron-builder-squirrel-windows@24.13.3)
+ app-builder-lib: 24.13.3(dmg-builder@26.15.3)(electron-builder-squirrel-windows@24.13.3)
archiver: 5.3.2
builder-util: 24.13.1
fs-extra: 10.1.0
@@ -2136,14 +2142,14 @@ snapshots:
- dmg-builder
- supports-color
- electron-builder@26.15.2(electron-builder-squirrel-windows@24.13.3):
+ electron-builder@26.15.3(electron-builder-squirrel-windows@24.13.3):
dependencies:
- app-builder-lib: 26.15.2(dmg-builder@26.15.2)(electron-builder-squirrel-windows@24.13.3)
- builder-util: 26.15.0
+ app-builder-lib: 26.15.3(dmg-builder@26.15.3)(electron-builder-squirrel-windows@24.13.3)
+ builder-util: 26.15.3
builder-util-runtime: 9.7.0
chalk: 4.1.2
ci-info: 4.3.1
- dmg-builder: 26.15.2(electron-builder-squirrel-windows@24.13.3)
+ dmg-builder: 26.15.3(electron-builder-squirrel-windows@24.13.3)
fs-extra: 10.1.0
lazy-val: 1.0.5
simple-update-notifier: 2.0.0
@@ -2164,21 +2170,21 @@ snapshots:
transitivePeerDependencies:
- supports-color
- electron-publish@26.15.1:
+ electron-publish@26.15.3:
dependencies:
'@types/fs-extra': 9.0.13
aws4: 1.13.2
- builder-util: 26.15.0
+ builder-util: 26.15.3
builder-util-runtime: 9.7.0
chalk: 4.1.2
- form-data: 4.0.5
+ form-data: 4.0.6
fs-extra: 10.1.0
lazy-val: 1.0.5
mime: 2.6.0
transitivePeerDependencies:
- supports-color
- electron@40.10.3:
+ electron@40.10.4:
dependencies:
'@electron-internal/extract-zip': 1.0.2
'@electron/get': 5.0.0
@@ -2215,7 +2221,7 @@ snapshots:
es-errors: 1.3.0
get-intrinsic: 1.3.0
has-tostringtag: 1.0.2
- hasown: 2.0.2
+ hasown: 2.0.4
es6-error@4.1.1:
optional: true
@@ -2294,12 +2300,12 @@ snapshots:
cross-spawn: 7.0.6
signal-exit: 4.1.0
- form-data@4.0.5:
+ form-data@4.0.6:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
es-set-tostringtag: 2.1.0
- hasown: 2.0.2
+ hasown: 2.0.4
mime-types: 2.1.35
forwarded@0.2.0: {}
@@ -2436,6 +2442,10 @@ snapshots:
dependencies:
function-bind: 1.1.2
+ hasown@2.0.4:
+ dependencies:
+ function-bind: 1.1.2
+
hosted-git-info@4.1.0:
dependencies:
lru-cache: 6.0.0
@@ -2534,7 +2544,7 @@ snapshots:
jiti@2.6.1: {}
- js-yaml@4.1.1:
+ js-yaml@4.2.0:
dependencies:
argparse: 2.0.1
@@ -2656,7 +2666,7 @@ snapshots:
nopt: 9.0.0
proc-log: 6.1.0
semver: 7.8.1
- tar: 7.5.15
+ tar: 7.5.16
tinyglobby: 0.2.15
undici: 6.26.0
which: 6.0.1
@@ -2791,7 +2801,7 @@ snapshots:
config-file-ts: 0.2.6
dotenv: 9.0.2
dotenv-expand: 5.1.0
- js-yaml: 4.1.1
+ js-yaml: 4.2.0
json5: 2.2.3
lazy-val: 1.0.5
@@ -3008,7 +3018,7 @@ snapshots:
inherits: 2.0.4
readable-stream: 3.6.2
- tar@7.5.15:
+ tar@7.5.16:
dependencies:
'@isaacs/fs-minipass': 4.0.1
chownr: 3.0.0
@@ -3032,9 +3042,9 @@ snapshots:
tmp-promise@3.0.3:
dependencies:
- tmp: 0.2.6
+ tmp: 0.2.7
- tmp@0.2.6: {}
+ tmp@0.2.7: {}
toidentifier@1.0.1: {}
diff --git a/frontend/package.json b/frontend/package.json
index b5243e44f..fdafc836a 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -82,7 +82,7 @@
"bulma-css-variables": "0.9.33",
"change-case": "5.4.4",
"dayjs": "1.11.19",
- "dompurify": "3.4.0",
+ "dompurify": "3.4.9",
"fast-deep-equal": "3.1.3",
"flatpickr": "4.6.13",
"floating-vue": "5.2.2",
@@ -105,20 +105,20 @@
"zhyswan-vuedraggable": "4.1.3"
},
"devDependencies": {
- "@faker-js/faker": "10.4.0",
+ "@faker-js/faker": "10.5.0",
"@histoire/plugin-screenshot": "1.0.0-beta.1",
"@histoire/plugin-vue": "1.0.0-beta.1",
"@playwright/test": "1.58.2",
"@sentry/vite-plugin": "3.6.1",
- "@tailwindcss/vite": "4.3.0",
+ "@tailwindcss/vite": "4.3.1",
"@tsconfig/node24": "24.0.4",
"@types/codemirror": "5.60.17",
"@types/is-touch-device": "1.0.3",
"@types/node": "24.13.2",
"@types/sortablejs": "1.15.9",
"@types/ws": "8.18.1",
- "@typescript-eslint/eslint-plugin": "8.61.0",
- "@typescript-eslint/parser": "8.61.0",
+ "@typescript-eslint/eslint-plugin": "8.61.1",
+ "@typescript-eslint/parser": "8.61.1",
"@vitejs/plugin-vue": "6.0.7",
"@vue/eslint-config-typescript": "14.8.0",
"@vue/test-utils": "2.4.11",
@@ -128,18 +128,18 @@
"browserslist": "4.28.2",
"caniuse-lite": "1.0.30001799",
"csstype": "3.2.3",
- "esbuild": "0.28.0",
+ "esbuild": "0.28.1",
"eslint": "9.39.4",
"eslint-plugin-depend": "1.5.0",
"eslint-plugin-vue": "10.9.2",
- "happy-dom": "20.10.2",
+ "happy-dom": "20.10.5",
"histoire": "1.0.0-beta.1",
"otplib": "12.0.1",
"postcss": "8.5.15",
"postcss-easing-gradients": "3.0.1",
"postcss-html": "1.8.1",
- "postcss-preset-env": "11.3.0",
- "rollup": "4.61.1",
+ "postcss-preset-env": "11.3.1",
+ "rollup": "4.62.0",
"rollup-plugin-visualizer": "6.0.11",
"sass-embedded": "1.100.0",
"stylelint": "17.13.0",
@@ -147,15 +147,15 @@
"stylelint-config-recommended-vue": "1.6.1",
"stylelint-config-standard-scss": "17.0.0",
"stylelint-use-logical": "2.1.3",
- "tailwindcss": "4.3.0",
+ "tailwindcss": "4.3.1",
"typescript": "5.9.3",
"unplugin-inject-preload": "3.0.0",
"vite": "7.3.5",
"vite-plugin-pwa": "1.3.0",
- "vite-plugin-vue-devtools": "8.1.2",
+ "vite-plugin-vue-devtools": "8.1.3",
"vite-svg-loader": "5.1.1",
- "vitest": "4.1.8",
- "vue-tsc": "3.3.4",
+ "vitest": "4.1.9",
+ "vue-tsc": "3.3.5",
"wait-on": "9.0.10",
"workbox-cli": "7.4.1",
"ws": "8.21.0"
@@ -176,7 +176,13 @@
"flatted": "^3.4.1",
"ip-address": ">=10.1.1",
"postcss": ">=8.5.10",
- "tmp": ">=0.2.6"
+ "tmp": ">=0.2.7",
+ "esbuild": ">=0.28.1",
+ "form-data": ">=4.0.6",
+ "markdown-it": ">=14.2.0",
+ "launch-editor": ">=2.14.1",
+ "@babel/core": ">=7.29.6",
+ "js-yaml@4": ">=4.2.0"
}
}
}
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 2369c33e4..8fa9e43e6 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -6,13 +6,19 @@ settings:
overrides:
minimatch: ^10.2.3
- rollup: 4.61.1
+ rollup: 4.62.0
basic-ftp: '>=5.2.2'
serialize-javascript: ^7.0.5
flatted: ^3.4.1
ip-address: '>=10.1.1'
postcss: '>=8.5.10'
- tmp: '>=0.2.6'
+ tmp: '>=0.2.7'
+ esbuild: '>=0.28.1'
+ form-data: '>=4.0.6'
+ markdown-it: '>=14.2.0'
+ launch-editor: '>=2.14.1'
+ '@babel/core': '>=7.29.6'
+ js-yaml@4: '>=4.2.0'
importers:
@@ -35,7 +41,7 @@ importers:
version: 3.1.3(@fortawesome/fontawesome-svg-core@7.1.0)(vue@3.5.27(typescript@5.9.3))
'@intlify/unplugin-vue-i18n':
specifier: 11.0.3
- version: 11.0.3(@vue/compiler-dom@3.5.27)(eslint@9.39.4(jiti@2.6.1))(rollup@4.61.1)(typescript@5.9.3)(vue-i18n@11.2.8(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
+ version: 11.0.3(@vue/compiler-dom@3.5.27)(eslint@9.39.4(jiti@2.6.1))(rollup@4.62.0)(typescript@5.9.3)(vue-i18n@11.2.8(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
'@kyvg/vue3-notification':
specifier: 3.4.2
version: 3.4.2(vue@3.5.27(typescript@5.9.3))
@@ -112,8 +118,8 @@ importers:
specifier: 1.11.19
version: 1.11.19
dompurify:
- specifier: 3.4.0
- version: 3.4.0
+ specifier: 3.4.9
+ version: 3.4.9
fast-deep-equal:
specifier: 3.1.3
version: 3.1.3
@@ -176,8 +182,8 @@ importers:
version: 4.1.3(vue@3.5.27(typescript@5.9.3))
devDependencies:
'@faker-js/faker':
- specifier: 10.4.0
- version: 10.4.0
+ specifier: 10.5.0
+ version: 10.5.0
'@histoire/plugin-screenshot':
specifier: 1.0.0-beta.1
version: 1.0.0-beta.1(histoire@1.0.0-beta.1(@types/node@24.13.2)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3))(typescript@5.9.3)
@@ -191,8 +197,8 @@ importers:
specifier: 3.6.1
version: 3.6.1
'@tailwindcss/vite':
- specifier: 4.3.0
- version: 4.3.0(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))
+ specifier: 4.3.1
+ version: 4.3.1(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))
'@tsconfig/node24':
specifier: 24.0.4
version: 24.0.4
@@ -212,17 +218,17 @@ importers:
specifier: 8.18.1
version: 8.18.1
'@typescript-eslint/eslint-plugin':
- specifier: 8.61.0
- version: 8.61.0(@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
+ specifier: 8.61.1
+ version: 8.61.1(@typescript-eslint/parser@8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/parser':
- specifier: 8.61.0
- version: 8.61.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
+ specifier: 8.61.1
+ version: 8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
'@vitejs/plugin-vue':
specifier: 6.0.7
version: 6.0.7(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(vue@3.5.27(typescript@5.9.3))
'@vue/eslint-config-typescript':
specifier: 14.8.0
- version: 14.8.0(eslint-plugin-vue@10.9.2(@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
+ version: 14.8.0(eslint-plugin-vue@10.9.2(@typescript-eslint/parser@8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
'@vue/test-utils':
specifier: 2.4.11
version: 2.4.11(@vue/compiler-dom@3.5.27)(@vue/server-renderer@3.5.27(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
@@ -245,8 +251,8 @@ importers:
specifier: 3.2.3
version: 3.2.3
esbuild:
- specifier: 0.28.0
- version: 0.28.0
+ specifier: '>=0.28.1'
+ version: 0.28.1
eslint:
specifier: 9.39.4
version: 9.39.4(jiti@2.6.1)
@@ -255,10 +261,10 @@ importers:
version: 1.5.0(eslint@9.39.4(jiti@2.6.1))
eslint-plugin-vue:
specifier: 10.9.2
- version: 10.9.2(@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1)))
+ version: 10.9.2(@typescript-eslint/parser@8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1)))
happy-dom:
- specifier: 20.10.2
- version: 20.10.2
+ specifier: 20.10.5
+ version: 20.10.5
histoire:
specifier: 1.0.0-beta.1
version: 1.0.0-beta.1(@types/node@24.13.2)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3)
@@ -275,14 +281,14 @@ importers:
specifier: 1.8.1
version: 1.8.1
postcss-preset-env:
- specifier: 11.3.0
- version: 11.3.0(postcss@8.5.14)
+ specifier: 11.3.1
+ version: 11.3.1(postcss@8.5.14)
rollup:
- specifier: 4.61.1
- version: 4.61.1
+ specifier: 4.62.0
+ version: 4.62.0
rollup-plugin-visualizer:
specifier: 6.0.11
- version: 6.0.11(rollup@4.61.1)
+ version: 6.0.11(rollup@4.62.0)
sass-embedded:
specifier: 1.100.0
version: 1.100.0
@@ -302,8 +308,8 @@ importers:
specifier: 2.1.3
version: 2.1.3(stylelint@17.13.0(typescript@5.9.3))
tailwindcss:
- specifier: 4.3.0
- version: 4.3.0
+ specifier: 4.3.1
+ version: 4.3.1
typescript:
specifier: 5.9.3
version: 5.9.3
@@ -317,17 +323,17 @@ importers:
specifier: 1.3.0
version: 1.3.0(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(workbox-build@7.4.1)(workbox-window@7.4.1)
vite-plugin-vue-devtools:
- specifier: 8.1.2
- version: 8.1.2(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(vue@3.5.27(typescript@5.9.3))
+ specifier: 8.1.3
+ version: 8.1.3(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(vue@3.5.27(typescript@5.9.3))
vite-svg-loader:
specifier: 5.1.1
version: 5.1.1(vue@3.5.27(typescript@5.9.3))
vitest:
- specifier: 4.1.8
- version: 4.1.8(@types/node@24.13.2)(happy-dom@20.10.2)(jsdom@27.4.0)(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))
+ specifier: 4.1.9
+ version: 4.1.9(@types/node@24.13.2)(happy-dom@20.10.5)(jsdom@27.4.0)(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))
vue-tsc:
- specifier: 3.3.4
- version: 3.3.4(typescript@5.9.3)
+ specifier: 3.3.5
+ version: 3.3.5(typescript@5.9.3)
wait-on:
specifier: 9.0.10
version: 9.0.10
@@ -347,10 +353,6 @@ packages:
resolution: {integrity: sha512-nznEC1ZA/m3hQDEnrGQ4c5gkaa9pcaVnw4LFJyzBAaR7E3nfiAPEHS3otnSafpZouVnoKeITl5D+2LsnwlnK8g==}
engines: {node: '>=14.0.0'}
- '@ampproject/remapping@2.3.0':
- resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
- engines: {node: '>=6.0.0'}
-
'@apideck/better-ajv-errors@0.3.6':
resolution: {integrity: sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==}
engines: {node: '>=10'}
@@ -366,28 +368,28 @@ packages:
'@asamuzakjp/nwsapi@2.3.9':
resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==}
- '@babel/code-frame@7.26.2':
- resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
- engines: {node: '>=6.9.0'}
-
'@babel/code-frame@7.29.0':
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
engines: {node: '>=6.9.0'}
+ '@babel/code-frame@7.29.7':
+ resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/compat-data@7.26.0':
resolution: {integrity: sha512-qETICbZSLe7uXv9VE8T/RWOdIE5qqyTucOt4zLYMafj2MRO271VGgLd4RACJMeBO37UPWhXiKMBk7YlJ0fOzQA==}
engines: {node: '>=6.9.0'}
- '@babel/core@7.26.0':
- resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==}
+ '@babel/compat-data@7.29.7':
+ resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==}
engines: {node: '>=6.9.0'}
- '@babel/generator@7.26.0':
- resolution: {integrity: sha512-/AIkAmInnWwgEAJGQr9vY0c66Mj6kjkE2ZPB1PurTRaRAh3U+J45sAQMjQDJdh4WbR3l0x5xkimXBKyBXXAu2w==}
+ '@babel/core@7.29.7':
+ resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==}
engines: {node: '>=6.9.0'}
- '@babel/generator@7.29.1':
- resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==}
+ '@babel/generator@7.29.7':
+ resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==}
engines: {node: '>=6.9.0'}
'@babel/helper-annotate-as-pure@7.25.9':
@@ -402,25 +404,29 @@ packages:
resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-compilation-targets@7.29.7':
+ resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-create-class-features-plugin@7.25.9':
resolution: {integrity: sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0
+ '@babel/core': '>=7.29.6'
'@babel/helper-create-regexp-features-plugin@7.25.9':
resolution: {integrity: sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0
+ '@babel/core': '>=7.29.6'
'@babel/helper-define-polyfill-provider@0.6.2':
resolution: {integrity: sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==}
peerDependencies:
- '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
+ '@babel/core': '>=7.29.6'
- '@babel/helper-globals@7.28.0':
- resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+ '@babel/helper-globals@7.29.7':
+ resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==}
engines: {node: '>=6.9.0'}
'@babel/helper-member-expression-to-functions@7.25.9':
@@ -431,21 +437,15 @@ packages:
resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==}
engines: {node: '>=6.9.0'}
- '@babel/helper-module-imports@7.28.6':
- resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==}
+ '@babel/helper-module-imports@7.29.7':
+ resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==}
engines: {node: '>=6.9.0'}
- '@babel/helper-module-transforms@7.26.0':
- resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==}
+ '@babel/helper-module-transforms@7.29.7':
+ resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0
-
- '@babel/helper-module-transforms@7.28.6':
- resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0
+ '@babel/core': '>=7.29.6'
'@babel/helper-optimise-call-expression@7.25.9':
resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==}
@@ -463,13 +463,13 @@ packages:
resolution: {integrity: sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0
+ '@babel/core': '>=7.29.6'
'@babel/helper-replace-supers@7.25.9':
resolution: {integrity: sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0
+ '@babel/core': '>=7.29.6'
'@babel/helper-simple-access@7.25.9':
resolution: {integrity: sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==}
@@ -483,20 +483,32 @@ packages:
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-string-parser@7.29.7':
+ resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-validator-identifier@7.28.5':
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-validator-identifier@7.29.7':
+ resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-validator-option@7.25.9':
resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-validator-option@7.29.7':
+ resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-wrap-function@7.25.9':
resolution: {integrity: sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==}
engines: {node: '>=6.9.0'}
- '@babel/helpers@7.26.10':
- resolution: {integrity: sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==}
+ '@babel/helpers@7.29.7':
+ resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==}
engines: {node: '>=6.9.0'}
'@babel/parser@7.28.5':
@@ -504,8 +516,8 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
- '@babel/parser@7.29.3':
- resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==}
+ '@babel/parser@7.29.7':
+ resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==}
engines: {node: '>=6.0.0'}
hasBin: true
@@ -513,428 +525,420 @@ packages:
resolution: {integrity: sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9':
resolution: {integrity: sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9':
resolution: {integrity: sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9':
resolution: {integrity: sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.13.0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9':
resolution: {integrity: sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-proposal-decorators@7.25.9':
resolution: {integrity: sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2':
resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-syntax-decorators@7.25.9':
resolution: {integrity: sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-syntax-import-assertions@7.26.0':
resolution: {integrity: sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-syntax-import-attributes@7.26.0':
resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-syntax-import-meta@7.10.4':
resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-syntax-jsx@7.25.9':
resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-syntax-typescript@7.25.9':
resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-syntax-unicode-sets-regex@7.18.6':
resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-arrow-functions@7.25.9':
resolution: {integrity: sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-async-generator-functions@7.25.9':
resolution: {integrity: sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-async-to-generator@7.25.9':
resolution: {integrity: sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-block-scoped-functions@7.25.9':
resolution: {integrity: sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-block-scoping@7.25.9':
resolution: {integrity: sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-class-properties@7.25.9':
resolution: {integrity: sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-class-static-block@7.26.0':
resolution: {integrity: sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.12.0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-classes@7.25.9':
resolution: {integrity: sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-computed-properties@7.25.9':
resolution: {integrity: sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-destructuring@7.25.9':
resolution: {integrity: sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-dotall-regex@7.25.9':
resolution: {integrity: sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-duplicate-keys@7.25.9':
resolution: {integrity: sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9':
resolution: {integrity: sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-dynamic-import@7.25.9':
resolution: {integrity: sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-exponentiation-operator@7.25.9':
resolution: {integrity: sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-export-namespace-from@7.25.9':
resolution: {integrity: sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-for-of@7.25.9':
resolution: {integrity: sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-function-name@7.25.9':
resolution: {integrity: sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-json-strings@7.25.9':
resolution: {integrity: sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-literals@7.25.9':
resolution: {integrity: sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-logical-assignment-operators@7.25.9':
resolution: {integrity: sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-member-expression-literals@7.25.9':
resolution: {integrity: sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-modules-amd@7.25.9':
resolution: {integrity: sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-modules-commonjs@7.25.9':
resolution: {integrity: sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-modules-systemjs@7.29.4':
resolution: {integrity: sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-modules-umd@7.25.9':
resolution: {integrity: sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-named-capturing-groups-regex@7.25.9':
resolution: {integrity: sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-new-target@7.25.9':
resolution: {integrity: sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-nullish-coalescing-operator@7.25.9':
resolution: {integrity: sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-numeric-separator@7.25.9':
resolution: {integrity: sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-object-rest-spread@7.25.9':
resolution: {integrity: sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-object-super@7.25.9':
resolution: {integrity: sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-optional-catch-binding@7.25.9':
resolution: {integrity: sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-optional-chaining@7.25.9':
resolution: {integrity: sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-parameters@7.25.9':
resolution: {integrity: sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-private-methods@7.25.9':
resolution: {integrity: sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-private-property-in-object@7.25.9':
resolution: {integrity: sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-property-literals@7.25.9':
resolution: {integrity: sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-regenerator@7.25.9':
resolution: {integrity: sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-regexp-modifiers@7.26.0':
resolution: {integrity: sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-reserved-words@7.25.9':
resolution: {integrity: sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-shorthand-properties@7.25.9':
resolution: {integrity: sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-spread@7.25.9':
resolution: {integrity: sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-sticky-regex@7.25.9':
resolution: {integrity: sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-template-literals@7.25.9':
resolution: {integrity: sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-typeof-symbol@7.25.9':
resolution: {integrity: sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-typescript@7.25.9':
resolution: {integrity: sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-unicode-escapes@7.25.9':
resolution: {integrity: sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-unicode-property-regex@7.25.9':
resolution: {integrity: sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-unicode-regex@7.25.9':
resolution: {integrity: sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/plugin-transform-unicode-sets-regex@7.25.9':
resolution: {integrity: sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0
+ '@babel/core': '>=7.29.6'
'@babel/preset-env@7.26.0':
resolution: {integrity: sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==}
engines: {node: '>=6.9.0'}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@babel/preset-modules@0.1.6-no-external-plugins':
resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==}
peerDependencies:
- '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0
+ '@babel/core': '>=7.29.6'
'@babel/runtime@7.25.4':
resolution: {integrity: sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w==}
engines: {node: '>=6.9.0'}
- '@babel/template@7.26.9':
- resolution: {integrity: sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==}
+ '@babel/template@7.29.7':
+ resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==}
engines: {node: '>=6.9.0'}
- '@babel/template@7.28.6':
- resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
- engines: {node: '>=6.9.0'}
-
- '@babel/traverse@7.25.9':
- resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==}
- engines: {node: '>=6.9.0'}
-
- '@babel/traverse@7.29.0':
- resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==}
+ '@babel/traverse@7.29.7':
+ resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==}
engines: {node: '>=6.9.0'}
'@babel/types@7.28.5':
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
engines: {node: '>=6.9.0'}
- '@babel/types@7.29.0':
- resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
+ '@babel/types@7.29.7':
+ resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==}
engines: {node: '>=6.9.0'}
'@bufbuild/protobuf@2.5.2':
@@ -1003,8 +1007,8 @@ packages:
'@csstools/css-parser-algorithms': ^3.0.5
'@csstools/css-tokenizer': ^3.0.4
- '@csstools/css-color-parser@4.1.1':
- resolution: {integrity: sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==}
+ '@csstools/css-color-parser@4.1.7':
+ resolution: {integrity: sha512-CmjJFQTFQx/U/xNJhSjCQ0ilpesPmNQ8+eOUeM/+kDOVW33qsIjeOXc27vrQDdWVkf83ZSWwtg7kXSUvKDJ8cQ==}
engines: {node: '>=20.19.0'}
peerDependencies:
'@csstools/css-parser-algorithms': ^4.0.0
@@ -1053,8 +1057,8 @@ packages:
'@csstools/css-parser-algorithms': ^4.0.0
'@csstools/css-tokenizer': ^4.0.0
- '@csstools/postcss-alpha-function@2.0.5':
- resolution: {integrity: sha512-i2lNJ6b4GdMoybHlpUM07TIk8KQRXTTe7Qf8LfctQhjDRTIgaodWTQqzWU4fpWO/nxBWNkSloDM22Lw/30NBcg==}
+ '@csstools/postcss-alpha-function@2.0.6':
+ resolution: {integrity: sha512-XaMnJJqqZv4veulLELvM+5caEMcLTsFyqTrkwGKPMF+UbiM7dlQoe4K46EnwfSJIvnm91K1ZXsZSd3OuJ04p9w==}
engines: {node: '>=20.19.0'}
peerDependencies:
postcss: '>=8.5.10'
@@ -1065,26 +1069,26 @@ packages:
peerDependencies:
postcss: '>=8.5.10'
- '@csstools/postcss-color-function-display-p3-linear@2.0.4':
- resolution: {integrity: sha512-xrGqSFj9pu6XbJYD4NNCxYK9WFbf0KMfXFaisnJezkIRDZCwefUB2azkU4Zr0dFmLtIb9LlshrSZ0be1/QVthQ==}
+ '@csstools/postcss-color-function-display-p3-linear@2.0.5':
+ resolution: {integrity: sha512-YzY5qI0S/CsvqvMSiDn85ZyTCRLdnywxQn+6Fv8AU17aCE/fjcor54OSdVb/HlABBTcBq+d8NlWcLz11Bmo2mQ==}
engines: {node: '>=20.19.0'}
peerDependencies:
postcss: '>=8.5.10'
- '@csstools/postcss-color-function@5.0.4':
- resolution: {integrity: sha512-PhUu86ppxKcNHHqrJ43ZL1mYa2uHKGRoY0KPbZA9k8iOaanL3I+1zYqbgVumxj1UgNTDw5BE3BUQ1Dono6bD6g==}
+ '@csstools/postcss-color-function@5.0.5':
+ resolution: {integrity: sha512-s+9fU1+sZazUNk0WyKShlfmTLC0fosxNY5x7DiD637xXbZLX2lyce23QrdRhytP3Ja1G77qUk6cRD37N1gemdQ==}
engines: {node: '>=20.19.0'}
peerDependencies:
postcss: '>=8.5.10'
- '@csstools/postcss-color-mix-function@4.0.4':
- resolution: {integrity: sha512-zYS78MHBuih9f9qtPFcSvVXMKg9q/lNPeFJUjyw7+/W1VHRjubvs5MlzuC363UUeahAhrOvYdo2ZZhmlxZbj6w==}
+ '@csstools/postcss-color-mix-function@4.0.5':
+ resolution: {integrity: sha512-eBrrzTKudOlDl2XOJzW/pzHPIkC8tGkcGpNiFO/vmevb08U1huYEINhlxr8iz4OzSqs1GtiJx4d2v5iHFOZjNw==}
engines: {node: '>=20.19.0'}
peerDependencies:
postcss: '>=8.5.10'
- '@csstools/postcss-color-mix-variadic-function-arguments@2.0.4':
- resolution: {integrity: sha512-qlrABMEFPUqbCxX0aOsHcxQZo/8XgMqnEtqqtVUbdizcuTUtJyLdHike7hkoemwDspMSEotdIfRlUY4jhZaD+A==}
+ '@csstools/postcss-color-mix-variadic-function-arguments@2.0.5':
+ resolution: {integrity: sha512-O4tE1hZXfEAbTP1IC2R857KjPCLNtpsFUqY2dqgycF/3M6GuFyJI20EWwkxVZzlSFvWdIcNppwRf9pxPFn0qnA==}
engines: {node: '>=20.19.0'}
peerDependencies:
postcss: '>=8.5.10'
@@ -1101,8 +1105,8 @@ packages:
peerDependencies:
postcss: '>=8.5.10'
- '@csstools/postcss-contrast-color-function@3.0.4':
- resolution: {integrity: sha512-EiTZzUICztGqEuYg8AVCUWH9vH2jDzO6RryxMja+PWluZHP6n3/iG6i1leTt5LiDQjDUQlCRbQtMNj7V7S+b4Q==}
+ '@csstools/postcss-contrast-color-function@3.0.5':
+ resolution: {integrity: sha512-gfdTZ4a5ioL2zM/yN2FqExy6rql+6egkI5sDuK9MvrbfrVJMzB0OjiCkboT5UprU/P0JwfTiIutW1ZSyqK4Icw==}
engines: {node: '>=20.19.0'}
peerDependencies:
postcss: '>=8.5.10'
@@ -1125,20 +1129,20 @@ packages:
peerDependencies:
postcss: '>=8.5.10'
- '@csstools/postcss-gamut-mapping@3.0.4':
- resolution: {integrity: sha512-2dWGsxtxypKU9Ra862F2335W8xegRwl9ohQ6hk808PiQlEahSaFtt5fqsGmKDaSiaFUx+2X8GZxVo970Ajr2vQ==}
+ '@csstools/postcss-gamut-mapping@3.0.5':
+ resolution: {integrity: sha512-X6XkKkR9R8KyJey9n1ryEzzfX6WpihPz/JBsyIVvxAlztQcMjMA7I9mMybWVv3ZyRMC+0+H7RlIUe85vZkasNQ==}
engines: {node: '>=20.19.0'}
peerDependencies:
postcss: '>=8.5.10'
- '@csstools/postcss-gradients-interpolation-method@6.0.4':
- resolution: {integrity: sha512-sC/7dqVTtQTniLjPp/NagzeUn4sGinnMTicNBLDzirKq/GNXuJaApBOnvBmgNXjV6XPizfMhNRYCk5stn3q2nQ==}
+ '@csstools/postcss-gradients-interpolation-method@6.0.5':
+ resolution: {integrity: sha512-wXiZI6bLRAGcw7XuzsqqPnTVNrHFkHTkcymK2su+ynJjemfCdpCD9HdG+ICikPqtQ782r6LSZdyC3cDhSQqF3Q==}
engines: {node: '>=20.19.0'}
peerDependencies:
postcss: '>=8.5.10'
- '@csstools/postcss-hwb-function@5.0.4':
- resolution: {integrity: sha512-cl0KPaaeYyAXNHO3pqK8adbpbAGmIU1cT1thyaEkmP8yvbJvmyztkpdGADGqziUUoh4dZQ0IhHxOxnKQ296T+A==}
+ '@csstools/postcss-hwb-function@5.0.5':
+ resolution: {integrity: sha512-HeJOXAMr1nYHZ7gJT1+6d899X9Y+5qJcpbLJ8WzhujQOIB4oqbzeP3769sd1xl3eH4qbasxtewxr4crs08SEQw==}
engines: {node: '>=20.19.0'}
peerDependencies:
postcss: '>=8.5.10'
@@ -1233,8 +1237,8 @@ packages:
peerDependencies:
postcss: '>=8.5.10'
- '@csstools/postcss-oklab-function@5.0.4':
- resolution: {integrity: sha512-vIgrKe5ffW99it5SUIXOBczGLSiTaHBhU6afVr9KPwoZ4uq9H0E3Ehvi+xsUjmvnAyMTxOUSszNo04kEhbvYjQ==}
+ '@csstools/postcss-oklab-function@5.0.5':
+ resolution: {integrity: sha512-A+Nkzj2ODvQboM5FlqEcp0iqilyVo78f9FMx/3cHrRrEBqCymSXvf8sa1cTY54lJoUVI3Sn9XysgvYaVIAuIYg==}
engines: {node: '>=20.19.0'}
peerDependencies:
postcss: '>=8.5.10'
@@ -1263,8 +1267,8 @@ packages:
peerDependencies:
postcss: '>=8.5.10'
- '@csstools/postcss-relative-color-syntax@4.0.4':
- resolution: {integrity: sha512-reFFKD9eS602We8621e5cAroKD7hH4104duLNBBhzwawGN7dhbnL1+c/DRHqwyq6eGK35HaKMMiifEZhAztlOA==}
+ '@csstools/postcss-relative-color-syntax@4.0.5':
+ resolution: {integrity: sha512-kBzf+LIm824cpjsZPhNtl/2N1KK+TXnxy8Kce4y+pEAQSrxhpX6WDUg54wjdHBGx2UZUXKBnlaUOsc71sSRDvg==}
engines: {node: '>=20.19.0'}
peerDependencies:
postcss: '>=8.5.10'
@@ -1335,470 +1339,158 @@ packages:
peerDependencies:
postcss: '>=8.5.10'
- '@esbuild/aix-ppc64@0.25.12':
- resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
+ '@esbuild/aix-ppc64@0.28.1':
+ resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
- '@esbuild/aix-ppc64@0.27.5':
- resolution: {integrity: sha512-nGsF/4C7uzUj+Nj/4J+Zt0bYQ6bz33Phz8Lb2N80Mti1HjGclTJdXZ+9APC4kLvONbjxN1zfvYNd8FEcbBK/MQ==}
- engines: {node: '>=18'}
- cpu: [ppc64]
- os: [aix]
-
- '@esbuild/aix-ppc64@0.28.0':
- resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==}
- engines: {node: '>=18'}
- cpu: [ppc64]
- os: [aix]
-
- '@esbuild/android-arm64@0.25.12':
- resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
+ '@esbuild/android-arm64@0.28.1':
+ resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
- '@esbuild/android-arm64@0.27.5':
- resolution: {integrity: sha512-Oeghq+XFgh1pUGd1YKs4DDoxzxkoUkvko+T/IVKwlghKLvvjbGFB3ek8VEDBmNvqhwuL0CQS3cExdzpmUyIrgA==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [android]
-
- '@esbuild/android-arm64@0.28.0':
- resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [android]
-
- '@esbuild/android-arm@0.25.12':
- resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
+ '@esbuild/android-arm@0.28.1':
+ resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
- '@esbuild/android-arm@0.27.5':
- resolution: {integrity: sha512-Cv781jd0Rfj/paoNrul1/r4G0HLvuFKYh7C9uHZ2Pl8YXstzvCyyeWENTFR9qFnRzNMCjXmsulZuvosDg10Mog==}
- engines: {node: '>=18'}
- cpu: [arm]
- os: [android]
-
- '@esbuild/android-arm@0.28.0':
- resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==}
- engines: {node: '>=18'}
- cpu: [arm]
- os: [android]
-
- '@esbuild/android-x64@0.25.12':
- resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
+ '@esbuild/android-x64@0.28.1':
+ resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
- '@esbuild/android-x64@0.27.5':
- resolution: {integrity: sha512-nQD7lspbzerlmtNOxYMFAGmhxgzn8Z7m9jgFkh6kpkjsAhZee1w8tJW3ZlW+N9iRePz0oPUDrYrXidCPSImD0Q==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [android]
-
- '@esbuild/android-x64@0.28.0':
- resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [android]
-
- '@esbuild/darwin-arm64@0.25.12':
- resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
+ '@esbuild/darwin-arm64@0.28.1':
+ resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
- '@esbuild/darwin-arm64@0.27.5':
- resolution: {integrity: sha512-I+Ya/MgC6rr8oRWGRDF3BXDfP8K1BVUggHqN6VI2lUZLdDi1IM1v2cy0e3lCPbP+pVcK3Tv8cgUhHse1kaNZZw==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [darwin]
-
- '@esbuild/darwin-arm64@0.28.0':
- resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [darwin]
-
- '@esbuild/darwin-x64@0.25.12':
- resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
+ '@esbuild/darwin-x64@0.28.1':
+ resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
- '@esbuild/darwin-x64@0.27.5':
- resolution: {integrity: sha512-MCjQUtC8wWJn/pIPM7vQaO69BFgwPD1jriEdqwTCKzWjGgkMbcg+M5HzrOhPhuYe1AJjXlHmD142KQf+jnYj8A==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [darwin]
-
- '@esbuild/darwin-x64@0.28.0':
- resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [darwin]
-
- '@esbuild/freebsd-arm64@0.25.12':
- resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
+ '@esbuild/freebsd-arm64@0.28.1':
+ resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
- '@esbuild/freebsd-arm64@0.27.5':
- resolution: {integrity: sha512-X6xVS+goSH0UelYXnuf4GHLwpOdc8rgK/zai+dKzBMnncw7BTQIwquOodE7EKvY2UVUetSqyAfyZC1D+oqLQtg==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [freebsd]
-
- '@esbuild/freebsd-arm64@0.28.0':
- resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [freebsd]
-
- '@esbuild/freebsd-x64@0.25.12':
- resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
+ '@esbuild/freebsd-x64@0.28.1':
+ resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
- '@esbuild/freebsd-x64@0.27.5':
- resolution: {integrity: sha512-233X1FGo3a8x1ekLB6XT69LfZ83vqz+9z3TSEQCTYfMNY880A97nr81KbPcAMl9rmOFp11wO0dP+eB18KU/Ucg==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [freebsd]
-
- '@esbuild/freebsd-x64@0.28.0':
- resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [freebsd]
-
- '@esbuild/linux-arm64@0.25.12':
- resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
+ '@esbuild/linux-arm64@0.28.1':
+ resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
- '@esbuild/linux-arm64@0.27.5':
- resolution: {integrity: sha512-euKkilsNOv7x/M1NKsx5znyprbpsRFIzTV6lWziqJch7yWYayfLtZzDxDTl+LSQDJYAjd9TVb/Kt5UKIrj2e4A==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [linux]
-
- '@esbuild/linux-arm64@0.28.0':
- resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [linux]
-
- '@esbuild/linux-arm@0.25.12':
- resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
+ '@esbuild/linux-arm@0.28.1':
+ resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
- '@esbuild/linux-arm@0.27.5':
- resolution: {integrity: sha512-0wkVrYHG4sdCCN/bcwQ7yYMXACkaHc3UFeaEOwSVW6e5RycMageYAFv+JS2bKLwHyeKVUvtoVH+5/RHq0fgeFw==}
- engines: {node: '>=18'}
- cpu: [arm]
- os: [linux]
-
- '@esbuild/linux-arm@0.28.0':
- resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==}
- engines: {node: '>=18'}
- cpu: [arm]
- os: [linux]
-
- '@esbuild/linux-ia32@0.25.12':
- resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
+ '@esbuild/linux-ia32@0.28.1':
+ resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
- '@esbuild/linux-ia32@0.27.5':
- resolution: {integrity: sha512-hVRQX4+P3MS36NxOy24v/Cdsimy/5HYePw+tmPqnNN1fxV0bPrFWR6TMqwXPwoTM2VzbkA+4lbHWUKDd5ZDA/w==}
- engines: {node: '>=18'}
- cpu: [ia32]
- os: [linux]
-
- '@esbuild/linux-ia32@0.28.0':
- resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==}
- engines: {node: '>=18'}
- cpu: [ia32]
- os: [linux]
-
- '@esbuild/linux-loong64@0.25.12':
- resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
+ '@esbuild/linux-loong64@0.28.1':
+ resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
- '@esbuild/linux-loong64@0.27.5':
- resolution: {integrity: sha512-mKqqRuOPALI8nDzhOBmIS0INvZOOFGGg5n1osGIXAx8oersceEbKd4t1ACNTHM3sJBXGFAlEgqM+svzjPot+ZQ==}
- engines: {node: '>=18'}
- cpu: [loong64]
- os: [linux]
-
- '@esbuild/linux-loong64@0.28.0':
- resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==}
- engines: {node: '>=18'}
- cpu: [loong64]
- os: [linux]
-
- '@esbuild/linux-mips64el@0.25.12':
- resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
+ '@esbuild/linux-mips64el@0.28.1':
+ resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
- '@esbuild/linux-mips64el@0.27.5':
- resolution: {integrity: sha512-EE/QXH9IyaAj1qeuIV5+/GZkBTipgGO782Ff7Um3vPS9cvLhJJeATy4Ggxikz2inZ46KByamMn6GqtqyVjhenA==}
- engines: {node: '>=18'}
- cpu: [mips64el]
- os: [linux]
-
- '@esbuild/linux-mips64el@0.28.0':
- resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==}
- engines: {node: '>=18'}
- cpu: [mips64el]
- os: [linux]
-
- '@esbuild/linux-ppc64@0.25.12':
- resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
+ '@esbuild/linux-ppc64@0.28.1':
+ resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
- '@esbuild/linux-ppc64@0.27.5':
- resolution: {integrity: sha512-0V2iF1RGxBf1b7/BjurA5jfkl7PtySjom1r6xOK2q9KWw/XCpAdtB6KNMO+9xx69yYfSCRR9FE0TyKfHA2eQMw==}
- engines: {node: '>=18'}
- cpu: [ppc64]
- os: [linux]
-
- '@esbuild/linux-ppc64@0.28.0':
- resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==}
- engines: {node: '>=18'}
- cpu: [ppc64]
- os: [linux]
-
- '@esbuild/linux-riscv64@0.25.12':
- resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
+ '@esbuild/linux-riscv64@0.28.1':
+ resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
- '@esbuild/linux-riscv64@0.27.5':
- resolution: {integrity: sha512-rYxThBx6G9HN6tFNuvB/vykeLi4VDsm5hE5pVwzqbAjZEARQrWu3noZSfbEnPZ/CRXP3271GyFk/49up2W190g==}
- engines: {node: '>=18'}
- cpu: [riscv64]
- os: [linux]
-
- '@esbuild/linux-riscv64@0.28.0':
- resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==}
- engines: {node: '>=18'}
- cpu: [riscv64]
- os: [linux]
-
- '@esbuild/linux-s390x@0.25.12':
- resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
+ '@esbuild/linux-s390x@0.28.1':
+ resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
- '@esbuild/linux-s390x@0.27.5':
- resolution: {integrity: sha512-uEP2q/4qgd8goEUc4QIdU/1P2NmEtZ/zX5u3OpLlCGhJIuBIv0s0wr7TB2nBrd3/A5XIdEkkS5ZLF0ULuvaaYQ==}
- engines: {node: '>=18'}
- cpu: [s390x]
- os: [linux]
-
- '@esbuild/linux-s390x@0.28.0':
- resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==}
- engines: {node: '>=18'}
- cpu: [s390x]
- os: [linux]
-
- '@esbuild/linux-x64@0.25.12':
- resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
+ '@esbuild/linux-x64@0.28.1':
+ resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
- '@esbuild/linux-x64@0.27.5':
- resolution: {integrity: sha512-+Gq47Wqq6PLOOZuBzVSII2//9yyHNKZLuwfzCemqexqOQCSz0zy0O26kIzyp9EMNMK+nZ0tFHBZrCeVUuMs/ew==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [linux]
-
- '@esbuild/linux-x64@0.28.0':
- resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [linux]
-
- '@esbuild/netbsd-arm64@0.25.12':
- resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
+ '@esbuild/netbsd-arm64@0.28.1':
+ resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
- '@esbuild/netbsd-arm64@0.27.5':
- resolution: {integrity: sha512-3F/5EG8VHfN/I+W5cO1/SV2H9Q/5r7vcHabMnBqhHK2lTWOh3F8vixNzo8lqxrlmBtZVFpW8pmITHnq54+Tq4g==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [netbsd]
-
- '@esbuild/netbsd-arm64@0.28.0':
- resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [netbsd]
-
- '@esbuild/netbsd-x64@0.25.12':
- resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
+ '@esbuild/netbsd-x64@0.28.1':
+ resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
- '@esbuild/netbsd-x64@0.27.5':
- resolution: {integrity: sha512-28t+Sj3CPN8vkMOlZotOmDgilQwVvxWZl7b8rxpn73Tt/gCnvrHxQUMng4uu3itdFvrtba/1nHejvxqz8xgEMA==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [netbsd]
-
- '@esbuild/netbsd-x64@0.28.0':
- resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [netbsd]
-
- '@esbuild/openbsd-arm64@0.25.12':
- resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
+ '@esbuild/openbsd-arm64@0.28.1':
+ resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
- '@esbuild/openbsd-arm64@0.27.5':
- resolution: {integrity: sha512-Doz/hKtiuVAi9hMsBMpwBANhIZc8l238U2Onko3t2xUp8xtM0ZKdDYHMnm/qPFVthY8KtxkXaocwmMh6VolzMA==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [openbsd]
-
- '@esbuild/openbsd-arm64@0.28.0':
- resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [openbsd]
-
- '@esbuild/openbsd-x64@0.25.12':
- resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
+ '@esbuild/openbsd-x64@0.28.1':
+ resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
- '@esbuild/openbsd-x64@0.27.5':
- resolution: {integrity: sha512-WfGVaa1oz5A7+ZFPkERIbIhKT4olvGl1tyzTRaB5yoZRLqC0KwaO95FeZtOdQj/oKkjW57KcVF944m62/0GYtA==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [openbsd]
-
- '@esbuild/openbsd-x64@0.28.0':
- resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [openbsd]
-
- '@esbuild/openharmony-arm64@0.25.12':
- resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
+ '@esbuild/openharmony-arm64@0.28.1':
+ resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
- '@esbuild/openharmony-arm64@0.27.5':
- resolution: {integrity: sha512-Xh+VRuh6OMh3uJ0JkCjI57l+DVe7VRGBYymen8rFPnTVgATBwA6nmToxM2OwTlSvrnWpPKkrQUj93+K9huYC6A==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [openharmony]
-
- '@esbuild/openharmony-arm64@0.28.0':
- resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [openharmony]
-
- '@esbuild/sunos-x64@0.25.12':
- resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
+ '@esbuild/sunos-x64@0.28.1':
+ resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
- '@esbuild/sunos-x64@0.27.5':
- resolution: {integrity: sha512-aC1gpJkkaUADHuAdQfuVTnqVUTLqqUNhAvEwHwVWcnVVZvNlDPGA0UveZsfXJJ9T6k9Po4eHi3c02gbdwO3g6w==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [sunos]
-
- '@esbuild/sunos-x64@0.28.0':
- resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [sunos]
-
- '@esbuild/win32-arm64@0.25.12':
- resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
+ '@esbuild/win32-arm64@0.28.1':
+ resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
- '@esbuild/win32-arm64@0.27.5':
- resolution: {integrity: sha512-0UNx2aavV0fk6UpZcwXFLztA2r/k9jTUa7OW7SAea1VYUhkug99MW1uZeXEnPn5+cHOd0n8myQay6TlFnBR07w==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [win32]
-
- '@esbuild/win32-arm64@0.28.0':
- resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [win32]
-
- '@esbuild/win32-ia32@0.25.12':
- resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
+ '@esbuild/win32-ia32@0.28.1':
+ resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
- '@esbuild/win32-ia32@0.27.5':
- resolution: {integrity: sha512-5nlJ3AeJWCTSzR7AEqVjT/faWyqKU86kCi1lLmxVqmNR+j4HrYdns+eTGjS/vmrzCIe8inGQckUadvS0+JkKdQ==}
- engines: {node: '>=18'}
- cpu: [ia32]
- os: [win32]
-
- '@esbuild/win32-ia32@0.28.0':
- resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==}
- engines: {node: '>=18'}
- cpu: [ia32]
- os: [win32]
-
- '@esbuild/win32-x64@0.25.12':
- resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [win32]
-
- '@esbuild/win32-x64@0.27.5':
- resolution: {integrity: sha512-PWypQR+d4FLfkhBIV+/kHsUELAnMpx1bRvvsn3p+/sAERbnCzFrtDRG2Xw5n+2zPxBK2+iaP+vetsRl4Ti7WgA==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [win32]
-
- '@esbuild/win32-x64@0.28.0':
- resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==}
+ '@esbuild/win32-x64@0.28.1':
+ resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
@@ -1850,8 +1542,8 @@ packages:
'@exodus/crypto':
optional: true
- '@faker-js/faker@10.4.0':
- resolution: {integrity: sha512-sDBWI3yLy8EcDzgobvJTWq1MJYzAkQdpjXuPukga9wXonhpMRvd1Izuo2Qgwey2OiEoRIBr35RMU9HJRoOHzpw==}
+ '@faker-js/faker@10.5.0':
+ resolution: {integrity: sha512-bsxD8WLS5lIj7aaoCx1YJkktqYj5vlBUE6HWzu2Q51ksrGJ0H737ECCKlFU7Yf8Br45z9t99frBp/J7kzbMPAg==}
engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0, npm: '>=10'}
'@floating-ui/core@1.7.3':
@@ -2029,10 +1721,6 @@ packages:
'@jridgewell/gen-mapping@0.3.13':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
- '@jridgewell/gen-mapping@0.3.5':
- resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
- engines: {node: '>=6.0.0'}
-
'@jridgewell/remapping@2.3.5':
resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
@@ -2040,19 +1728,12 @@ packages:
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
- '@jridgewell/set-array@1.2.1':
- resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
- engines: {node: '>=6.0.0'}
-
'@jridgewell/source-map@0.3.6':
resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==}
'@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
- '@jridgewell/trace-mapping@0.3.25':
- resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
-
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
@@ -2257,9 +1938,9 @@ packages:
resolution: {integrity: sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==}
engines: {node: '>=14.0.0'}
peerDependencies:
- '@babel/core': ^7.0.0
+ '@babel/core': '>=7.29.6'
'@types/babel__core': ^7.1.9
- rollup: 4.61.1
+ rollup: 4.62.0
peerDependenciesMeta:
'@types/babel__core':
optional: true
@@ -2270,7 +1951,7 @@ packages:
resolution: {integrity: sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==}
engines: {node: '>=14.0.0'}
peerDependencies:
- rollup: 4.61.1
+ rollup: 4.62.0
peerDependenciesMeta:
rollup:
optional: true
@@ -2279,7 +1960,7 @@ packages:
resolution: {integrity: sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==}
engines: {node: '>=14.0.0'}
peerDependencies:
- rollup: 4.61.1
+ rollup: 4.62.0
peerDependenciesMeta:
rollup:
optional: true
@@ -2288,7 +1969,7 @@ packages:
resolution: {integrity: sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==}
engines: {node: '>=20.0.0'}
peerDependencies:
- rollup: 4.61.1
+ rollup: 4.62.0
peerDependenciesMeta:
rollup:
optional: true
@@ -2297,133 +1978,133 @@ packages:
resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==}
engines: {node: '>=14.0.0'}
peerDependencies:
- rollup: 4.61.1
+ rollup: 4.62.0
peerDependenciesMeta:
rollup:
optional: true
- '@rollup/rollup-android-arm-eabi@4.61.1':
- resolution: {integrity: sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==}
+ '@rollup/rollup-android-arm-eabi@4.62.0':
+ resolution: {integrity: sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ==}
cpu: [arm]
os: [android]
- '@rollup/rollup-android-arm64@4.61.1':
- resolution: {integrity: sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==}
+ '@rollup/rollup-android-arm64@4.62.0':
+ resolution: {integrity: sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA==}
cpu: [arm64]
os: [android]
- '@rollup/rollup-darwin-arm64@4.61.1':
- resolution: {integrity: sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==}
+ '@rollup/rollup-darwin-arm64@4.62.0':
+ resolution: {integrity: sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw==}
cpu: [arm64]
os: [darwin]
- '@rollup/rollup-darwin-x64@4.61.1':
- resolution: {integrity: sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==}
+ '@rollup/rollup-darwin-x64@4.62.0':
+ resolution: {integrity: sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w==}
cpu: [x64]
os: [darwin]
- '@rollup/rollup-freebsd-arm64@4.61.1':
- resolution: {integrity: sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==}
+ '@rollup/rollup-freebsd-arm64@4.62.0':
+ resolution: {integrity: sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ==}
cpu: [arm64]
os: [freebsd]
- '@rollup/rollup-freebsd-x64@4.61.1':
- resolution: {integrity: sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==}
+ '@rollup/rollup-freebsd-x64@4.62.0':
+ resolution: {integrity: sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ==}
cpu: [x64]
os: [freebsd]
- '@rollup/rollup-linux-arm-gnueabihf@4.61.1':
- resolution: {integrity: sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==}
+ '@rollup/rollup-linux-arm-gnueabihf@4.62.0':
+ resolution: {integrity: sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ==}
cpu: [arm]
os: [linux]
- '@rollup/rollup-linux-arm-musleabihf@4.61.1':
- resolution: {integrity: sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==}
+ '@rollup/rollup-linux-arm-musleabihf@4.62.0':
+ resolution: {integrity: sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA==}
cpu: [arm]
os: [linux]
- '@rollup/rollup-linux-arm64-gnu@4.61.1':
- resolution: {integrity: sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==}
+ '@rollup/rollup-linux-arm64-gnu@4.62.0':
+ resolution: {integrity: sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g==}
cpu: [arm64]
os: [linux]
- '@rollup/rollup-linux-arm64-musl@4.61.1':
- resolution: {integrity: sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==}
+ '@rollup/rollup-linux-arm64-musl@4.62.0':
+ resolution: {integrity: sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw==}
cpu: [arm64]
os: [linux]
- '@rollup/rollup-linux-loong64-gnu@4.61.1':
- resolution: {integrity: sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==}
+ '@rollup/rollup-linux-loong64-gnu@4.62.0':
+ resolution: {integrity: sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg==}
cpu: [loong64]
os: [linux]
- '@rollup/rollup-linux-loong64-musl@4.61.1':
- resolution: {integrity: sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==}
+ '@rollup/rollup-linux-loong64-musl@4.62.0':
+ resolution: {integrity: sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA==}
cpu: [loong64]
os: [linux]
- '@rollup/rollup-linux-ppc64-gnu@4.61.1':
- resolution: {integrity: sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==}
+ '@rollup/rollup-linux-ppc64-gnu@4.62.0':
+ resolution: {integrity: sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w==}
cpu: [ppc64]
os: [linux]
- '@rollup/rollup-linux-ppc64-musl@4.61.1':
- resolution: {integrity: sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==}
+ '@rollup/rollup-linux-ppc64-musl@4.62.0':
+ resolution: {integrity: sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg==}
cpu: [ppc64]
os: [linux]
- '@rollup/rollup-linux-riscv64-gnu@4.61.1':
- resolution: {integrity: sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==}
+ '@rollup/rollup-linux-riscv64-gnu@4.62.0':
+ resolution: {integrity: sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg==}
cpu: [riscv64]
os: [linux]
- '@rollup/rollup-linux-riscv64-musl@4.61.1':
- resolution: {integrity: sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==}
+ '@rollup/rollup-linux-riscv64-musl@4.62.0':
+ resolution: {integrity: sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg==}
cpu: [riscv64]
os: [linux]
- '@rollup/rollup-linux-s390x-gnu@4.61.1':
- resolution: {integrity: sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==}
+ '@rollup/rollup-linux-s390x-gnu@4.62.0':
+ resolution: {integrity: sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA==}
cpu: [s390x]
os: [linux]
- '@rollup/rollup-linux-x64-gnu@4.61.1':
- resolution: {integrity: sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==}
+ '@rollup/rollup-linux-x64-gnu@4.62.0':
+ resolution: {integrity: sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg==}
cpu: [x64]
os: [linux]
- '@rollup/rollup-linux-x64-musl@4.61.1':
- resolution: {integrity: sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==}
+ '@rollup/rollup-linux-x64-musl@4.62.0':
+ resolution: {integrity: sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw==}
cpu: [x64]
os: [linux]
- '@rollup/rollup-openbsd-x64@4.61.1':
- resolution: {integrity: sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==}
+ '@rollup/rollup-openbsd-x64@4.62.0':
+ resolution: {integrity: sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg==}
cpu: [x64]
os: [openbsd]
- '@rollup/rollup-openharmony-arm64@4.61.1':
- resolution: {integrity: sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==}
+ '@rollup/rollup-openharmony-arm64@4.62.0':
+ resolution: {integrity: sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg==}
cpu: [arm64]
os: [openharmony]
- '@rollup/rollup-win32-arm64-msvc@4.61.1':
- resolution: {integrity: sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==}
+ '@rollup/rollup-win32-arm64-msvc@4.62.0':
+ resolution: {integrity: sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw==}
cpu: [arm64]
os: [win32]
- '@rollup/rollup-win32-ia32-msvc@4.61.1':
- resolution: {integrity: sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==}
+ '@rollup/rollup-win32-ia32-msvc@4.62.0':
+ resolution: {integrity: sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw==}
cpu: [ia32]
os: [win32]
- '@rollup/rollup-win32-x64-gnu@4.61.1':
- resolution: {integrity: sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==}
+ '@rollup/rollup-win32-x64-gnu@4.62.0':
+ resolution: {integrity: sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA==}
cpu: [x64]
os: [win32]
- '@rollup/rollup-win32-x64-msvc@4.61.1':
- resolution: {integrity: sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==}
+ '@rollup/rollup-win32-x64-msvc@4.62.0':
+ resolution: {integrity: sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA==}
cpu: [x64]
os: [win32]
@@ -2560,65 +2241,65 @@ packages:
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
- '@tailwindcss/node@4.3.0':
- resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==}
+ '@tailwindcss/node@4.3.1':
+ resolution: {integrity: sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==}
- '@tailwindcss/oxide-android-arm64@4.3.0':
- resolution: {integrity: sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==}
+ '@tailwindcss/oxide-android-arm64@4.3.1':
+ resolution: {integrity: sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [android]
- '@tailwindcss/oxide-darwin-arm64@4.3.0':
- resolution: {integrity: sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==}
+ '@tailwindcss/oxide-darwin-arm64@4.3.1':
+ resolution: {integrity: sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [darwin]
- '@tailwindcss/oxide-darwin-x64@4.3.0':
- resolution: {integrity: sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==}
+ '@tailwindcss/oxide-darwin-x64@4.3.1':
+ resolution: {integrity: sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg==}
engines: {node: '>= 20'}
cpu: [x64]
os: [darwin]
- '@tailwindcss/oxide-freebsd-x64@4.3.0':
- resolution: {integrity: sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==}
+ '@tailwindcss/oxide-freebsd-x64@4.3.1':
+ resolution: {integrity: sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g==}
engines: {node: '>= 20'}
cpu: [x64]
os: [freebsd]
- '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0':
- resolution: {integrity: sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==}
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.1':
+ resolution: {integrity: sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg==}
engines: {node: '>= 20'}
cpu: [arm]
os: [linux]
- '@tailwindcss/oxide-linux-arm64-gnu@4.3.0':
- resolution: {integrity: sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==}
+ '@tailwindcss/oxide-linux-arm64-gnu@4.3.1':
+ resolution: {integrity: sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [linux]
- '@tailwindcss/oxide-linux-arm64-musl@4.3.0':
- resolution: {integrity: sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==}
+ '@tailwindcss/oxide-linux-arm64-musl@4.3.1':
+ resolution: {integrity: sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [linux]
- '@tailwindcss/oxide-linux-x64-gnu@4.3.0':
- resolution: {integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==}
+ '@tailwindcss/oxide-linux-x64-gnu@4.3.1':
+ resolution: {integrity: sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg==}
engines: {node: '>= 20'}
cpu: [x64]
os: [linux]
- '@tailwindcss/oxide-linux-x64-musl@4.3.0':
- resolution: {integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==}
+ '@tailwindcss/oxide-linux-x64-musl@4.3.1':
+ resolution: {integrity: sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ==}
engines: {node: '>= 20'}
cpu: [x64]
os: [linux]
- '@tailwindcss/oxide-wasm32-wasi@4.3.0':
- resolution: {integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==}
+ '@tailwindcss/oxide-wasm32-wasi@4.3.1':
+ resolution: {integrity: sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA==}
engines: {node: '>=14.0.0'}
cpu: [wasm32]
bundledDependencies:
@@ -2629,24 +2310,24 @@ packages:
- '@emnapi/wasi-threads'
- tslib
- '@tailwindcss/oxide-win32-arm64-msvc@4.3.0':
- resolution: {integrity: sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==}
+ '@tailwindcss/oxide-win32-arm64-msvc@4.3.1':
+ resolution: {integrity: sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [win32]
- '@tailwindcss/oxide-win32-x64-msvc@4.3.0':
- resolution: {integrity: sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==}
+ '@tailwindcss/oxide-win32-x64-msvc@4.3.1':
+ resolution: {integrity: sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA==}
engines: {node: '>= 20'}
cpu: [x64]
os: [win32]
- '@tailwindcss/oxide@4.3.0':
- resolution: {integrity: sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==}
+ '@tailwindcss/oxide@4.3.1':
+ resolution: {integrity: sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA==}
engines: {node: '>= 20'}
- '@tailwindcss/vite@4.3.0':
- resolution: {integrity: sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==}
+ '@tailwindcss/vite@4.3.1':
+ resolution: {integrity: sha512-hItDHuIIlEV61R+faXu66s1K36aTurO/Qw0e45Vskz57gXl9pWOT6eg3zmcEui6CZXddbN7zd41bwmvag4JGwQ==}
peerDependencies:
vite: ^5.2.0 || ^6 || ^7 || ^8
@@ -2931,11 +2612,11 @@ packages:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/eslint-plugin@8.61.0':
- resolution: {integrity: sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==}
+ '@typescript-eslint/eslint-plugin@8.61.1':
+ resolution: {integrity: sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- '@typescript-eslint/parser': ^8.61.0
+ '@typescript-eslint/parser': ^8.61.1
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
@@ -2946,8 +2627,8 @@ packages:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/parser@8.61.0':
- resolution: {integrity: sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==}
+ '@typescript-eslint/parser@8.61.1':
+ resolution: {integrity: sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
@@ -2965,8 +2646,8 @@ packages:
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/project-service@8.61.0':
- resolution: {integrity: sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA==}
+ '@typescript-eslint/project-service@8.61.1':
+ resolution: {integrity: sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
@@ -2979,8 +2660,8 @@ packages:
resolution: {integrity: sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/scope-manager@8.61.0':
- resolution: {integrity: sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA==}
+ '@typescript-eslint/scope-manager@8.61.1':
+ resolution: {integrity: sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/tsconfig-utils@8.58.0':
@@ -3001,6 +2682,12 @@ packages:
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
+ '@typescript-eslint/tsconfig-utils@8.61.1':
+ resolution: {integrity: sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.1.0'
+
'@typescript-eslint/type-utils@8.60.1':
resolution: {integrity: sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3008,8 +2695,8 @@ packages:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/type-utils@8.61.0':
- resolution: {integrity: sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A==}
+ '@typescript-eslint/type-utils@8.61.1':
+ resolution: {integrity: sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
@@ -3027,6 +2714,10 @@ packages:
resolution: {integrity: sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@typescript-eslint/types@8.61.1':
+ resolution: {integrity: sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
'@typescript-eslint/typescript-estree@8.58.0':
resolution: {integrity: sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3039,8 +2730,8 @@ packages:
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/typescript-estree@8.61.0':
- resolution: {integrity: sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA==}
+ '@typescript-eslint/typescript-estree@8.61.1':
+ resolution: {integrity: sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
@@ -3052,8 +2743,8 @@ packages:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/utils@8.61.0':
- resolution: {integrity: sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA==}
+ '@typescript-eslint/utils@8.61.1':
+ resolution: {integrity: sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
@@ -3067,8 +2758,8 @@ packages:
resolution: {integrity: sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/visitor-keys@8.61.0':
- resolution: {integrity: sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==}
+ '@typescript-eslint/visitor-keys@8.61.1':
+ resolution: {integrity: sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@ungap/structured-clone@1.3.0':
@@ -3082,11 +2773,11 @@ packages:
vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
vue: ^3.2.25
- '@vitest/expect@4.1.8':
- resolution: {integrity: sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==}
+ '@vitest/expect@4.1.9':
+ resolution: {integrity: sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==}
- '@vitest/mocker@4.1.8':
- resolution: {integrity: sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==}
+ '@vitest/mocker@4.1.9':
+ resolution: {integrity: sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==}
peerDependencies:
msw: ^2.4.9
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -3096,20 +2787,20 @@ packages:
vite:
optional: true
- '@vitest/pretty-format@4.1.8':
- resolution: {integrity: sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==}
+ '@vitest/pretty-format@4.1.9':
+ resolution: {integrity: sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==}
- '@vitest/runner@4.1.8':
- resolution: {integrity: sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==}
+ '@vitest/runner@4.1.9':
+ resolution: {integrity: sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==}
- '@vitest/snapshot@4.1.8':
- resolution: {integrity: sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==}
+ '@vitest/snapshot@4.1.9':
+ resolution: {integrity: sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==}
- '@vitest/spy@4.1.8':
- resolution: {integrity: sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==}
+ '@vitest/spy@4.1.9':
+ resolution: {integrity: sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==}
- '@vitest/utils@4.1.8':
- resolution: {integrity: sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==}
+ '@vitest/utils@4.1.9':
+ resolution: {integrity: sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==}
'@volar/language-core@2.4.28':
resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==}
@@ -3126,7 +2817,7 @@ packages:
'@vue/babel-plugin-jsx@1.2.5':
resolution: {integrity: sha512-zTrNmOd4939H9KsRIGmmzn3q2zvv1mjxkYZHgqHZgDrXz5B1Q3WyGEjO2f+JrmKghvl1JIRcvo63LgM1kH5zFg==}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
peerDependenciesMeta:
'@babel/core':
optional: true
@@ -3134,7 +2825,7 @@ packages:
'@vue/babel-plugin-resolve-type@1.2.5':
resolution: {integrity: sha512-U/ibkQrf5sx0XXRnUZD1mo5F7PkpKyTbfXM3a3rC4YnUz6crHEz9Jg09jzzL6QYlXNto/9CePdOg/c87O4Nlfg==}
peerDependencies:
- '@babel/core': ^7.0.0-0
+ '@babel/core': '>=7.29.6'
'@vue/compiler-core@3.5.27':
resolution: {integrity: sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==}
@@ -3154,22 +2845,22 @@ packages:
'@vue/devtools-api@7.7.7':
resolution: {integrity: sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==}
- '@vue/devtools-core@8.1.2':
- resolution: {integrity: sha512-ZGGyaSBP4/+bN2Nd9ZHNYAVDRIzMw1rv2RyXWtyZlo6mQal+IDmTvKY4V+DjAEBhaXt30mHmsgYp1yXJ/2tIWg==}
+ '@vue/devtools-core@8.1.3':
+ resolution: {integrity: sha512-xezkv5/CPH/o5C8PE2Len9MnTJMsctYYQbKbbUiNOJpKd+fRHj27nKDb/sbtYI8NSQduegeQhCJGKRgAiOV6Uw==}
peerDependencies:
vue: ^3.0.0
'@vue/devtools-kit@7.7.7':
resolution: {integrity: sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==}
- '@vue/devtools-kit@8.1.2':
- resolution: {integrity: sha512-f75/upc+GCyjXErpgPGz4582ujS0L/adAltGy+tqXMGUJpgAcfGr6CxnnhpZY8BHuMYt6KpbF8uaFrrQG66rGQ==}
+ '@vue/devtools-kit@8.1.3':
+ resolution: {integrity: sha512-cRn7GXiCQkMYU2Z3h3pM4YO/ndbx9FY1yLDAqIqPLcmIq4H6zAOJHein6tvZU3AfPwgrodqLiPBEF+YQaS8AxA==}
'@vue/devtools-shared@7.7.7':
resolution: {integrity: sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==}
- '@vue/devtools-shared@8.1.2':
- resolution: {integrity: sha512-X9RyVFYAdkBe4IUf5v48TxBF/6QPmF8CmWrDAjXzfUHrgQ/HGfTC1A6TqgXqZ03ye66l3AD51BAGD69IvKM9sw==}
+ '@vue/devtools-shared@8.1.3':
+ resolution: {integrity: sha512-CM3uIPL+v+lrJUk33+pxspYo0MhuMWlCvf7zC9fybifvCPyM2jUbYRPwoYEJgYbwRqPikm5HozbUhp60MF2QuA==}
'@vue/eslint-config-typescript@14.8.0':
resolution: {integrity: sha512-yIquzhXH7ZsrwSSm+rYvoGCRY6wcuF4qBi76e0l7hHLq7YU0f9aC+RcR5fL+XJNfmBZxgX5cVl4sppt4x7ZCBg==}
@@ -3182,8 +2873,8 @@ packages:
typescript:
optional: true
- '@vue/language-core@3.3.4':
- resolution: {integrity: sha512-IuHqQ5zGGOE7CXP72VX6A42IVeIzYv4WAhO6arej11TRNqtdZfGyH8Yr2FOCaDX0dSQG+JwULLoFHGY1igYVjQ==}
+ '@vue/language-core@3.3.5':
+ resolution: {integrity: sha512-UkKu5nhX89fg4VhlG/FOeI10G3cj/7radKT/cy9BT4Q9qJmJlSTAc/dP63Xqs29aypN4f39xUV6PsLNk/dcD6g==}
'@vue/reactivity@3.5.27':
resolution: {integrity: sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==}
@@ -3372,17 +3063,17 @@ packages:
babel-plugin-polyfill-corejs2@0.4.11:
resolution: {integrity: sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==}
peerDependencies:
- '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
+ '@babel/core': '>=7.29.6'
babel-plugin-polyfill-corejs3@0.10.6:
resolution: {integrity: sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==}
peerDependencies:
- '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
+ '@babel/core': '>=7.29.6'
babel-plugin-polyfill-regenerator@0.6.2:
resolution: {integrity: sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==}
peerDependencies:
- '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
+ '@babel/core': '>=7.29.6'
balanced-match@4.0.3:
resolution: {integrity: sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==}
@@ -3880,8 +3571,8 @@ packages:
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
engines: {node: '>= 4'}
- dompurify@3.4.0:
- resolution: {integrity: sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==}
+ dompurify@3.4.9:
+ resolution: {integrity: sha512-4dPSRMRDqHvs0V4YDFCsaIZo4if5u0xM+llyxiM2fwuZFdKArUBAF3VtI2+n8NKg9P870WMdYk0UhqQNoWXbfQ==}
domutils@3.2.2:
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
@@ -3949,8 +3640,8 @@ packages:
end-of-stream@1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
- enhanced-resolve@5.21.3:
- resolution: {integrity: sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==}
+ enhanced-resolve@5.21.6:
+ resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==}
engines: {node: '>=10.13.0'}
entities@4.5.0:
@@ -4009,18 +3700,8 @@ packages:
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
engines: {node: '>= 0.4'}
- esbuild@0.25.12:
- resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
- engines: {node: '>=18'}
- hasBin: true
-
- esbuild@0.27.5:
- resolution: {integrity: sha512-zdQoHBjuDqKsvV5OPaWansOwfSQ0Js+Uj9J85TBvj3bFW1JjWTSULMRwdQAc8qMeIScbClxeMK0jlrtB9linhA==}
- engines: {node: '>=18'}
- hasBin: true
-
- esbuild@0.28.0:
- resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==}
+ esbuild@0.28.1:
+ resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==}
engines: {node: '>=18'}
hasBin: true
@@ -4263,8 +3944,8 @@ packages:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
- form-data@4.0.5:
- resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
+ form-data@4.0.6:
+ resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==}
engines: {node: '>= 6'}
fraction.js@5.3.4:
@@ -4417,8 +4098,8 @@ packages:
resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==}
engines: {node: '>=6.0'}
- happy-dom@20.10.2:
- resolution: {integrity: sha512-5p9Sxis3eowDJKqx90QCsgbNA02XXqJ59NOHvD4V6cxp+rP4d/xOyVx7uY3hS8hiUbY1VeiFH8lbJ81AyuDVLQ==}
+ happy-dom@20.10.5:
+ resolution: {integrity: sha512-0aA6BQoMnpcRE/c1E8ZyF2jXnET7MJskereWOXher4CJuYjrI5esN0Az/1NPMD4KeWUbampBGw2MGqabMPFIbg==}
engines: {node: '>=20.0.0'}
hard-rejection@2.1.0:
@@ -4801,6 +4482,10 @@ packages:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
+ jiti@2.7.0:
+ resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
+ hasBin: true
+
joi@18.2.1:
resolution: {integrity: sha512-2/OKlogiESf2Nh3TFCrRjrr9z1DRHeW0I+KReF67+4J0Ns+8hBtHRmoWAZ2OFU6I5+TWLEe6sVlSdXPjHm5UbQ==}
engines: {node: '>= 20'}
@@ -4824,8 +4509,8 @@ packages:
resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==}
hasBin: true
- js-yaml@4.1.1:
- resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
+ js-yaml@4.2.0:
+ resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==}
hasBin: true
jsdom@27.4.0:
@@ -4909,8 +4594,8 @@ packages:
resolution: {integrity: sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==}
engines: {node: '>=18'}
- launch-editor@2.10.0:
- resolution: {integrity: sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==}
+ launch-editor@2.14.1:
+ resolution: {integrity: sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==}
leven@3.1.0:
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
@@ -4993,8 +4678,8 @@ packages:
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
- linkify-it@5.0.0:
- resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
+ linkify-it@5.0.1:
+ resolution: {integrity: sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==}
linkifyjs@4.3.2:
resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==}
@@ -5062,19 +4747,19 @@ packages:
resolution: {integrity: sha512-sa2ErMQ6kKOA4l31gLGYliFQrMKkqSO0ZJgGhDHKijPf0pNFM9vghjAh3gn26pS4JDRs7Iwa9S36gxm3vgZTzg==}
peerDependencies:
'@types/markdown-it': '*'
- markdown-it: '*'
+ markdown-it: '>=14.2.0'
markdown-it-attrs@4.3.1:
resolution: {integrity: sha512-/ko6cba+H6gdZ0DOw7BbNMZtfuJTRp9g/IrGIuz8lYc/EfnmWRpaR3CFPnNbVz0LDvF8Gf1hFGPqrQqq7De0rg==}
engines: {node: '>=6'}
peerDependencies:
- markdown-it: '>= 9.0.0'
+ markdown-it: '>=14.2.0'
markdown-it-emoji@3.0.0:
resolution: {integrity: sha512-+rUD93bXHubA4arpEZO3q80so0qgoFJEKRkRbjKX8RTdca89v2kfyF+xR3i2sQTwql9tpPZPOQN5B+PunspXRg==}
- markdown-it@14.1.1:
- resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==}
+ markdown-it@14.2.0:
+ resolution: {integrity: sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==}
hasBin: true
marked@17.0.1:
@@ -5441,8 +5126,8 @@ packages:
peerDependencies:
postcss: '>=8.5.10'
- postcss-color-functional-notation@8.0.4:
- resolution: {integrity: sha512-Zn3yPgBFakVXthmA2n1NUMY7gdhuFUB/DrUJ0Eug/d0rl9wahMQZykp4NVTJLGzQrDUwZ2rzjiTeW5udxFNG8A==}
+ postcss-color-functional-notation@8.0.5:
+ resolution: {integrity: sha512-Cxr97Vtt2VeJCGaex0JNSU5MViqYtjKmJLHKM+jI7d+qIs0J5xgHEVG6Q2bTCaFJ1yjcFz9s9VmWCibuzk3+MA==}
engines: {node: '>=20.19.0'}
peerDependencies:
postcss: '>=8.5.10'
@@ -5526,8 +5211,8 @@ packages:
peerDependencies:
postcss: '>=8.5.10'
- postcss-lab-function@8.0.4:
- resolution: {integrity: sha512-dqcJSzVasdELD9xqJ1wfP95uzP57J6zFd80c7S3AWK127H9zwqR9Kbk5ZgyIfN2DiMStI7Vq8E7ablXNeTvpew==}
+ postcss-lab-function@8.0.5:
+ resolution: {integrity: sha512-ohQnYx1LloPkiLQhAjpt/Y9tAGCGOBOUaxgbcmO+1bDTFzUQCTfdpemOVh6oewI4V2K6q7+Vz8d3rP1glvK3uw==}
engines: {node: '>=20.19.0'}
peerDependencies:
postcss: '>=8.5.10'
@@ -5570,8 +5255,8 @@ packages:
peerDependencies:
postcss: '>=8.5.10'
- postcss-preset-env@11.3.0:
- resolution: {integrity: sha512-PpijTuY+NT35vvk7us0pw9lJVrsZZWukjONZsza2Kq1Gag8nrUXRkgdKdxyyhZPJ6R43L3/nLpspUK99TmU9xg==}
+ postcss-preset-env@11.3.1:
+ resolution: {integrity: sha512-ox2lu2L0fbuKXB0zRcUFCNii7koS9+fNLFqj+WOKaJ4DU/zZsYkFHOmz73lWNTKx8OHDqnV0R7Si98PIbJXLjQ==}
engines: {node: '>=20.19.0'}
peerDependencies:
postcss: '>=8.5.10'
@@ -5877,15 +5562,15 @@ packages:
hasBin: true
peerDependencies:
rolldown: 1.x || ^1.0.0-beta
- rollup: 4.61.1
+ rollup: 4.62.0
peerDependenciesMeta:
rolldown:
optional: true
rollup:
optional: true
- rollup@4.61.1:
- resolution: {integrity: sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==}
+ rollup@4.62.0:
+ resolution: {integrity: sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
@@ -6420,8 +6105,8 @@ packages:
resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==}
engines: {node: '>=10.0.0'}
- tailwindcss@4.3.0:
- resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==}
+ tailwindcss@4.3.1:
+ resolution: {integrity: sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q==}
tapable@2.3.3:
resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==}
@@ -6488,8 +6173,8 @@ packages:
resolution: {integrity: sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==}
hasBin: true
- tmp@0.2.6:
- resolution: {integrity: sha512-5sJPdPjfI5Kx+qbrDesxkglRBxW//g7hCsqspEjwkewGvBMGIKMOTKzLt1hFVJzyadba3lDUN20O9qhvbQUSTA==}
+ tmp@0.2.7:
+ resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==}
engines: {node: '>=14.14'}
to-regex-range@5.0.1:
@@ -6760,8 +6445,8 @@ packages:
'@vite-pwa/assets-generator':
optional: true
- vite-plugin-vue-devtools@8.1.2:
- resolution: {integrity: sha512-gt5h1CNryR9Hy0tvhSbqY3j0F7aj0pGxBxWLa1lXSiZVkhdWDf0vbCOZyjh8ivFGE6FDHTGy3zkcZGlMZdVHig==}
+ vite-plugin-vue-devtools@8.1.3:
+ resolution: {integrity: sha512-KBTUhbTXvY+GsCdShnCHG4WdijEV74KIDxhF8erfSs5g5mS13g/cPRUf4mLpD10qr5FqHYosNt0j6rP5kpiS1Q==}
engines: {node: '>=v14.21.3'}
peerDependencies:
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -6816,20 +6501,20 @@ packages:
yaml:
optional: true
- vitest@4.1.8:
- resolution: {integrity: sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==}
+ vitest@4.1.9:
+ resolution: {integrity: sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==}
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@opentelemetry/api': ^1.9.0
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
- '@vitest/browser-playwright': 4.1.8
- '@vitest/browser-preview': 4.1.8
- '@vitest/browser-webdriverio': 4.1.8
- '@vitest/coverage-istanbul': 4.1.8
- '@vitest/coverage-v8': 4.1.8
- '@vitest/ui': 4.1.8
+ '@vitest/browser-playwright': 4.1.9
+ '@vitest/browser-preview': 4.1.9
+ '@vitest/browser-webdriverio': 4.1.9
+ '@vitest/coverage-istanbul': 4.1.9
+ '@vitest/coverage-v8': 4.1.9
+ '@vitest/ui': 4.1.9
happy-dom: '*'
jsdom: '*'
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -6908,8 +6593,8 @@ packages:
peerDependencies:
vue: ^3.5.0
- vue-tsc@3.3.4:
- resolution: {integrity: sha512-XA/JqmQwS2GZmfgpjOEGdrKwaTSEuPwxpHa7/t6f4yiGrJb3gVHTPb9wBfByMNZwQ+xDXs41b8gaS2DKsOozUw==}
+ vue-tsc@3.3.5:
+ resolution: {integrity: sha512-Rzh/G2MmNlMSAMTiQEjDrsb4dgB/jbtEM47rVN2NtidF1dfb/q4w4QvpQBtW5+y3y5H27Hjh7deVwk+YB02fNg==}
hasBin: true
peerDependencies:
typescript: '>=5.0.0'
@@ -7181,11 +6866,6 @@ snapshots:
'@akryum/tinypool@0.3.1': {}
- '@ampproject/remapping@2.3.0':
- dependencies:
- '@jridgewell/gen-mapping': 0.3.5
- '@jridgewell/trace-mapping': 0.3.25
-
'@apideck/better-ajv-errors@0.3.6(ajv@8.18.0)':
dependencies:
ajv: 8.18.0
@@ -7211,32 +6891,34 @@ snapshots:
'@asamuzakjp/nwsapi@2.3.9': {}
- '@babel/code-frame@7.26.2':
- dependencies:
- '@babel/helper-validator-identifier': 7.28.5
- js-tokens: 4.0.0
- picocolors: 1.1.1
-
'@babel/code-frame@7.29.0':
dependencies:
'@babel/helper-validator-identifier': 7.28.5
js-tokens: 4.0.0
picocolors: 1.1.1
+ '@babel/code-frame@7.29.7':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.29.7
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
'@babel/compat-data@7.26.0': {}
- '@babel/core@7.26.0':
+ '@babel/compat-data@7.29.7': {}
+
+ '@babel/core@7.29.7':
dependencies:
- '@ampproject/remapping': 2.3.0
- '@babel/code-frame': 7.26.2
- '@babel/generator': 7.26.0
- '@babel/helper-compilation-targets': 7.25.9
- '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0)
- '@babel/helpers': 7.26.10
- '@babel/parser': 7.28.5
- '@babel/template': 7.26.9
- '@babel/traverse': 7.25.9
- '@babel/types': 7.28.5
+ '@babel/code-frame': 7.29.7
+ '@babel/generator': 7.29.7
+ '@babel/helper-compilation-targets': 7.29.7
+ '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7)
+ '@babel/helpers': 7.29.7
+ '@babel/parser': 7.29.7
+ '@babel/template': 7.29.7
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
+ '@jridgewell/remapping': 2.3.5
convert-source-map: 2.0.0
debug: 4.4.3
gensync: 1.0.0-beta.2
@@ -7245,716 +6927,695 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@babel/generator@7.26.0':
+ '@babel/generator@7.29.7':
dependencies:
- '@babel/parser': 7.28.5
- '@babel/types': 7.28.5
- '@jridgewell/gen-mapping': 0.3.5
- '@jridgewell/trace-mapping': 0.3.25
- jsesc: 3.0.2
-
- '@babel/generator@7.29.1':
- dependencies:
- '@babel/parser': 7.29.3
- '@babel/types': 7.29.0
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
jsesc: 3.1.0
'@babel/helper-annotate-as-pure@7.25.9':
dependencies:
- '@babel/types': 7.28.5
+ '@babel/types': 7.29.7
'@babel/helper-builder-binary-assignment-operator-visitor@7.25.9':
dependencies:
- '@babel/traverse': 7.25.9
- '@babel/types': 7.28.5
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
transitivePeerDependencies:
- supports-color
'@babel/helper-compilation-targets@7.25.9':
dependencies:
- '@babel/compat-data': 7.26.0
- '@babel/helper-validator-option': 7.25.9
+ '@babel/compat-data': 7.29.7
+ '@babel/helper-validator-option': 7.29.7
browserslist: 4.28.2
lru-cache: 5.1.1
semver: 6.3.1
- '@babel/helper-create-class-features-plugin@7.25.9(@babel/core@7.26.0)':
+ '@babel/helper-compilation-targets@7.29.7':
dependencies:
- '@babel/core': 7.26.0
+ '@babel/compat-data': 7.29.7
+ '@babel/helper-validator-option': 7.29.7
+ browserslist: 4.28.2
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-create-class-features-plugin@7.25.9(@babel/core@7.29.7)':
+ dependencies:
+ '@babel/core': 7.29.7
'@babel/helper-annotate-as-pure': 7.25.9
'@babel/helper-member-expression-to-functions': 7.25.9
'@babel/helper-optimise-call-expression': 7.25.9
- '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0)
+ '@babel/helper-replace-supers': 7.25.9(@babel/core@7.29.7)
'@babel/helper-skip-transparent-expression-wrappers': 7.25.9
- '@babel/traverse': 7.25.9
+ '@babel/traverse': 7.29.7
semver: 6.3.1
transitivePeerDependencies:
- supports-color
- '@babel/helper-create-regexp-features-plugin@7.25.9(@babel/core@7.26.0)':
+ '@babel/helper-create-regexp-features-plugin@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
+ '@babel/core': 7.29.7
'@babel/helper-annotate-as-pure': 7.25.9
regexpu-core: 6.1.1
semver: 6.3.1
- '@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.26.0)':
+ '@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-compilation-targets': 7.25.9
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-compilation-targets': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
debug: 4.4.3
lodash.debounce: 4.0.8
resolve: 1.22.8
transitivePeerDependencies:
- supports-color
- '@babel/helper-globals@7.28.0': {}
+ '@babel/helper-globals@7.29.7': {}
'@babel/helper-member-expression-to-functions@7.25.9':
dependencies:
- '@babel/traverse': 7.25.9
- '@babel/types': 7.28.5
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
transitivePeerDependencies:
- supports-color
'@babel/helper-module-imports@7.25.9':
dependencies:
- '@babel/traverse': 7.25.9
- '@babel/types': 7.28.5
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
transitivePeerDependencies:
- supports-color
- '@babel/helper-module-imports@7.28.6':
+ '@babel/helper-module-imports@7.29.7':
dependencies:
- '@babel/traverse': 7.29.0
- '@babel/types': 7.29.0
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
transitivePeerDependencies:
- supports-color
- '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)':
+ '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-module-imports': 7.25.9
- '@babel/helper-validator-identifier': 7.28.5
- '@babel/traverse': 7.25.9
- transitivePeerDependencies:
- - supports-color
-
- '@babel/helper-module-transforms@7.28.6(@babel/core@7.26.0)':
- dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-module-imports': 7.28.6
- '@babel/helper-validator-identifier': 7.28.5
- '@babel/traverse': 7.29.0
+ '@babel/core': 7.29.7
+ '@babel/helper-module-imports': 7.29.7
+ '@babel/helper-validator-identifier': 7.29.7
+ '@babel/traverse': 7.29.7
transitivePeerDependencies:
- supports-color
'@babel/helper-optimise-call-expression@7.25.9':
dependencies:
- '@babel/types': 7.28.5
+ '@babel/types': 7.29.7
'@babel/helper-plugin-utils@7.25.9': {}
'@babel/helper-plugin-utils@7.28.6': {}
- '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.26.0)':
+ '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
+ '@babel/core': 7.29.7
'@babel/helper-annotate-as-pure': 7.25.9
'@babel/helper-wrap-function': 7.25.9
- '@babel/traverse': 7.25.9
+ '@babel/traverse': 7.29.7
transitivePeerDependencies:
- supports-color
- '@babel/helper-replace-supers@7.25.9(@babel/core@7.26.0)':
+ '@babel/helper-replace-supers@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
+ '@babel/core': 7.29.7
'@babel/helper-member-expression-to-functions': 7.25.9
'@babel/helper-optimise-call-expression': 7.25.9
- '@babel/traverse': 7.25.9
+ '@babel/traverse': 7.29.7
transitivePeerDependencies:
- supports-color
'@babel/helper-simple-access@7.25.9':
dependencies:
- '@babel/traverse': 7.25.9
- '@babel/types': 7.28.5
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
transitivePeerDependencies:
- supports-color
'@babel/helper-skip-transparent-expression-wrappers@7.25.9':
dependencies:
- '@babel/traverse': 7.25.9
- '@babel/types': 7.28.5
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
transitivePeerDependencies:
- supports-color
'@babel/helper-string-parser@7.27.1': {}
+ '@babel/helper-string-parser@7.29.7': {}
+
'@babel/helper-validator-identifier@7.28.5': {}
+ '@babel/helper-validator-identifier@7.29.7': {}
+
'@babel/helper-validator-option@7.25.9': {}
+ '@babel/helper-validator-option@7.29.7': {}
+
'@babel/helper-wrap-function@7.25.9':
dependencies:
- '@babel/template': 7.26.9
- '@babel/traverse': 7.25.9
- '@babel/types': 7.28.5
+ '@babel/template': 7.29.7
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
transitivePeerDependencies:
- supports-color
- '@babel/helpers@7.26.10':
+ '@babel/helpers@7.29.7':
dependencies:
- '@babel/template': 7.26.9
- '@babel/types': 7.28.5
+ '@babel/template': 7.29.7
+ '@babel/types': 7.29.7
'@babel/parser@7.28.5':
dependencies:
'@babel/types': 7.28.5
- '@babel/parser@7.29.3':
+ '@babel/parser@7.29.7':
dependencies:
- '@babel/types': 7.29.0
+ '@babel/types': 7.29.7
- '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
- '@babel/traverse': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/traverse': 7.29.7
transitivePeerDependencies:
- supports-color
- '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/helper-skip-transparent-expression-wrappers': 7.25.9
- '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.0)
+ '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.29.7)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
- '@babel/traverse': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/traverse': 7.29.7
transitivePeerDependencies:
- supports-color
- '@babel/plugin-proposal-decorators@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-proposal-decorators@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0)
- '@babel/helper-plugin-utils': 7.25.9
- '@babel/plugin-syntax-decorators': 7.25.9(@babel/core@7.26.0)
+ '@babel/core': 7.29.7
+ '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.29.7)
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/plugin-syntax-decorators': 7.25.9(@babel/core@7.29.7)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0)':
+ '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
+ '@babel/core': 7.29.7
- '@babel/plugin-syntax-decorators@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-syntax-decorators@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.26.0)':
+ '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.0)':
+ '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.0)':
+ '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.0)':
+ '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0)
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.29.7)
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-async-generator-functions@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-async-generator-functions@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
- '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.0)
- '@babel/traverse': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.29.7)
+ '@babel/traverse': 7.29.7
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-module-imports': 7.25.9
- '@babel/helper-plugin-utils': 7.25.9
- '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.0)
+ '@babel/core': 7.29.7
+ '@babel/helper-module-imports': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.29.7)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-block-scoped-functions@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-block-scoped-functions@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0)
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.29.7)
+ '@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.26.0)':
+ '@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0)
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.29.7)
+ '@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-classes@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-classes@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
+ '@babel/core': 7.29.7
'@babel/helper-annotate-as-pure': 7.25.9
- '@babel/helper-compilation-targets': 7.25.9
- '@babel/helper-plugin-utils': 7.25.9
- '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0)
- '@babel/traverse': 7.25.9
+ '@babel/helper-compilation-targets': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-replace-supers': 7.25.9(@babel/core@7.29.7)
+ '@babel/traverse': 7.29.7
globals: 11.12.0
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
- '@babel/template': 7.26.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/template': 7.29.7
- '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0)
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.29.7)
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0)
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.29.7)
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-exponentiation-operator@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-exponentiation-operator@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
+ '@babel/core': 7.29.7
'@babel/helper-builder-binary-assignment-operator-visitor': 7.25.9
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-for-of@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-for-of@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/helper-skip-transparent-expression-wrappers': 7.25.9
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-compilation-targets': 7.25.9
- '@babel/helper-plugin-utils': 7.25.9
- '@babel/traverse': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-compilation-targets': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/traverse': 7.29.7
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-literals@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-literals@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0)
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7)
+ '@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-modules-commonjs@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-modules-commonjs@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0)
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7)
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/helper-simple-access': 7.25.9
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-modules-systemjs@7.29.4(@babel/core@7.26.0)':
+ '@babel/plugin-transform-modules-systemjs@7.29.4(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-module-transforms': 7.28.6(@babel/core@7.26.0)
+ '@babel/core': 7.29.7
+ '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7)
'@babel/helper-plugin-utils': 7.28.6
- '@babel/helper-validator-identifier': 7.28.5
- '@babel/traverse': 7.29.0
+ '@babel/helper-validator-identifier': 7.29.7
+ '@babel/traverse': 7.29.7
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0)
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7)
+ '@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0)
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.29.7)
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-new-target@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-new-target@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-nullish-coalescing-operator@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-nullish-coalescing-operator@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-compilation-targets': 7.25.9
- '@babel/helper-plugin-utils': 7.25.9
- '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.0)
+ '@babel/core': 7.29.7
+ '@babel/helper-compilation-targets': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.29.7)
- '@babel/plugin-transform-object-super@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-object-super@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
- '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0)
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-replace-supers': 7.25.9(@babel/core@7.29.7)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/helper-skip-transparent-expression-wrappers': 7.25.9
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-parameters@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-parameters@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0)
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.29.7)
+ '@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
+ '@babel/core': 7.29.7
'@babel/helper-annotate-as-pure': 7.25.9
- '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0)
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.29.7)
+ '@babel/helper-plugin-utils': 7.28.6
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
regenerator-transform: 0.15.2
- '@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.26.0)':
+ '@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0)
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.29.7)
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-spread@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-spread@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/helper-skip-transparent-expression-wrappers': 7.25.9
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-template-literals@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-template-literals@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-typeof-symbol@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-typeof-symbol@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-typescript@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-typescript@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
+ '@babel/core': 7.29.7
'@babel/helper-annotate-as-pure': 7.25.9
- '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0)
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.29.7)
+ '@babel/helper-plugin-utils': 7.28.6
'@babel/helper-skip-transparent-expression-wrappers': 7.25.9
- '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0)
+ '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.29.7)
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0)
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.29.7)
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0)
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.29.7)
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.26.0)':
+ '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0)
- '@babel/helper-plugin-utils': 7.25.9
+ '@babel/core': 7.29.7
+ '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.29.7)
+ '@babel/helper-plugin-utils': 7.28.6
- '@babel/preset-env@7.26.0(@babel/core@7.26.0)':
+ '@babel/preset-env@7.26.0(@babel/core@7.29.7)':
dependencies:
'@babel/compat-data': 7.26.0
- '@babel/core': 7.26.0
+ '@babel/core': 7.29.7
'@babel/helper-compilation-targets': 7.25.9
'@babel/helper-plugin-utils': 7.25.9
'@babel/helper-validator-option': 7.25.9
- '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0)
- '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.26.0)
- '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0)
- '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.26.0)
- '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-async-generator-functions': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-block-scoped-functions': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.26.0)
- '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-dotall-regex': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-exponentiation-operator': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-for-of': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-json-strings': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-modules-systemjs': 7.29.4(@babel/core@7.26.0)
- '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-new-target': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-nullish-coalescing-operator': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-object-super': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.26.0)
- '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-template-literals': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-typeof-symbol': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.26.0)
- '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.26.0)
- babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.26.0)
- babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.26.0)
- babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.26.0)
+ '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.7)
+ '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.29.7)
+ '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.29.7)
+ '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.7)
+ '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-async-generator-functions': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-block-scoped-functions': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.29.7)
+ '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-dotall-regex': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-exponentiation-operator': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-for-of': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-json-strings': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-modules-systemjs': 7.29.4(@babel/core@7.29.7)
+ '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-new-target': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-nullish-coalescing-operator': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-object-super': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.29.7)
+ '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-template-literals': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-typeof-symbol': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.29.7)
+ '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.7)
+ babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.29.7)
+ babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.29.7)
+ babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.29.7)
core-js-compat: 3.38.1
semver: 6.3.1
transitivePeerDependencies:
- supports-color
- '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.26.0)':
+ '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.7)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-plugin-utils': 7.25.9
- '@babel/types': 7.28.5
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/types': 7.29.7
esutils: 2.0.3
'@babel/runtime@7.25.4':
dependencies:
regenerator-runtime: 0.14.1
- '@babel/template@7.26.9':
+ '@babel/template@7.29.7':
dependencies:
- '@babel/code-frame': 7.29.0
- '@babel/parser': 7.28.5
- '@babel/types': 7.28.5
+ '@babel/code-frame': 7.29.7
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
- '@babel/template@7.28.6':
+ '@babel/traverse@7.29.7':
dependencies:
- '@babel/code-frame': 7.29.0
- '@babel/parser': 7.29.3
- '@babel/types': 7.29.0
-
- '@babel/traverse@7.25.9':
- dependencies:
- '@babel/code-frame': 7.29.0
- '@babel/generator': 7.26.0
- '@babel/parser': 7.28.5
- '@babel/template': 7.26.9
- '@babel/types': 7.28.5
- debug: 4.4.3
- globals: 11.12.0
- transitivePeerDependencies:
- - supports-color
-
- '@babel/traverse@7.29.0':
- dependencies:
- '@babel/code-frame': 7.29.0
- '@babel/generator': 7.29.1
- '@babel/helper-globals': 7.28.0
- '@babel/parser': 7.29.3
- '@babel/template': 7.28.6
- '@babel/types': 7.29.0
+ '@babel/code-frame': 7.29.7
+ '@babel/generator': 7.29.7
+ '@babel/helper-globals': 7.29.7
+ '@babel/parser': 7.29.7
+ '@babel/template': 7.29.7
+ '@babel/types': 7.29.7
debug: 4.4.3
transitivePeerDependencies:
- supports-color
@@ -7964,10 +7625,10 @@ snapshots:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5
- '@babel/types@7.29.0':
+ '@babel/types@7.29.7':
dependencies:
- '@babel/helper-string-parser': 7.27.1
- '@babel/helper-validator-identifier': 7.28.5
+ '@babel/helper-string-parser': 7.29.7
+ '@babel/helper-validator-identifier': 7.29.7
'@bufbuild/protobuf@2.5.2': {}
@@ -8053,7 +7714,7 @@ snapshots:
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
'@csstools/css-tokenizer': 3.0.4
- '@csstools/css-color-parser@4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
+ '@csstools/css-color-parser@4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
dependencies:
'@csstools/color-helpers': 6.0.2
'@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
@@ -8085,9 +7746,9 @@ snapshots:
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-tokenizer': 4.0.0
- '@csstools/postcss-alpha-function@2.0.5(postcss@8.5.14)':
+ '@csstools/postcss-alpha-function@2.0.6(postcss@8.5.14)':
dependencies:
- '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-tokenizer': 4.0.0
'@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14)
@@ -8100,36 +7761,36 @@ snapshots:
postcss: 8.5.14
postcss-selector-parser: 7.1.1
- '@csstools/postcss-color-function-display-p3-linear@2.0.4(postcss@8.5.14)':
+ '@csstools/postcss-color-function-display-p3-linear@2.0.5(postcss@8.5.14)':
dependencies:
- '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-tokenizer': 4.0.0
'@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14)
'@csstools/utilities': 3.0.0(postcss@8.5.14)
postcss: 8.5.14
- '@csstools/postcss-color-function@5.0.4(postcss@8.5.14)':
+ '@csstools/postcss-color-function@5.0.5(postcss@8.5.14)':
dependencies:
- '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-tokenizer': 4.0.0
'@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14)
'@csstools/utilities': 3.0.0(postcss@8.5.14)
postcss: 8.5.14
- '@csstools/postcss-color-mix-function@4.0.4(postcss@8.5.14)':
+ '@csstools/postcss-color-mix-function@4.0.5(postcss@8.5.14)':
dependencies:
- '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-tokenizer': 4.0.0
'@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14)
'@csstools/utilities': 3.0.0(postcss@8.5.14)
postcss: 8.5.14
- '@csstools/postcss-color-mix-variadic-function-arguments@2.0.4(postcss@8.5.14)':
+ '@csstools/postcss-color-mix-variadic-function-arguments@2.0.5(postcss@8.5.14)':
dependencies:
- '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-tokenizer': 4.0.0
'@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14)
@@ -8150,9 +7811,9 @@ snapshots:
'@csstools/utilities': 3.0.0(postcss@8.5.14)
postcss: 8.5.14
- '@csstools/postcss-contrast-color-function@3.0.4(postcss@8.5.14)':
+ '@csstools/postcss-contrast-color-function@3.0.5(postcss@8.5.14)':
dependencies:
- '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-tokenizer': 4.0.0
'@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14)
@@ -8177,25 +7838,25 @@ snapshots:
'@csstools/utilities': 3.0.0(postcss@8.5.14)
postcss: 8.5.14
- '@csstools/postcss-gamut-mapping@3.0.4(postcss@8.5.14)':
+ '@csstools/postcss-gamut-mapping@3.0.5(postcss@8.5.14)':
dependencies:
- '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-tokenizer': 4.0.0
postcss: 8.5.14
- '@csstools/postcss-gradients-interpolation-method@6.0.4(postcss@8.5.14)':
+ '@csstools/postcss-gradients-interpolation-method@6.0.5(postcss@8.5.14)':
dependencies:
- '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-tokenizer': 4.0.0
'@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14)
'@csstools/utilities': 3.0.0(postcss@8.5.14)
postcss: 8.5.14
- '@csstools/postcss-hwb-function@5.0.4(postcss@8.5.14)':
+ '@csstools/postcss-hwb-function@5.0.5(postcss@8.5.14)':
dependencies:
- '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-tokenizer': 4.0.0
'@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14)
@@ -8290,9 +7951,9 @@ snapshots:
postcss: 8.5.14
postcss-value-parser: 4.2.0
- '@csstools/postcss-oklab-function@5.0.4(postcss@8.5.14)':
+ '@csstools/postcss-oklab-function@5.0.5(postcss@8.5.14)':
dependencies:
- '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-tokenizer': 4.0.0
'@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14)
@@ -8321,9 +7982,9 @@ snapshots:
'@csstools/css-tokenizer': 4.0.0
postcss: 8.5.14
- '@csstools/postcss-relative-color-syntax@4.0.4(postcss@8.5.14)':
+ '@csstools/postcss-relative-color-syntax@4.0.5(postcss@8.5.14)':
dependencies:
- '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-tokenizer': 4.0.0
'@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14)
@@ -8389,238 +8050,82 @@ snapshots:
dependencies:
postcss: 8.5.14
- '@esbuild/aix-ppc64@0.25.12':
+ '@esbuild/aix-ppc64@0.28.1':
optional: true
- '@esbuild/aix-ppc64@0.27.5':
+ '@esbuild/android-arm64@0.28.1':
optional: true
- '@esbuild/aix-ppc64@0.28.0':
+ '@esbuild/android-arm@0.28.1':
optional: true
- '@esbuild/android-arm64@0.25.12':
+ '@esbuild/android-x64@0.28.1':
optional: true
- '@esbuild/android-arm64@0.27.5':
+ '@esbuild/darwin-arm64@0.28.1':
optional: true
- '@esbuild/android-arm64@0.28.0':
+ '@esbuild/darwin-x64@0.28.1':
optional: true
- '@esbuild/android-arm@0.25.12':
+ '@esbuild/freebsd-arm64@0.28.1':
optional: true
- '@esbuild/android-arm@0.27.5':
+ '@esbuild/freebsd-x64@0.28.1':
optional: true
- '@esbuild/android-arm@0.28.0':
+ '@esbuild/linux-arm64@0.28.1':
optional: true
- '@esbuild/android-x64@0.25.12':
+ '@esbuild/linux-arm@0.28.1':
optional: true
- '@esbuild/android-x64@0.27.5':
+ '@esbuild/linux-ia32@0.28.1':
optional: true
- '@esbuild/android-x64@0.28.0':
+ '@esbuild/linux-loong64@0.28.1':
optional: true
- '@esbuild/darwin-arm64@0.25.12':
+ '@esbuild/linux-mips64el@0.28.1':
optional: true
- '@esbuild/darwin-arm64@0.27.5':
+ '@esbuild/linux-ppc64@0.28.1':
optional: true
- '@esbuild/darwin-arm64@0.28.0':
+ '@esbuild/linux-riscv64@0.28.1':
optional: true
- '@esbuild/darwin-x64@0.25.12':
+ '@esbuild/linux-s390x@0.28.1':
optional: true
- '@esbuild/darwin-x64@0.27.5':
+ '@esbuild/linux-x64@0.28.1':
optional: true
- '@esbuild/darwin-x64@0.28.0':
+ '@esbuild/netbsd-arm64@0.28.1':
optional: true
- '@esbuild/freebsd-arm64@0.25.12':
+ '@esbuild/netbsd-x64@0.28.1':
optional: true
- '@esbuild/freebsd-arm64@0.27.5':
+ '@esbuild/openbsd-arm64@0.28.1':
optional: true
- '@esbuild/freebsd-arm64@0.28.0':
+ '@esbuild/openbsd-x64@0.28.1':
optional: true
- '@esbuild/freebsd-x64@0.25.12':
+ '@esbuild/openharmony-arm64@0.28.1':
optional: true
- '@esbuild/freebsd-x64@0.27.5':
+ '@esbuild/sunos-x64@0.28.1':
optional: true
- '@esbuild/freebsd-x64@0.28.0':
+ '@esbuild/win32-arm64@0.28.1':
optional: true
- '@esbuild/linux-arm64@0.25.12':
+ '@esbuild/win32-ia32@0.28.1':
optional: true
- '@esbuild/linux-arm64@0.27.5':
- optional: true
-
- '@esbuild/linux-arm64@0.28.0':
- optional: true
-
- '@esbuild/linux-arm@0.25.12':
- optional: true
-
- '@esbuild/linux-arm@0.27.5':
- optional: true
-
- '@esbuild/linux-arm@0.28.0':
- optional: true
-
- '@esbuild/linux-ia32@0.25.12':
- optional: true
-
- '@esbuild/linux-ia32@0.27.5':
- optional: true
-
- '@esbuild/linux-ia32@0.28.0':
- optional: true
-
- '@esbuild/linux-loong64@0.25.12':
- optional: true
-
- '@esbuild/linux-loong64@0.27.5':
- optional: true
-
- '@esbuild/linux-loong64@0.28.0':
- optional: true
-
- '@esbuild/linux-mips64el@0.25.12':
- optional: true
-
- '@esbuild/linux-mips64el@0.27.5':
- optional: true
-
- '@esbuild/linux-mips64el@0.28.0':
- optional: true
-
- '@esbuild/linux-ppc64@0.25.12':
- optional: true
-
- '@esbuild/linux-ppc64@0.27.5':
- optional: true
-
- '@esbuild/linux-ppc64@0.28.0':
- optional: true
-
- '@esbuild/linux-riscv64@0.25.12':
- optional: true
-
- '@esbuild/linux-riscv64@0.27.5':
- optional: true
-
- '@esbuild/linux-riscv64@0.28.0':
- optional: true
-
- '@esbuild/linux-s390x@0.25.12':
- optional: true
-
- '@esbuild/linux-s390x@0.27.5':
- optional: true
-
- '@esbuild/linux-s390x@0.28.0':
- optional: true
-
- '@esbuild/linux-x64@0.25.12':
- optional: true
-
- '@esbuild/linux-x64@0.27.5':
- optional: true
-
- '@esbuild/linux-x64@0.28.0':
- optional: true
-
- '@esbuild/netbsd-arm64@0.25.12':
- optional: true
-
- '@esbuild/netbsd-arm64@0.27.5':
- optional: true
-
- '@esbuild/netbsd-arm64@0.28.0':
- optional: true
-
- '@esbuild/netbsd-x64@0.25.12':
- optional: true
-
- '@esbuild/netbsd-x64@0.27.5':
- optional: true
-
- '@esbuild/netbsd-x64@0.28.0':
- optional: true
-
- '@esbuild/openbsd-arm64@0.25.12':
- optional: true
-
- '@esbuild/openbsd-arm64@0.27.5':
- optional: true
-
- '@esbuild/openbsd-arm64@0.28.0':
- optional: true
-
- '@esbuild/openbsd-x64@0.25.12':
- optional: true
-
- '@esbuild/openbsd-x64@0.27.5':
- optional: true
-
- '@esbuild/openbsd-x64@0.28.0':
- optional: true
-
- '@esbuild/openharmony-arm64@0.25.12':
- optional: true
-
- '@esbuild/openharmony-arm64@0.27.5':
- optional: true
-
- '@esbuild/openharmony-arm64@0.28.0':
- optional: true
-
- '@esbuild/sunos-x64@0.25.12':
- optional: true
-
- '@esbuild/sunos-x64@0.27.5':
- optional: true
-
- '@esbuild/sunos-x64@0.28.0':
- optional: true
-
- '@esbuild/win32-arm64@0.25.12':
- optional: true
-
- '@esbuild/win32-arm64@0.27.5':
- optional: true
-
- '@esbuild/win32-arm64@0.28.0':
- optional: true
-
- '@esbuild/win32-ia32@0.25.12':
- optional: true
-
- '@esbuild/win32-ia32@0.27.5':
- optional: true
-
- '@esbuild/win32-ia32@0.28.0':
- optional: true
-
- '@esbuild/win32-x64@0.25.12':
- optional: true
-
- '@esbuild/win32-x64@0.27.5':
- optional: true
-
- '@esbuild/win32-x64@0.28.0':
+ '@esbuild/win32-x64@0.28.1':
optional: true
'@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))':
@@ -8654,7 +8159,7 @@ snapshots:
globals: 14.0.0
ignore: 5.3.2
import-fresh: 3.3.0
- js-yaml: 4.1.1
+ js-yaml: 4.2.0
minimatch: 10.2.4
strip-json-comments: 3.1.1
transitivePeerDependencies:
@@ -8671,7 +8176,7 @@ snapshots:
'@exodus/bytes@1.8.0': {}
- '@faker-js/faker@10.4.0': {}
+ '@faker-js/faker@10.5.0': {}
'@floating-ui/core@1.7.3':
dependencies:
@@ -8791,7 +8296,7 @@ snapshots:
change-case: 5.4.4
globby: 14.1.0
histoire: 1.0.0-beta.1(@types/node@24.13.2)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(yaml@2.8.3)
- launch-editor: 2.10.0
+ launch-editor: 2.14.1
pathe: 1.1.2
vue: 3.5.27(typescript@5.9.3)
transitivePeerDependencies:
@@ -8827,7 +8332,7 @@ snapshots:
'@intlify/message-compiler': 11.2.8
'@intlify/shared': 11.2.8
acorn: 8.15.0
- esbuild: 0.25.12
+ esbuild: 0.28.1
escodegen: 2.1.0
estree-walker: 2.0.2
jsonc-eslint-parser: 2.4.0
@@ -8848,13 +8353,13 @@ snapshots:
'@intlify/shared@11.2.8': {}
- '@intlify/unplugin-vue-i18n@11.0.3(@vue/compiler-dom@3.5.27)(eslint@9.39.4(jiti@2.6.1))(rollup@4.61.1)(typescript@5.9.3)(vue-i18n@11.2.8(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))':
+ '@intlify/unplugin-vue-i18n@11.0.3(@vue/compiler-dom@3.5.27)(eslint@9.39.4(jiti@2.6.1))(rollup@4.62.0)(typescript@5.9.3)(vue-i18n@11.2.8(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1))
'@intlify/bundle-utils': 11.0.3(vue-i18n@11.2.8(vue@3.5.27(typescript@5.9.3)))
'@intlify/shared': 11.2.8
'@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.2.8)(@vue/compiler-dom@3.5.27)(vue-i18n@11.2.8(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
- '@rollup/pluginutils': 5.1.3(rollup@4.61.1)
+ '@rollup/pluginutils': 5.1.3(rollup@4.62.0)
'@typescript-eslint/scope-manager': 8.58.0
'@typescript-eslint/typescript-estree': 8.58.0(typescript@5.9.3)
debug: 4.4.3
@@ -8895,33 +8400,20 @@ snapshots:
'@jridgewell/sourcemap-codec': 1.5.5
'@jridgewell/trace-mapping': 0.3.31
- '@jridgewell/gen-mapping@0.3.5':
- dependencies:
- '@jridgewell/set-array': 1.2.1
- '@jridgewell/sourcemap-codec': 1.5.5
- '@jridgewell/trace-mapping': 0.3.25
-
'@jridgewell/remapping@2.3.5':
dependencies:
- '@jridgewell/gen-mapping': 0.3.5
- '@jridgewell/trace-mapping': 0.3.25
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
'@jridgewell/resolve-uri@3.1.2': {}
- '@jridgewell/set-array@1.2.1': {}
-
'@jridgewell/source-map@0.3.6':
dependencies:
- '@jridgewell/gen-mapping': 0.3.5
- '@jridgewell/trace-mapping': 0.3.25
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
'@jridgewell/sourcemap-codec@1.5.5': {}
- '@jridgewell/trace-mapping@0.3.25':
- dependencies:
- '@jridgewell/resolve-uri': 3.1.2
- '@jridgewell/sourcemap-codec': 1.5.5
-
'@jridgewell/trace-mapping@0.3.31':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
@@ -9111,122 +8603,122 @@ snapshots:
'@rolldown/pluginutils@1.0.1': {}
- '@rollup/plugin-babel@6.1.0(@babel/core@7.26.0)(rollup@4.61.1)':
+ '@rollup/plugin-babel@6.1.0(@babel/core@7.29.7)(rollup@4.62.0)':
dependencies:
- '@babel/core': 7.26.0
+ '@babel/core': 7.29.7
'@babel/helper-module-imports': 7.25.9
- '@rollup/pluginutils': 5.1.3(rollup@4.61.1)
+ '@rollup/pluginutils': 5.1.3(rollup@4.62.0)
optionalDependencies:
- rollup: 4.61.1
+ rollup: 4.62.0
transitivePeerDependencies:
- supports-color
- '@rollup/plugin-node-resolve@16.0.3(rollup@4.61.1)':
+ '@rollup/plugin-node-resolve@16.0.3(rollup@4.62.0)':
dependencies:
- '@rollup/pluginutils': 5.1.3(rollup@4.61.1)
+ '@rollup/pluginutils': 5.1.3(rollup@4.62.0)
'@types/resolve': 1.20.2
deepmerge: 4.3.1
is-module: 1.0.0
resolve: 1.22.8
optionalDependencies:
- rollup: 4.61.1
+ rollup: 4.62.0
- '@rollup/plugin-replace@6.0.3(rollup@4.61.1)':
+ '@rollup/plugin-replace@6.0.3(rollup@4.62.0)':
dependencies:
- '@rollup/pluginutils': 5.1.3(rollup@4.61.1)
+ '@rollup/pluginutils': 5.1.3(rollup@4.62.0)
magic-string: 0.30.21
optionalDependencies:
- rollup: 4.61.1
+ rollup: 4.62.0
- '@rollup/plugin-terser@1.0.0(rollup@4.61.1)':
+ '@rollup/plugin-terser@1.0.0(rollup@4.62.0)':
dependencies:
serialize-javascript: 7.0.5
smob: 1.5.0
terser: 5.31.6
optionalDependencies:
- rollup: 4.61.1
+ rollup: 4.62.0
- '@rollup/pluginutils@5.1.3(rollup@4.61.1)':
+ '@rollup/pluginutils@5.1.3(rollup@4.62.0)':
dependencies:
'@types/estree': 1.0.9
estree-walker: 2.0.2
picomatch: 4.0.4
optionalDependencies:
- rollup: 4.61.1
+ rollup: 4.62.0
- '@rollup/rollup-android-arm-eabi@4.61.1':
+ '@rollup/rollup-android-arm-eabi@4.62.0':
optional: true
- '@rollup/rollup-android-arm64@4.61.1':
+ '@rollup/rollup-android-arm64@4.62.0':
optional: true
- '@rollup/rollup-darwin-arm64@4.61.1':
+ '@rollup/rollup-darwin-arm64@4.62.0':
optional: true
- '@rollup/rollup-darwin-x64@4.61.1':
+ '@rollup/rollup-darwin-x64@4.62.0':
optional: true
- '@rollup/rollup-freebsd-arm64@4.61.1':
+ '@rollup/rollup-freebsd-arm64@4.62.0':
optional: true
- '@rollup/rollup-freebsd-x64@4.61.1':
+ '@rollup/rollup-freebsd-x64@4.62.0':
optional: true
- '@rollup/rollup-linux-arm-gnueabihf@4.61.1':
+ '@rollup/rollup-linux-arm-gnueabihf@4.62.0':
optional: true
- '@rollup/rollup-linux-arm-musleabihf@4.61.1':
+ '@rollup/rollup-linux-arm-musleabihf@4.62.0':
optional: true
- '@rollup/rollup-linux-arm64-gnu@4.61.1':
+ '@rollup/rollup-linux-arm64-gnu@4.62.0':
optional: true
- '@rollup/rollup-linux-arm64-musl@4.61.1':
+ '@rollup/rollup-linux-arm64-musl@4.62.0':
optional: true
- '@rollup/rollup-linux-loong64-gnu@4.61.1':
+ '@rollup/rollup-linux-loong64-gnu@4.62.0':
optional: true
- '@rollup/rollup-linux-loong64-musl@4.61.1':
+ '@rollup/rollup-linux-loong64-musl@4.62.0':
optional: true
- '@rollup/rollup-linux-ppc64-gnu@4.61.1':
+ '@rollup/rollup-linux-ppc64-gnu@4.62.0':
optional: true
- '@rollup/rollup-linux-ppc64-musl@4.61.1':
+ '@rollup/rollup-linux-ppc64-musl@4.62.0':
optional: true
- '@rollup/rollup-linux-riscv64-gnu@4.61.1':
+ '@rollup/rollup-linux-riscv64-gnu@4.62.0':
optional: true
- '@rollup/rollup-linux-riscv64-musl@4.61.1':
+ '@rollup/rollup-linux-riscv64-musl@4.62.0':
optional: true
- '@rollup/rollup-linux-s390x-gnu@4.61.1':
+ '@rollup/rollup-linux-s390x-gnu@4.62.0':
optional: true
- '@rollup/rollup-linux-x64-gnu@4.61.1':
+ '@rollup/rollup-linux-x64-gnu@4.62.0':
optional: true
- '@rollup/rollup-linux-x64-musl@4.61.1':
+ '@rollup/rollup-linux-x64-musl@4.62.0':
optional: true
- '@rollup/rollup-openbsd-x64@4.61.1':
+ '@rollup/rollup-openbsd-x64@4.62.0':
optional: true
- '@rollup/rollup-openharmony-arm64@4.61.1':
+ '@rollup/rollup-openharmony-arm64@4.62.0':
optional: true
- '@rollup/rollup-win32-arm64-msvc@4.61.1':
+ '@rollup/rollup-win32-arm64-msvc@4.62.0':
optional: true
- '@rollup/rollup-win32-ia32-msvc@4.61.1':
+ '@rollup/rollup-win32-ia32-msvc@4.62.0':
optional: true
- '@rollup/rollup-win32-x64-gnu@4.61.1':
+ '@rollup/rollup-win32-x64-gnu@4.62.0':
optional: true
- '@rollup/rollup-win32-x64-msvc@4.61.1':
+ '@rollup/rollup-win32-x64-msvc@4.62.0':
optional: true
'@sentry-internal/browser-utils@10.36.0':
@@ -9259,7 +8751,7 @@ snapshots:
'@sentry/bundler-plugin-core@3.6.1':
dependencies:
- '@babel/core': 7.26.0
+ '@babel/core': 7.29.7
'@sentry/babel-plugin-component-annotate': 3.6.1
'@sentry/cli': 2.58.5
dotenv: 16.6.1
@@ -9372,72 +8864,72 @@ snapshots:
'@standard-schema/spec@1.1.0': {}
- '@tailwindcss/node@4.3.0':
+ '@tailwindcss/node@4.3.1':
dependencies:
'@jridgewell/remapping': 2.3.5
- enhanced-resolve: 5.21.3
- jiti: 2.6.1
+ enhanced-resolve: 5.21.6
+ jiti: 2.7.0
lightningcss: 1.32.0
magic-string: 0.30.21
source-map-js: 1.2.1
- tailwindcss: 4.3.0
+ tailwindcss: 4.3.1
- '@tailwindcss/oxide-android-arm64@4.3.0':
+ '@tailwindcss/oxide-android-arm64@4.3.1':
optional: true
- '@tailwindcss/oxide-darwin-arm64@4.3.0':
+ '@tailwindcss/oxide-darwin-arm64@4.3.1':
optional: true
- '@tailwindcss/oxide-darwin-x64@4.3.0':
+ '@tailwindcss/oxide-darwin-x64@4.3.1':
optional: true
- '@tailwindcss/oxide-freebsd-x64@4.3.0':
+ '@tailwindcss/oxide-freebsd-x64@4.3.1':
optional: true
- '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0':
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.1':
optional: true
- '@tailwindcss/oxide-linux-arm64-gnu@4.3.0':
+ '@tailwindcss/oxide-linux-arm64-gnu@4.3.1':
optional: true
- '@tailwindcss/oxide-linux-arm64-musl@4.3.0':
+ '@tailwindcss/oxide-linux-arm64-musl@4.3.1':
optional: true
- '@tailwindcss/oxide-linux-x64-gnu@4.3.0':
+ '@tailwindcss/oxide-linux-x64-gnu@4.3.1':
optional: true
- '@tailwindcss/oxide-linux-x64-musl@4.3.0':
+ '@tailwindcss/oxide-linux-x64-musl@4.3.1':
optional: true
- '@tailwindcss/oxide-wasm32-wasi@4.3.0':
+ '@tailwindcss/oxide-wasm32-wasi@4.3.1':
optional: true
- '@tailwindcss/oxide-win32-arm64-msvc@4.3.0':
+ '@tailwindcss/oxide-win32-arm64-msvc@4.3.1':
optional: true
- '@tailwindcss/oxide-win32-x64-msvc@4.3.0':
+ '@tailwindcss/oxide-win32-x64-msvc@4.3.1':
optional: true
- '@tailwindcss/oxide@4.3.0':
+ '@tailwindcss/oxide@4.3.1':
optionalDependencies:
- '@tailwindcss/oxide-android-arm64': 4.3.0
- '@tailwindcss/oxide-darwin-arm64': 4.3.0
- '@tailwindcss/oxide-darwin-x64': 4.3.0
- '@tailwindcss/oxide-freebsd-x64': 4.3.0
- '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.0
- '@tailwindcss/oxide-linux-arm64-gnu': 4.3.0
- '@tailwindcss/oxide-linux-arm64-musl': 4.3.0
- '@tailwindcss/oxide-linux-x64-gnu': 4.3.0
- '@tailwindcss/oxide-linux-x64-musl': 4.3.0
- '@tailwindcss/oxide-wasm32-wasi': 4.3.0
- '@tailwindcss/oxide-win32-arm64-msvc': 4.3.0
- '@tailwindcss/oxide-win32-x64-msvc': 4.3.0
+ '@tailwindcss/oxide-android-arm64': 4.3.1
+ '@tailwindcss/oxide-darwin-arm64': 4.3.1
+ '@tailwindcss/oxide-darwin-x64': 4.3.1
+ '@tailwindcss/oxide-freebsd-x64': 4.3.1
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.1
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.3.1
+ '@tailwindcss/oxide-linux-arm64-musl': 4.3.1
+ '@tailwindcss/oxide-linux-x64-gnu': 4.3.1
+ '@tailwindcss/oxide-linux-x64-musl': 4.3.1
+ '@tailwindcss/oxide-wasm32-wasi': 4.3.1
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.3.1
+ '@tailwindcss/oxide-win32-x64-msvc': 4.3.1
- '@tailwindcss/vite@4.3.0(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))':
+ '@tailwindcss/vite@4.3.1(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))':
dependencies:
- '@tailwindcss/node': 4.3.0
- '@tailwindcss/oxide': 4.3.0
- tailwindcss: 4.3.0
+ '@tailwindcss/node': 4.3.1
+ '@tailwindcss/oxide': 4.3.1
+ tailwindcss: 4.3.1
vite: 7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)
'@tiptap/core@3.17.0(@tiptap/pm@3.17.0)':
@@ -9746,14 +9238,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/eslint-plugin@8.61.0(@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
+ '@typescript-eslint/eslint-plugin@8.61.1(@typescript-eslint/parser@8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 8.61.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
- '@typescript-eslint/scope-manager': 8.61.0
- '@typescript-eslint/type-utils': 8.61.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
- '@typescript-eslint/utils': 8.61.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
- '@typescript-eslint/visitor-keys': 8.61.0
+ '@typescript-eslint/parser': 8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/scope-manager': 8.61.1
+ '@typescript-eslint/type-utils': 8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.61.1
eslint: 9.39.4(jiti@2.6.1)
ignore: 7.0.5
natural-compare: 1.4.0
@@ -9774,12 +9266,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
+ '@typescript-eslint/parser@8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
- '@typescript-eslint/scope-manager': 8.61.0
- '@typescript-eslint/types': 8.61.0
- '@typescript-eslint/typescript-estree': 8.61.0(typescript@5.9.3)
- '@typescript-eslint/visitor-keys': 8.61.0
+ '@typescript-eslint/scope-manager': 8.61.1
+ '@typescript-eslint/types': 8.61.1
+ '@typescript-eslint/typescript-estree': 8.61.1(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.61.1
debug: 4.4.3
eslint: 9.39.4(jiti@2.6.1)
typescript: 5.9.3
@@ -9788,8 +9280,8 @@ snapshots:
'@typescript-eslint/project-service@8.58.0(typescript@5.9.3)':
dependencies:
- '@typescript-eslint/tsconfig-utils': 8.60.1(typescript@5.9.3)
- '@typescript-eslint/types': 8.60.1
+ '@typescript-eslint/tsconfig-utils': 8.61.0(typescript@5.9.3)
+ '@typescript-eslint/types': 8.61.0
debug: 4.4.3
typescript: 5.9.3
transitivePeerDependencies:
@@ -9797,17 +9289,17 @@ snapshots:
'@typescript-eslint/project-service@8.60.1(typescript@5.9.3)':
dependencies:
- '@typescript-eslint/tsconfig-utils': 8.60.1(typescript@5.9.3)
- '@typescript-eslint/types': 8.60.1
+ '@typescript-eslint/tsconfig-utils': 8.61.0(typescript@5.9.3)
+ '@typescript-eslint/types': 8.61.0
debug: 4.4.3
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/project-service@8.61.0(typescript@5.9.3)':
+ '@typescript-eslint/project-service@8.61.1(typescript@5.9.3)':
dependencies:
- '@typescript-eslint/tsconfig-utils': 8.61.0(typescript@5.9.3)
- '@typescript-eslint/types': 8.61.0
+ '@typescript-eslint/tsconfig-utils': 8.61.1(typescript@5.9.3)
+ '@typescript-eslint/types': 8.61.1
debug: 4.4.3
typescript: 5.9.3
transitivePeerDependencies:
@@ -9823,10 +9315,10 @@ snapshots:
'@typescript-eslint/types': 8.60.1
'@typescript-eslint/visitor-keys': 8.60.1
- '@typescript-eslint/scope-manager@8.61.0':
+ '@typescript-eslint/scope-manager@8.61.1':
dependencies:
- '@typescript-eslint/types': 8.61.0
- '@typescript-eslint/visitor-keys': 8.61.0
+ '@typescript-eslint/types': 8.61.1
+ '@typescript-eslint/visitor-keys': 8.61.1
'@typescript-eslint/tsconfig-utils@8.58.0(typescript@5.9.3)':
dependencies:
@@ -9840,6 +9332,10 @@ snapshots:
dependencies:
typescript: 5.9.3
+ '@typescript-eslint/tsconfig-utils@8.61.1(typescript@5.9.3)':
+ dependencies:
+ typescript: 5.9.3
+
'@typescript-eslint/type-utils@8.60.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/types': 8.60.1
@@ -9852,11 +9348,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/type-utils@8.61.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
+ '@typescript-eslint/type-utils@8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
- '@typescript-eslint/types': 8.61.0
- '@typescript-eslint/typescript-estree': 8.61.0(typescript@5.9.3)
- '@typescript-eslint/utils': 8.61.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/types': 8.61.1
+ '@typescript-eslint/typescript-estree': 8.61.1(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
debug: 4.4.3
eslint: 9.39.4(jiti@2.6.1)
ts-api-utils: 2.5.0(typescript@5.9.3)
@@ -9870,6 +9366,8 @@ snapshots:
'@typescript-eslint/types@8.61.0': {}
+ '@typescript-eslint/types@8.61.1': {}
+
'@typescript-eslint/typescript-estree@8.58.0(typescript@5.9.3)':
dependencies:
'@typescript-eslint/project-service': 8.58.0(typescript@5.9.3)
@@ -9900,12 +9398,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/typescript-estree@8.61.0(typescript@5.9.3)':
+ '@typescript-eslint/typescript-estree@8.61.1(typescript@5.9.3)':
dependencies:
- '@typescript-eslint/project-service': 8.61.0(typescript@5.9.3)
- '@typescript-eslint/tsconfig-utils': 8.61.0(typescript@5.9.3)
- '@typescript-eslint/types': 8.61.0
- '@typescript-eslint/visitor-keys': 8.61.0
+ '@typescript-eslint/project-service': 8.61.1(typescript@5.9.3)
+ '@typescript-eslint/tsconfig-utils': 8.61.1(typescript@5.9.3)
+ '@typescript-eslint/types': 8.61.1
+ '@typescript-eslint/visitor-keys': 8.61.1
debug: 4.4.3
minimatch: 10.2.4
semver: 7.7.3
@@ -9926,12 +9424,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.61.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
+ '@typescript-eslint/utils@8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1))
- '@typescript-eslint/scope-manager': 8.61.0
- '@typescript-eslint/types': 8.61.0
- '@typescript-eslint/typescript-estree': 8.61.0(typescript@5.9.3)
+ '@typescript-eslint/scope-manager': 8.61.1
+ '@typescript-eslint/types': 8.61.1
+ '@typescript-eslint/typescript-estree': 8.61.1(typescript@5.9.3)
eslint: 9.39.4(jiti@2.6.1)
typescript: 5.9.3
transitivePeerDependencies:
@@ -9947,9 +9445,9 @@ snapshots:
'@typescript-eslint/types': 8.60.1
eslint-visitor-keys: 5.0.0
- '@typescript-eslint/visitor-keys@8.61.0':
+ '@typescript-eslint/visitor-keys@8.61.1':
dependencies:
- '@typescript-eslint/types': 8.61.0
+ '@typescript-eslint/types': 8.61.1
eslint-visitor-keys: 5.0.0
'@ungap/structured-clone@1.3.0': {}
@@ -9960,44 +9458,44 @@ snapshots:
vite: 7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)
vue: 3.5.27(typescript@5.9.3)
- '@vitest/expect@4.1.8':
+ '@vitest/expect@4.1.9':
dependencies:
'@standard-schema/spec': 1.1.0
'@types/chai': 5.2.2
- '@vitest/spy': 4.1.8
- '@vitest/utils': 4.1.8
+ '@vitest/spy': 4.1.9
+ '@vitest/utils': 4.1.9
chai: 6.2.2
tinyrainbow: 3.1.0
- '@vitest/mocker@4.1.8(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))':
+ '@vitest/mocker@4.1.9(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))':
dependencies:
- '@vitest/spy': 4.1.8
+ '@vitest/spy': 4.1.9
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)
- '@vitest/pretty-format@4.1.8':
+ '@vitest/pretty-format@4.1.9':
dependencies:
tinyrainbow: 3.1.0
- '@vitest/runner@4.1.8':
+ '@vitest/runner@4.1.9':
dependencies:
- '@vitest/utils': 4.1.8
+ '@vitest/utils': 4.1.9
pathe: 2.0.3
- '@vitest/snapshot@4.1.8':
+ '@vitest/snapshot@4.1.9':
dependencies:
- '@vitest/pretty-format': 4.1.8
- '@vitest/utils': 4.1.8
+ '@vitest/pretty-format': 4.1.9
+ '@vitest/utils': 4.1.9
magic-string: 0.30.21
pathe: 2.0.3
- '@vitest/spy@4.1.8': {}
+ '@vitest/spy@4.1.9': {}
- '@vitest/utils@4.1.8':
+ '@vitest/utils@4.1.9':
dependencies:
- '@vitest/pretty-format': 4.1.8
+ '@vitest/pretty-format': 4.1.9
convert-source-map: 2.0.0
tinyrainbow: 3.1.0
@@ -10015,30 +9513,30 @@ snapshots:
'@vue/babel-helper-vue-transform-on@1.2.5': {}
- '@vue/babel-plugin-jsx@1.2.5(@babel/core@7.26.0)':
+ '@vue/babel-plugin-jsx@1.2.5(@babel/core@7.29.7)':
dependencies:
- '@babel/helper-module-imports': 7.25.9
- '@babel/helper-plugin-utils': 7.25.9
- '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0)
- '@babel/template': 7.26.9
- '@babel/traverse': 7.25.9
- '@babel/types': 7.28.5
+ '@babel/helper-module-imports': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.29.7)
+ '@babel/template': 7.29.7
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
'@vue/babel-helper-vue-transform-on': 1.2.5
- '@vue/babel-plugin-resolve-type': 1.2.5(@babel/core@7.26.0)
+ '@vue/babel-plugin-resolve-type': 1.2.5(@babel/core@7.29.7)
html-tags: 3.3.1
svg-tags: 1.0.0
optionalDependencies:
- '@babel/core': 7.26.0
+ '@babel/core': 7.29.7
transitivePeerDependencies:
- supports-color
- '@vue/babel-plugin-resolve-type@1.2.5(@babel/core@7.26.0)':
+ '@vue/babel-plugin-resolve-type@1.2.5(@babel/core@7.29.7)':
dependencies:
- '@babel/code-frame': 7.29.0
- '@babel/core': 7.26.0
- '@babel/helper-module-imports': 7.25.9
- '@babel/helper-plugin-utils': 7.25.9
- '@babel/parser': 7.28.5
+ '@babel/code-frame': 7.29.7
+ '@babel/core': 7.29.7
+ '@babel/helper-module-imports': 7.29.7
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/parser': 7.29.7
'@vue/compiler-sfc': 3.5.27
transitivePeerDependencies:
- supports-color
@@ -10079,10 +9577,10 @@ snapshots:
dependencies:
'@vue/devtools-kit': 7.7.7
- '@vue/devtools-core@8.1.2(vue@3.5.27(typescript@5.9.3))':
+ '@vue/devtools-core@8.1.3(vue@3.5.27(typescript@5.9.3))':
dependencies:
- '@vue/devtools-kit': 8.1.2
- '@vue/devtools-shared': 8.1.2
+ '@vue/devtools-kit': 8.1.3
+ '@vue/devtools-shared': 8.1.3
vue: 3.5.27(typescript@5.9.3)
'@vue/devtools-kit@7.7.7':
@@ -10095,9 +9593,9 @@ snapshots:
speakingurl: 14.0.1
superjson: 2.2.2
- '@vue/devtools-kit@8.1.2':
+ '@vue/devtools-kit@8.1.3':
dependencies:
- '@vue/devtools-shared': 8.1.2
+ '@vue/devtools-shared': 8.1.3
birpc: 2.6.1
hookable: 5.5.3
perfect-debounce: 2.0.0
@@ -10106,13 +9604,13 @@ snapshots:
dependencies:
rfdc: 1.4.1
- '@vue/devtools-shared@8.1.2': {}
+ '@vue/devtools-shared@8.1.3': {}
- '@vue/eslint-config-typescript@14.8.0(eslint-plugin-vue@10.9.2(@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
+ '@vue/eslint-config-typescript@14.8.0(eslint-plugin-vue@10.9.2(@typescript-eslint/parser@8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/utils': 8.60.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
eslint: 9.39.4(jiti@2.6.1)
- eslint-plugin-vue: 10.9.2(@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1)))
+ eslint-plugin-vue: 10.9.2(@typescript-eslint/parser@8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1)))
fast-glob: 3.3.3
typescript-eslint: 8.60.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
vue-eslint-parser: 10.4.0(eslint@9.39.4(jiti@2.6.1))
@@ -10121,7 +9619,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@vue/language-core@3.3.4':
+ '@vue/language-core@3.3.5':
dependencies:
'@volar/language-core': 2.4.28
'@vue/compiler-dom': 3.5.27
@@ -10307,34 +9805,34 @@ snapshots:
axios@1.16.0:
dependencies:
follow-redirects: 1.16.0
- form-data: 4.0.5
+ form-data: 4.0.6
proxy-from-env: 2.1.0
transitivePeerDependencies:
- debug
b4a@1.6.7: {}
- babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.26.0):
+ babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.29.7):
dependencies:
- '@babel/compat-data': 7.26.0
- '@babel/core': 7.26.0
- '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.26.0)
+ '@babel/compat-data': 7.29.7
+ '@babel/core': 7.29.7
+ '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.29.7)
semver: 6.3.1
transitivePeerDependencies:
- supports-color
- babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.26.0):
+ babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.29.7):
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.26.0)
+ '@babel/core': 7.29.7
+ '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.29.7)
core-js-compat: 3.38.1
transitivePeerDependencies:
- supports-color
- babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.26.0):
+ babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.29.7):
dependencies:
- '@babel/core': 7.26.0
- '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.26.0)
+ '@babel/core': 7.29.7
+ '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.29.7)
transitivePeerDependencies:
- supports-color
@@ -10618,7 +10116,7 @@ snapshots:
dependencies:
env-paths: 2.2.1
import-fresh: 3.3.0
- js-yaml: 4.1.1
+ js-yaml: 4.2.0
parse-json: 5.2.0
optionalDependencies:
typescript: 5.9.3
@@ -10811,7 +10309,7 @@ snapshots:
dependencies:
domelementtype: 2.3.0
- dompurify@3.4.0:
+ dompurify@3.4.9:
optionalDependencies:
'@types/trusted-types': 2.0.7
@@ -10872,7 +10370,7 @@ snapshots:
dependencies:
once: 1.4.0
- enhanced-resolve@5.21.3:
+ enhanced-resolve@5.21.6:
dependencies:
graceful-fs: 4.2.11
tapable: 2.3.3
@@ -10977,92 +10475,34 @@ snapshots:
is-date-object: 1.0.5
is-symbol: 1.0.4
- esbuild@0.25.12:
+ esbuild@0.28.1:
optionalDependencies:
- '@esbuild/aix-ppc64': 0.25.12
- '@esbuild/android-arm': 0.25.12
- '@esbuild/android-arm64': 0.25.12
- '@esbuild/android-x64': 0.25.12
- '@esbuild/darwin-arm64': 0.25.12
- '@esbuild/darwin-x64': 0.25.12
- '@esbuild/freebsd-arm64': 0.25.12
- '@esbuild/freebsd-x64': 0.25.12
- '@esbuild/linux-arm': 0.25.12
- '@esbuild/linux-arm64': 0.25.12
- '@esbuild/linux-ia32': 0.25.12
- '@esbuild/linux-loong64': 0.25.12
- '@esbuild/linux-mips64el': 0.25.12
- '@esbuild/linux-ppc64': 0.25.12
- '@esbuild/linux-riscv64': 0.25.12
- '@esbuild/linux-s390x': 0.25.12
- '@esbuild/linux-x64': 0.25.12
- '@esbuild/netbsd-arm64': 0.25.12
- '@esbuild/netbsd-x64': 0.25.12
- '@esbuild/openbsd-arm64': 0.25.12
- '@esbuild/openbsd-x64': 0.25.12
- '@esbuild/openharmony-arm64': 0.25.12
- '@esbuild/sunos-x64': 0.25.12
- '@esbuild/win32-arm64': 0.25.12
- '@esbuild/win32-ia32': 0.25.12
- '@esbuild/win32-x64': 0.25.12
-
- esbuild@0.27.5:
- optionalDependencies:
- '@esbuild/aix-ppc64': 0.27.5
- '@esbuild/android-arm': 0.27.5
- '@esbuild/android-arm64': 0.27.5
- '@esbuild/android-x64': 0.27.5
- '@esbuild/darwin-arm64': 0.27.5
- '@esbuild/darwin-x64': 0.27.5
- '@esbuild/freebsd-arm64': 0.27.5
- '@esbuild/freebsd-x64': 0.27.5
- '@esbuild/linux-arm': 0.27.5
- '@esbuild/linux-arm64': 0.27.5
- '@esbuild/linux-ia32': 0.27.5
- '@esbuild/linux-loong64': 0.27.5
- '@esbuild/linux-mips64el': 0.27.5
- '@esbuild/linux-ppc64': 0.27.5
- '@esbuild/linux-riscv64': 0.27.5
- '@esbuild/linux-s390x': 0.27.5
- '@esbuild/linux-x64': 0.27.5
- '@esbuild/netbsd-arm64': 0.27.5
- '@esbuild/netbsd-x64': 0.27.5
- '@esbuild/openbsd-arm64': 0.27.5
- '@esbuild/openbsd-x64': 0.27.5
- '@esbuild/openharmony-arm64': 0.27.5
- '@esbuild/sunos-x64': 0.27.5
- '@esbuild/win32-arm64': 0.27.5
- '@esbuild/win32-ia32': 0.27.5
- '@esbuild/win32-x64': 0.27.5
-
- esbuild@0.28.0:
- optionalDependencies:
- '@esbuild/aix-ppc64': 0.28.0
- '@esbuild/android-arm': 0.28.0
- '@esbuild/android-arm64': 0.28.0
- '@esbuild/android-x64': 0.28.0
- '@esbuild/darwin-arm64': 0.28.0
- '@esbuild/darwin-x64': 0.28.0
- '@esbuild/freebsd-arm64': 0.28.0
- '@esbuild/freebsd-x64': 0.28.0
- '@esbuild/linux-arm': 0.28.0
- '@esbuild/linux-arm64': 0.28.0
- '@esbuild/linux-ia32': 0.28.0
- '@esbuild/linux-loong64': 0.28.0
- '@esbuild/linux-mips64el': 0.28.0
- '@esbuild/linux-ppc64': 0.28.0
- '@esbuild/linux-riscv64': 0.28.0
- '@esbuild/linux-s390x': 0.28.0
- '@esbuild/linux-x64': 0.28.0
- '@esbuild/netbsd-arm64': 0.28.0
- '@esbuild/netbsd-x64': 0.28.0
- '@esbuild/openbsd-arm64': 0.28.0
- '@esbuild/openbsd-x64': 0.28.0
- '@esbuild/openharmony-arm64': 0.28.0
- '@esbuild/sunos-x64': 0.28.0
- '@esbuild/win32-arm64': 0.28.0
- '@esbuild/win32-ia32': 0.28.0
- '@esbuild/win32-x64': 0.28.0
+ '@esbuild/aix-ppc64': 0.28.1
+ '@esbuild/android-arm': 0.28.1
+ '@esbuild/android-arm64': 0.28.1
+ '@esbuild/android-x64': 0.28.1
+ '@esbuild/darwin-arm64': 0.28.1
+ '@esbuild/darwin-x64': 0.28.1
+ '@esbuild/freebsd-arm64': 0.28.1
+ '@esbuild/freebsd-x64': 0.28.1
+ '@esbuild/linux-arm': 0.28.1
+ '@esbuild/linux-arm64': 0.28.1
+ '@esbuild/linux-ia32': 0.28.1
+ '@esbuild/linux-loong64': 0.28.1
+ '@esbuild/linux-mips64el': 0.28.1
+ '@esbuild/linux-ppc64': 0.28.1
+ '@esbuild/linux-riscv64': 0.28.1
+ '@esbuild/linux-s390x': 0.28.1
+ '@esbuild/linux-x64': 0.28.1
+ '@esbuild/netbsd-arm64': 0.28.1
+ '@esbuild/netbsd-x64': 0.28.1
+ '@esbuild/openbsd-arm64': 0.28.1
+ '@esbuild/openbsd-x64': 0.28.1
+ '@esbuild/openharmony-arm64': 0.28.1
+ '@esbuild/sunos-x64': 0.28.1
+ '@esbuild/win32-arm64': 0.28.1
+ '@esbuild/win32-ia32': 0.28.1
+ '@esbuild/win32-x64': 0.28.1
escalade@3.2.0: {}
@@ -11089,7 +10529,7 @@ snapshots:
module-replacements: 2.11.0
semver: 7.7.3
- eslint-plugin-vue@10.9.2(@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))):
+ eslint-plugin-vue@10.9.2(@typescript-eslint/parser@8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))):
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1))
eslint: 9.39.4(jiti@2.6.1)
@@ -11100,7 +10540,7 @@ snapshots:
vue-eslint-parser: 10.4.0(eslint@9.39.4(jiti@2.6.1))
xml-name-validator: 4.0.0
optionalDependencies:
- '@typescript-eslint/parser': 8.61.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/parser': 8.61.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
eslint-scope@8.4.0:
dependencies:
@@ -11198,7 +10638,7 @@ snapshots:
dependencies:
chardet: 0.7.0
iconv-lite: 0.4.24
- tmp: 0.2.6
+ tmp: 0.2.7
extract-zip@2.0.1:
dependencies:
@@ -11322,7 +10762,7 @@ snapshots:
cross-spawn: 7.0.6
signal-exit: 4.1.0
- form-data@4.0.5:
+ form-data@4.0.6:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
@@ -11503,7 +10943,7 @@ snapshots:
section-matter: 1.0.0
strip-bom-string: 1.0.0
- happy-dom@20.10.2:
+ happy-dom@20.10.5:
dependencies:
'@types/node': 24.13.2
'@types/whatwg-mimetype': 3.0.2
@@ -11585,9 +11025,9 @@ snapshots:
gray-matter: 4.0.3
jiti: 2.6.1
jsdom: 27.4.0
- markdown-it: 14.1.1
- markdown-it-anchor: 9.2.0(@types/markdown-it@14.1.2)(markdown-it@14.1.1)
- markdown-it-attrs: 4.3.1(markdown-it@14.1.1)
+ markdown-it: 14.2.0
+ markdown-it-anchor: 9.2.0(@types/markdown-it@14.1.2)(markdown-it@14.2.0)
+ markdown-it-attrs: 4.3.1(markdown-it@14.2.0)
markdown-it-emoji: 3.0.0
micromatch: 4.0.8
mrmime: 2.0.0
@@ -11916,6 +11356,8 @@ snapshots:
jiti@2.6.1: {}
+ jiti@2.7.0: {}
+
joi@18.2.1:
dependencies:
'@hapi/address': 5.1.1
@@ -11945,7 +11387,7 @@ snapshots:
argparse: 1.0.10
esprima: 4.0.1
- js-yaml@4.1.1:
+ js-yaml@4.2.0:
dependencies:
argparse: 2.0.1
@@ -12032,7 +11474,7 @@ snapshots:
dependencies:
package-json: 10.0.1
- launch-editor@2.10.0:
+ launch-editor@2.14.1:
dependencies:
picocolors: 1.1.1
shell-quote: 1.8.4
@@ -12095,7 +11537,7 @@ snapshots:
lines-and-columns@1.2.4: {}
- linkify-it@5.0.0:
+ linkify-it@5.0.1:
dependencies:
uc.micro: 2.1.0
@@ -12152,22 +11594,22 @@ snapshots:
map-obj@4.3.0: {}
- markdown-it-anchor@9.2.0(@types/markdown-it@14.1.2)(markdown-it@14.1.1):
+ markdown-it-anchor@9.2.0(@types/markdown-it@14.1.2)(markdown-it@14.2.0):
dependencies:
'@types/markdown-it': 14.1.2
- markdown-it: 14.1.1
+ markdown-it: 14.2.0
- markdown-it-attrs@4.3.1(markdown-it@14.1.1):
+ markdown-it-attrs@4.3.1(markdown-it@14.2.0):
dependencies:
- markdown-it: 14.1.1
+ markdown-it: 14.2.0
markdown-it-emoji@3.0.0: {}
- markdown-it@14.1.1:
+ markdown-it@14.2.0:
dependencies:
argparse: 2.0.1
entities: 4.5.0
- linkify-it: 5.0.0
+ linkify-it: 5.0.1
mdurl: 2.0.0
punycode.js: 2.3.1
uc.micro: 2.1.0
@@ -12525,9 +11967,9 @@ snapshots:
postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-color-functional-notation@8.0.4(postcss@8.5.14):
+ postcss-color-functional-notation@8.0.5(postcss@8.5.14):
dependencies:
- '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-tokenizer': 4.0.0
'@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14)
@@ -12621,9 +12063,9 @@ snapshots:
postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-lab-function@8.0.4(postcss@8.5.14):
+ postcss-lab-function@8.0.5(postcss@8.5.14):
dependencies:
- '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-color-parser': 4.1.7(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
'@csstools/css-tokenizer': 4.0.0
'@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14)
@@ -12662,23 +12104,23 @@ snapshots:
postcss: 8.5.14
postcss-value-parser: 4.2.0
- postcss-preset-env@11.3.0(postcss@8.5.14):
+ postcss-preset-env@11.3.1(postcss@8.5.14):
dependencies:
- '@csstools/postcss-alpha-function': 2.0.5(postcss@8.5.14)
+ '@csstools/postcss-alpha-function': 2.0.6(postcss@8.5.14)
'@csstools/postcss-cascade-layers': 6.0.0(postcss@8.5.14)
- '@csstools/postcss-color-function': 5.0.4(postcss@8.5.14)
- '@csstools/postcss-color-function-display-p3-linear': 2.0.4(postcss@8.5.14)
- '@csstools/postcss-color-mix-function': 4.0.4(postcss@8.5.14)
- '@csstools/postcss-color-mix-variadic-function-arguments': 2.0.4(postcss@8.5.14)
+ '@csstools/postcss-color-function': 5.0.5(postcss@8.5.14)
+ '@csstools/postcss-color-function-display-p3-linear': 2.0.5(postcss@8.5.14)
+ '@csstools/postcss-color-mix-function': 4.0.5(postcss@8.5.14)
+ '@csstools/postcss-color-mix-variadic-function-arguments': 2.0.5(postcss@8.5.14)
'@csstools/postcss-container-rule-prelude-list': 1.0.1(postcss@8.5.14)
'@csstools/postcss-content-alt-text': 3.0.1(postcss@8.5.14)
- '@csstools/postcss-contrast-color-function': 3.0.4(postcss@8.5.14)
+ '@csstools/postcss-contrast-color-function': 3.0.5(postcss@8.5.14)
'@csstools/postcss-exponential-functions': 3.0.3(postcss@8.5.14)
'@csstools/postcss-font-format-keywords': 5.0.0(postcss@8.5.14)
'@csstools/postcss-font-width-property': 1.0.0(postcss@8.5.14)
- '@csstools/postcss-gamut-mapping': 3.0.4(postcss@8.5.14)
- '@csstools/postcss-gradients-interpolation-method': 6.0.4(postcss@8.5.14)
- '@csstools/postcss-hwb-function': 5.0.4(postcss@8.5.14)
+ '@csstools/postcss-gamut-mapping': 3.0.5(postcss@8.5.14)
+ '@csstools/postcss-gradients-interpolation-method': 6.0.5(postcss@8.5.14)
+ '@csstools/postcss-hwb-function': 5.0.5(postcss@8.5.14)
'@csstools/postcss-ic-unit': 5.0.1(postcss@8.5.14)
'@csstools/postcss-image-function': 1.0.0(postcss@8.5.14)
'@csstools/postcss-initial': 3.0.0(postcss@8.5.14)
@@ -12694,12 +12136,12 @@ snapshots:
'@csstools/postcss-mixins': 1.0.0(postcss@8.5.14)
'@csstools/postcss-nested-calc': 5.0.0(postcss@8.5.14)
'@csstools/postcss-normalize-display-values': 5.0.1(postcss@8.5.14)
- '@csstools/postcss-oklab-function': 5.0.4(postcss@8.5.14)
+ '@csstools/postcss-oklab-function': 5.0.5(postcss@8.5.14)
'@csstools/postcss-position-area-property': 2.0.0(postcss@8.5.14)
'@csstools/postcss-progressive-custom-properties': 5.1.0(postcss@8.5.14)
'@csstools/postcss-property-rule-prelude-list': 2.0.0(postcss@8.5.14)
'@csstools/postcss-random-function': 3.0.3(postcss@8.5.14)
- '@csstools/postcss-relative-color-syntax': 4.0.4(postcss@8.5.14)
+ '@csstools/postcss-relative-color-syntax': 4.0.5(postcss@8.5.14)
'@csstools/postcss-scope-pseudo-class': 5.0.0(postcss@8.5.14)
'@csstools/postcss-sign-functions': 2.0.3(postcss@8.5.14)
'@csstools/postcss-stepped-value-functions': 5.0.3(postcss@8.5.14)
@@ -12717,7 +12159,7 @@ snapshots:
postcss: 8.5.14
postcss-attribute-case-insensitive: 8.0.0(postcss@8.5.14)
postcss-clamp: 4.1.0(postcss@8.5.14)
- postcss-color-functional-notation: 8.0.4(postcss@8.5.14)
+ postcss-color-functional-notation: 8.0.5(postcss@8.5.14)
postcss-color-hex-alpha: 11.0.0(postcss@8.5.14)
postcss-color-rebeccapurple: 11.0.0(postcss@8.5.14)
postcss-custom-media: 12.0.1(postcss@8.5.14)
@@ -12730,7 +12172,7 @@ snapshots:
postcss-font-variant: 5.0.0(postcss@8.5.14)
postcss-gap-properties: 7.0.0(postcss@8.5.14)
postcss-image-set-function: 8.0.0(postcss@8.5.14)
- postcss-lab-function: 8.0.4(postcss@8.5.14)
+ postcss-lab-function: 8.0.5(postcss@8.5.14)
postcss-logical: 9.0.0(postcss@8.5.14)
postcss-nesting: 14.0.0(postcss@8.5.14)
postcss-opacity-percentage: 3.0.0(postcss@8.5.14)
@@ -12845,7 +12287,7 @@ snapshots:
prosemirror-markdown@1.13.1:
dependencies:
'@types/markdown-it': 14.1.2
- markdown-it: 14.1.1
+ markdown-it: 14.2.0
prosemirror-model: 1.25.0
prosemirror-menu@1.2.4:
@@ -13098,44 +12540,44 @@ snapshots:
rfdc@1.4.1: {}
- rollup-plugin-visualizer@6.0.11(rollup@4.61.1):
+ rollup-plugin-visualizer@6.0.11(rollup@4.62.0):
dependencies:
open: 8.4.2
picomatch: 4.0.4
source-map: 0.7.4
yargs: 17.7.2
optionalDependencies:
- rollup: 4.61.1
+ rollup: 4.62.0
- rollup@4.61.1:
+ rollup@4.62.0:
dependencies:
'@types/estree': 1.0.9
optionalDependencies:
- '@rollup/rollup-android-arm-eabi': 4.61.1
- '@rollup/rollup-android-arm64': 4.61.1
- '@rollup/rollup-darwin-arm64': 4.61.1
- '@rollup/rollup-darwin-x64': 4.61.1
- '@rollup/rollup-freebsd-arm64': 4.61.1
- '@rollup/rollup-freebsd-x64': 4.61.1
- '@rollup/rollup-linux-arm-gnueabihf': 4.61.1
- '@rollup/rollup-linux-arm-musleabihf': 4.61.1
- '@rollup/rollup-linux-arm64-gnu': 4.61.1
- '@rollup/rollup-linux-arm64-musl': 4.61.1
- '@rollup/rollup-linux-loong64-gnu': 4.61.1
- '@rollup/rollup-linux-loong64-musl': 4.61.1
- '@rollup/rollup-linux-ppc64-gnu': 4.61.1
- '@rollup/rollup-linux-ppc64-musl': 4.61.1
- '@rollup/rollup-linux-riscv64-gnu': 4.61.1
- '@rollup/rollup-linux-riscv64-musl': 4.61.1
- '@rollup/rollup-linux-s390x-gnu': 4.61.1
- '@rollup/rollup-linux-x64-gnu': 4.61.1
- '@rollup/rollup-linux-x64-musl': 4.61.1
- '@rollup/rollup-openbsd-x64': 4.61.1
- '@rollup/rollup-openharmony-arm64': 4.61.1
- '@rollup/rollup-win32-arm64-msvc': 4.61.1
- '@rollup/rollup-win32-ia32-msvc': 4.61.1
- '@rollup/rollup-win32-x64-gnu': 4.61.1
- '@rollup/rollup-win32-x64-msvc': 4.61.1
+ '@rollup/rollup-android-arm-eabi': 4.62.0
+ '@rollup/rollup-android-arm64': 4.62.0
+ '@rollup/rollup-darwin-arm64': 4.62.0
+ '@rollup/rollup-darwin-x64': 4.62.0
+ '@rollup/rollup-freebsd-arm64': 4.62.0
+ '@rollup/rollup-freebsd-x64': 4.62.0
+ '@rollup/rollup-linux-arm-gnueabihf': 4.62.0
+ '@rollup/rollup-linux-arm-musleabihf': 4.62.0
+ '@rollup/rollup-linux-arm64-gnu': 4.62.0
+ '@rollup/rollup-linux-arm64-musl': 4.62.0
+ '@rollup/rollup-linux-loong64-gnu': 4.62.0
+ '@rollup/rollup-linux-loong64-musl': 4.62.0
+ '@rollup/rollup-linux-ppc64-gnu': 4.62.0
+ '@rollup/rollup-linux-ppc64-musl': 4.62.0
+ '@rollup/rollup-linux-riscv64-gnu': 4.62.0
+ '@rollup/rollup-linux-riscv64-musl': 4.62.0
+ '@rollup/rollup-linux-s390x-gnu': 4.62.0
+ '@rollup/rollup-linux-x64-gnu': 4.62.0
+ '@rollup/rollup-linux-x64-musl': 4.62.0
+ '@rollup/rollup-openbsd-x64': 4.62.0
+ '@rollup/rollup-openharmony-arm64': 4.62.0
+ '@rollup/rollup-win32-arm64-msvc': 4.62.0
+ '@rollup/rollup-win32-ia32-msvc': 4.62.0
+ '@rollup/rollup-win32-x64-gnu': 4.62.0
+ '@rollup/rollup-win32-x64-msvc': 4.62.0
fsevents: 2.3.3
rope-sequence@1.3.4: {}
@@ -13723,7 +13165,7 @@ snapshots:
string-width: 4.2.3
strip-ansi: 6.0.1
- tailwindcss@4.3.0: {}
+ tailwindcss@4.3.1: {}
tapable@2.3.3: {}
@@ -13794,7 +13236,7 @@ snapshots:
dependencies:
tldts-core: 7.0.19
- tmp@0.2.6: {}
+ tmp@0.2.7: {}
to-regex-range@5.0.1:
dependencies:
@@ -14095,11 +13537,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
- vite-plugin-vue-devtools@8.1.2(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(vue@3.5.27(typescript@5.9.3)):
+ vite-plugin-vue-devtools@8.1.3(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))(vue@3.5.27(typescript@5.9.3)):
dependencies:
- '@vue/devtools-core': 8.1.2(vue@3.5.27(typescript@5.9.3))
- '@vue/devtools-kit': 8.1.2
- '@vue/devtools-shared': 8.1.2
+ '@vue/devtools-core': 8.1.3(vue@3.5.27(typescript@5.9.3))
+ '@vue/devtools-kit': 8.1.3
+ '@vue/devtools-shared': 8.1.3
sirv: 3.0.2
vite: 7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)
vite-plugin-inspect: 11.3.3(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))
@@ -14111,12 +13553,12 @@ snapshots:
vite-plugin-vue-inspector@6.0.0(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)):
dependencies:
- '@babel/core': 7.26.0
- '@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.26.0)
- '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0)
- '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.26.0)
- '@babel/plugin-transform-typescript': 7.25.9(@babel/core@7.26.0)
- '@vue/babel-plugin-jsx': 1.2.5(@babel/core@7.26.0)
+ '@babel/core': 7.29.7
+ '@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.29.7)
+ '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.29.7)
+ '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7)
+ '@babel/plugin-transform-typescript': 7.25.9(@babel/core@7.29.7)
+ '@vue/babel-plugin-jsx': 1.2.5(@babel/core@7.29.7)
'@vue/compiler-dom': 3.5.27
kolorist: 1.8.0
magic-string: 0.30.21
@@ -14134,11 +13576,11 @@ snapshots:
vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3):
dependencies:
- esbuild: 0.27.5
+ esbuild: 0.28.1
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
postcss: 8.5.14
- rollup: 4.61.1
+ rollup: 4.62.0
tinyglobby: 0.2.15
optionalDependencies:
'@types/node': 24.13.2
@@ -14150,15 +13592,15 @@ snapshots:
terser: 5.31.6
yaml: 2.8.3
- vitest@4.1.8(@types/node@24.13.2)(happy-dom@20.10.2)(jsdom@27.4.0)(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)):
+ vitest@4.1.9(@types/node@24.13.2)(happy-dom@20.10.5)(jsdom@27.4.0)(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3)):
dependencies:
- '@vitest/expect': 4.1.8
- '@vitest/mocker': 4.1.8(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))
- '@vitest/pretty-format': 4.1.8
- '@vitest/runner': 4.1.8
- '@vitest/snapshot': 4.1.8
- '@vitest/spy': 4.1.8
- '@vitest/utils': 4.1.8
+ '@vitest/expect': 4.1.9
+ '@vitest/mocker': 4.1.9(vite@7.3.5(@types/node@24.13.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.31.6)(yaml@2.8.3))
+ '@vitest/pretty-format': 4.1.9
+ '@vitest/runner': 4.1.9
+ '@vitest/snapshot': 4.1.9
+ '@vitest/spy': 4.1.9
+ '@vitest/utils': 4.1.9
es-module-lexer: 2.0.0
expect-type: 1.3.0
magic-string: 0.30.21
@@ -14174,7 +13616,7 @@ snapshots:
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 24.13.2
- happy-dom: 20.10.2
+ happy-dom: 20.10.5
jsdom: 27.4.0
transitivePeerDependencies:
- msw
@@ -14227,10 +13669,10 @@ snapshots:
'@vue/devtools-api': 6.6.4
vue: 3.5.27(typescript@5.9.3)
- vue-tsc@3.3.4(typescript@5.9.3):
+ vue-tsc@3.3.5(typescript@5.9.3):
dependencies:
'@volar/typescript': 2.4.28
- '@vue/language-core': 3.3.4
+ '@vue/language-core': 3.3.5
typescript: 5.9.3
vue@3.5.27(typescript@5.9.3):
@@ -14375,13 +13817,13 @@ snapshots:
workbox-build@7.4.1:
dependencies:
'@apideck/better-ajv-errors': 0.3.6(ajv@8.18.0)
- '@babel/core': 7.26.0
- '@babel/preset-env': 7.26.0(@babel/core@7.26.0)
+ '@babel/core': 7.29.7
+ '@babel/preset-env': 7.26.0(@babel/core@7.29.7)
'@babel/runtime': 7.25.4
- '@rollup/plugin-babel': 6.1.0(@babel/core@7.26.0)(rollup@4.61.1)
- '@rollup/plugin-node-resolve': 16.0.3(rollup@4.61.1)
- '@rollup/plugin-replace': 6.0.3(rollup@4.61.1)
- '@rollup/plugin-terser': 1.0.0(rollup@4.61.1)
+ '@rollup/plugin-babel': 6.1.0(@babel/core@7.29.7)(rollup@4.62.0)
+ '@rollup/plugin-node-resolve': 16.0.3(rollup@4.62.0)
+ '@rollup/plugin-replace': 6.0.3(rollup@4.62.0)
+ '@rollup/plugin-terser': 1.0.0(rollup@4.62.0)
'@trickfilm400/rollup-plugin-off-main-thread': 3.0.0-pre1
ajv: 8.18.0
common-tags: 1.8.2
@@ -14390,7 +13832,7 @@ snapshots:
fs-extra: 9.1.0
glob: 11.1.0
pretty-bytes: 5.6.0
- rollup: 4.61.1
+ rollup: 4.62.0
source-map: 0.8.0-beta.0
stringify-object: 3.3.0
strip-comments: 2.0.1
diff --git a/frontend/src/components/gantt/GanttChart.vue b/frontend/src/components/gantt/GanttChart.vue
index 989bd400a..f75753428 100644
--- a/frontend/src/components/gantt/GanttChart.vue
+++ b/frontend/src/components/gantt/GanttChart.vue
@@ -730,7 +730,7 @@ function focusTaskBar(rowId: string) {
setTimeout(() => {
const taskBarElement = document.querySelector(`[data-row-id="${rowId}"] [role="slider"]`) as HTMLElement
if (taskBarElement) {
- taskBarElement.focus()
+ taskBarElement.focus({preventScroll: true})
}
}, 0)
}
diff --git a/frontend/src/composables/useGlobalNow.ts b/frontend/src/composables/useGlobalNow.ts
index 83d9cf9ef..c5e3510e7 100644
--- a/frontend/src/composables/useGlobalNow.ts
+++ b/frontend/src/composables/useGlobalNow.ts
@@ -1,4 +1,4 @@
-import { ref } from 'vue'
+import { getCurrentInstance, ref } from 'vue'
import { createGlobalState, useIntervalFn } from '@vueuse/core'
import { onBeforeRouteUpdate } from 'vue-router'
@@ -18,10 +18,14 @@ export const useGlobalNow = createGlobalState(() => {
useIntervalFn(update, GLOBAL_NOW_INTERVAL, { immediate: true })
- // ensure the now value is refreshed when the route changes
- onBeforeRouteUpdate(() => {
- update()
- })
+ // Now that this state can be initialised from a plain helper (formatDateSince), the
+ // first caller is not guaranteed to be a component — guard the route hook accordingly.
+ if (getCurrentInstance()) {
+ // ensure the now value is refreshed when the route changes
+ onBeforeRouteUpdate(() => {
+ update()
+ })
+ }
return {
now,
diff --git a/frontend/src/helpers/time/formatDate.ts b/frontend/src/helpers/time/formatDate.ts
index 4ff9a4da5..ed7f4a3d7 100644
--- a/frontend/src/helpers/time/formatDate.ts
+++ b/frontend/src/helpers/time/formatDate.ts
@@ -5,6 +5,7 @@ import {i18n} from '@/i18n'
import {createSharedComposable} from '@vueuse/core'
import {computed, toValue, type MaybeRefOrGetter} from 'vue'
import {useDateDisplay} from '@/composables/useDateDisplay'
+import {useGlobalNow} from '@/composables/useGlobalNow'
import {useTimeFormat} from '@/composables/useTimeFormat'
import {DATE_DISPLAY, type DateDisplay} from '@/constants/dateDisplay'
import {TIME_FORMAT, type TimeFormat} from '@/constants/timeFormat'
@@ -49,8 +50,13 @@ export const formatDateSince = (date: Date | string | null) => {
const locale = DAYJS_LOCALE_MAPPING[i18n.global.locale.value.toLowerCase()] ?? 'en'
+ // Computing the relative string against the shared, ticking `now` (instead of fromNow's
+ // internal Date.now()) makes every reactive caller re-render on the 60s tick, so open views
+ // don't keep showing a stale "x minutes ago".
+ const {now} = useGlobalNow()
+
return date
- ? dayjs(date).locale(locale).fromNow()
+ ? dayjs(date).locale(locale).from(now.value)
: ''
}
diff --git a/frontend/src/i18n/lang/el-GR.json b/frontend/src/i18n/lang/el-GR.json
index 4848afad7..14837735b 100644
--- a/frontend/src/i18n/lang/el-GR.json
+++ b/frontend/src/i18n/lang/el-GR.json
@@ -172,6 +172,7 @@
"yyyy/mm/dd": "ΕΕΕΕ/ΜΜ/ΗΗ"
},
"timeFormat": "Μορφή ώρας",
+ "timeTrackingDefaultStart": "Ώρα έναρξης παρακολούθησης χρόνου με έξυπνο-γέμισμα",
"timeFormatOptions": {
"12h": "12 ώρες (ΠΜ/ΜΜ)",
"24h": "24 ώρες (ΩΩ:ΛΛ)"
@@ -781,7 +782,10 @@
"closeDialog": "Κλείσμο του διαλόγου",
"closeQuickActions": "Κλείσιμο των γρήγορων ενεργειών",
"skipToContent": "Μετάβαση στο κύριο περιεχόμενο",
- "sortBy": "Ταξινόμηση ανά"
+ "sortBy": "Ταξινόμηση ανά",
+ "dateRange": "Εύρος ημερομηνιών",
+ "notSet": "Μη ορισμένο",
+ "user": "Χρήστης"
},
"input": {
"projectColor": "Χρώμα έργου",
@@ -991,6 +995,7 @@
"repeatAfter": "Ορισμός Επαναλαμβανόμενου Διαστήματος",
"percentDone": "Ορισμός Προόδου",
"attachments": "Προσθήκη Συνημμένων",
+ "timeTracking": "Χρόνος ίχνους",
"relatedTasks": "Προσθήκη Συσχέτισης",
"moveProject": "Μετακίνηση",
"duplicate": "Αντιγραφή",
@@ -1460,6 +1465,32 @@
"frontendVersion": "Έκδοση frontend: {version}",
"apiVersion": "Έκδοση API: {version}"
},
+ "timeTracking": {
+ "title": "Ιχνηλάτηση χρόνου",
+ "stop": "Διακοπή χρονομέτρου",
+ "logTime": "Καταγραφή χρόνου",
+ "editEntry": "Επεξεργασία εγγραφής",
+ "form": {
+ "task": "Εργασία",
+ "taskSearch": "Αναζήτηση για μια εργασία…",
+ "commentPlaceholder": "Σε τι δουλέψατε;",
+ "save": "Αποθήκευση εγγραφής",
+ "startTimer": "Έναρξη χρονοµέτρου",
+ "update": "Ενημέρωση εγγραφής",
+ "smartFill": "Συμπλήρωση από την τελευταία καταχώριση"
+ },
+ "list": {
+ "emptyTask": "Δεν καταγράφηκε ακόμη χρόνος για αυτήν την εργασία.",
+ "emptyFiltered": "Δεν καταγράφηκε χρόνος με βάση τα επιλεγμένα φίλτρα.",
+ "total": "Σύνολο",
+ "time": "Ώρα",
+ "duration": "Διάρκεια"
+ },
+ "browse": {
+ "selectRange": "Επιλέξτε ένα εύρος",
+ "userSearch": "Αναζήτηση για ένα χρήστη…"
+ }
+ },
"time": {
"units": {
"seconds": "δευτερόλεπτο|δευτερόλεπτα",
diff --git a/frontend/src/i18n/lang/uk-UA.json b/frontend/src/i18n/lang/uk-UA.json
index d92642a0d..9c95bc7f1 100644
--- a/frontend/src/i18n/lang/uk-UA.json
+++ b/frontend/src/i18n/lang/uk-UA.json
@@ -1480,7 +1480,15 @@
"smartFill": "Заповнити з останнього запису"
},
"list": {
- "emptyTask": "Для цього завдання ще немає записів обліку часу."
+ "emptyTask": "Для цього завдання ще немає записів обліку часу.",
+ "emptyFiltered": "Немає записів обліку часу для вибраних фільтрів.",
+ "total": "Загалом",
+ "time": "Час",
+ "duration": "Тривалість"
+ },
+ "browse": {
+ "selectRange": "Обрати діапазон",
+ "userSearch": "Знайти користувача…"
}
},
"time": {
diff --git a/pkg/db/dump.go b/pkg/db/dump.go
index 1d9f803b9..be345e173 100644
--- a/pkg/db/dump.go
+++ b/pkg/db/dump.go
@@ -127,7 +127,7 @@ func RestoreAndTruncate(table string, contents []map[string]interface{}) (err er
return err
}
} else {
- if _, err := x.Query("TRUNCATE TABLE ?", table); err != nil {
+ if _, err := x.Query("TRUNCATE TABLE " + x.Quote(table)); err != nil {
return err
}
}
@@ -148,7 +148,7 @@ func TruncateAllTables() error {
return err
}
} else {
- if _, err := x.Query("TRUNCATE TABLE ?", name); err != nil {
+ if _, err := x.Query("TRUNCATE TABLE " + x.Quote(name)); err != nil {
return err
}
}
diff --git a/pkg/migration/20260617153629.go b/pkg/migration/20260617153629.go
new file mode 100644
index 000000000..d5ae3d772
--- /dev/null
+++ b/pkg/migration/20260617153629.go
@@ -0,0 +1,127 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package migration
+
+import (
+ "fmt"
+
+ "src.techknowlogick.com/xormigrate"
+ "xorm.io/xorm"
+ "xorm.io/xorm/schemas"
+)
+
+type taskPosition20260617153629 struct {
+ TaskID int64 `xorm:"bigint not null index"`
+ ProjectViewID int64 `xorm:"bigint not null index"`
+ Position float64 `xorm:"double not null"`
+}
+
+func (taskPosition20260617153629) TableName() string {
+ return "task_positions"
+}
+
+func init() {
+ migrations = append(migrations, &xormigrate.Migration{
+ ID: "20260617153629",
+ Description: "deduplicate task positions and add a unique index on task_id + project_view_id",
+ Migrate: func(tx *xorm.Engine) error {
+
+ s := tx.NewSession()
+ defer s.Close()
+
+ err := s.Begin()
+ if err != nil {
+ return err
+ }
+
+ // First remove all duplicate entries. A task may only ever have a
+ // single position per view; rapid task creation could race and
+ // insert more than one row before this constraint existed.
+ duplicates := []taskPosition20260617153629{}
+ err = s.
+ Select("task_id, project_view_id").
+ GroupBy("task_id, project_view_id").
+ Having("count(*) > 1").
+ Find(&duplicates)
+ if err != nil {
+ _ = s.Rollback()
+ return err
+ }
+
+ // Keep the lowest position of each group so the result is
+ // deterministic across databases.
+ kept := []taskPosition20260617153629{}
+ for _, dup := range duplicates {
+ row := taskPosition20260617153629{}
+ has, err := s.
+ Where("task_id = ? AND project_view_id = ?", dup.TaskID, dup.ProjectViewID).
+ OrderBy("position ASC").
+ Get(&row)
+ if err != nil {
+ _ = s.Rollback()
+ return err
+ }
+ if !has {
+ // The pair was just reported as duplicated by the GroupBy above,
+ // so a row must exist. If it doesn't, fail instead of continuing —
+ // the delete loop below would otherwise drop every row for the pair
+ // without re-inserting one.
+ _ = s.Rollback()
+ return fmt.Errorf("no task_positions row found for task %d and project view %d while deduplicating positions", dup.TaskID, dup.ProjectViewID)
+ }
+ kept = append(kept, row)
+ }
+
+ for _, dup := range duplicates {
+ _, err = s.
+ Where("task_id = ? AND project_view_id = ?", dup.TaskID, dup.ProjectViewID).
+ Delete(&taskPosition20260617153629{})
+ if err != nil {
+ _ = s.Rollback()
+ return err
+ }
+ }
+
+ for _, position := range kept {
+ _, err = s.Insert(&position)
+ if err != nil {
+ _ = s.Rollback()
+ return err
+ }
+ }
+
+ err = s.Commit()
+ if err != nil {
+ return err
+ }
+
+ // Then create the unique index
+ var query string
+ switch tx.Dialect().URI().DBType {
+ case schemas.MYSQL:
+ query = "CREATE UNIQUE INDEX UQE_task_positions_task_project_view ON task_positions (task_id, project_view_id)"
+ default:
+ query = "CREATE UNIQUE INDEX IF NOT EXISTS UQE_task_positions_task_project_view ON task_positions (task_id, project_view_id)"
+ }
+ _, err = tx.Exec(query)
+ return err
+ },
+ Rollback: func(_ *xorm.Engine) error {
+ return nil
+ },
+ })
+}
diff --git a/pkg/models/error.go b/pkg/models/error.go
index 2f9d652c9..8f1a47553 100644
--- a/pkg/models/error.go
+++ b/pkg/models/error.go
@@ -535,6 +535,34 @@ func (err *ErrProjectViewDoesNotExist) HTTPError() web.HTTPError {
}
}
+// ErrProjectHasNoBackground represents an error where a project has no background set.
+type ErrProjectHasNoBackground struct {
+ ProjectID int64
+}
+
+// IsErrProjectHasNoBackground checks if an error is ErrProjectHasNoBackground.
+func IsErrProjectHasNoBackground(err error) bool {
+ _, ok := err.(*ErrProjectHasNoBackground)
+ return ok
+}
+
+func (err *ErrProjectHasNoBackground) Error() string {
+ return fmt.Sprintf("Project has no background [ProjectID: %d]", err.ProjectID)
+}
+
+// ErrCodeProjectHasNoBackground holds the unique world-error code of this error
+const ErrCodeProjectHasNoBackground = 3015
+
+// HTTPError holds the http error description
+func (err *ErrProjectHasNoBackground) HTTPError() web.HTTPError {
+ return web.HTTPError{
+ HTTPCode: http.StatusNotFound,
+ Code: ErrCodeProjectHasNoBackground,
+ // Message kept verbatim from v1's inline handler error so the wire body is unchanged.
+ Message: "Project background not found",
+ }
+}
+
// ==============
// Task errors
// ==============
@@ -2596,3 +2624,32 @@ func (err ErrTimeEntryEndBeforeStart) HTTPError() web.HTTPError {
Message: "A time entry's end time cannot be before its start time.",
}
}
+
+// =================
+// User export errors
+// =================
+
+// ErrUserDataExportDoesNotExist represents an error where a user has no ready data export to download.
+type ErrUserDataExportDoesNotExist struct{}
+
+// IsErrUserDataExportDoesNotExist checks if an error is ErrUserDataExportDoesNotExist.
+func IsErrUserDataExportDoesNotExist(err error) bool {
+ _, ok := err.(ErrUserDataExportDoesNotExist)
+ return ok
+}
+
+func (err ErrUserDataExportDoesNotExist) Error() string {
+ return "No user data export found"
+}
+
+// ErrCodeUserDataExportDoesNotExist holds the unique world-error code of this error
+const ErrCodeUserDataExportDoesNotExist = 19001
+
+// HTTPError holds the http error description
+func (err ErrUserDataExportDoesNotExist) HTTPError() web.HTTPError {
+ return web.HTTPError{
+ HTTPCode: http.StatusNotFound,
+ Code: ErrCodeUserDataExportDoesNotExist,
+ Message: "No user data export found.",
+ }
+}
diff --git a/pkg/models/export.go b/pkg/models/export.go
index 4772fa2d5..2d9b57651 100644
--- a/pkg/models/export.go
+++ b/pkg/models/export.go
@@ -404,6 +404,64 @@ func exportProjectBackgrounds(s *xorm.Session, u *user.User, wr *zip.Writer) (er
return utils.WriteFilesToZip(backgroundFiles, wr)
}
+// GetUserDataExportFile loads the user's ready data export with its bytes open for
+// reading. It returns ErrUserDataExportDoesNotExist when the user never requested an
+// export or the underlying file is gone. The caller must close the returned reader.
+func GetUserDataExportFile(u *user.User) (*files.File, error) {
+ if u.ExportFileID == 0 {
+ return nil, ErrUserDataExportDoesNotExist{}
+ }
+
+ exportFile := &files.File{ID: u.ExportFileID}
+ if err := exportFile.LoadFileMetaByID(); err != nil {
+ if files.IsErrFileDoesNotExist(err) {
+ return nil, ErrUserDataExportDoesNotExist{}
+ }
+ return nil, err
+ }
+ if err := exportFile.LoadFileByID(); err != nil {
+ if os.IsNotExist(err) {
+ return nil, ErrUserDataExportDoesNotExist{}
+ }
+ return nil, err
+ }
+
+ return exportFile, nil
+}
+
+// GetUserDataExportStatus returns metadata about the user's current data export, or
+// nil when none exists. The expiry mirrors the cleanup cron's 7-day retention.
+func GetUserDataExportStatus(u *user.User) (*UserExportStatus, error) {
+ if u.ExportFileID == 0 {
+ return nil, nil
+ }
+
+ exportFile := &files.File{ID: u.ExportFileID}
+ if err := exportFile.LoadFileMetaByID(); err != nil {
+ // A missing meta row means there is no export — mirror the download path
+ // (404 there) instead of surfacing a 500.
+ if files.IsErrFileDoesNotExist(err) {
+ return nil, nil
+ }
+ return nil, err
+ }
+
+ return &UserExportStatus{
+ ID: exportFile.ID,
+ Size: exportFile.Size,
+ Created: exportFile.Created,
+ Expires: exportFile.Created.Add(7 * 24 * time.Hour),
+ }, nil
+}
+
+// UserExportStatus is the metadata returned for a user's current data export.
+type UserExportStatus struct {
+ ID int64 `json:"id" readOnly:"true" doc:"The id of the export file."`
+ Size uint64 `json:"size" readOnly:"true" doc:"The size of the export file in bytes."`
+ Created time.Time `json:"created" readOnly:"true" doc:"When the export was created."`
+ Expires time.Time `json:"expires" readOnly:"true" doc:"When the export will be automatically deleted (7 days after creation)."`
+}
+
func RegisterOldExportCleanupCron() {
const logPrefix = "[User Export Cleanup Cron] "
diff --git a/pkg/models/export_test.go b/pkg/models/export_test.go
new file mode 100644
index 000000000..f2d0fa2fb
--- /dev/null
+++ b/pkg/models/export_test.go
@@ -0,0 +1,53 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package models
+
+import (
+ "testing"
+
+ "code.vikunja.io/api/pkg/db"
+ "code.vikunja.io/api/pkg/user"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestGetUserDataExportStatus(t *testing.T) {
+ t.Run("no export", func(t *testing.T) {
+ db.LoadAndAssertFixtures(t)
+ status, err := GetUserDataExportStatus(&user.User{ID: 15})
+ require.NoError(t, err)
+ assert.Nil(t, status)
+ })
+
+ t.Run("with export", func(t *testing.T) {
+ db.LoadAndAssertFixtures(t)
+ status, err := GetUserDataExportStatus(&user.User{ID: 1, ExportFileID: 1})
+ require.NoError(t, err)
+ require.NotNil(t, status)
+ assert.Equal(t, int64(1), status.ID)
+ })
+
+ t.Run("export points at a missing file", func(t *testing.T) {
+ db.LoadAndAssertFixtures(t)
+ // A dangling ExportFileID must read as "no export" rather than erroring,
+ // matching the download path which 404s the same case.
+ status, err := GetUserDataExportStatus(&user.User{ID: 15, ExportFileID: 9999})
+ require.NoError(t, err)
+ assert.Nil(t, status)
+ })
+}
diff --git a/pkg/models/kanban_task_bucket_test.go b/pkg/models/kanban_task_bucket_test.go
index 6d1eb2f24..7bde2ade4 100644
--- a/pkg/models/kanban_task_bucket_test.go
+++ b/pkg/models/kanban_task_bucket_test.go
@@ -226,6 +226,68 @@ func TestTaskBucket_Update(t *testing.T) {
})
})
+ t.Run("saved filter: first task into empty limited bucket is allowed", func(t *testing.T) {
+ // Regression test for #2672: on a saved-filter kanban view the bucket
+ // limit was checked against the total number of tasks matching the
+ // filter instead of the number of tasks actually in the target bucket,
+ // so adding the first task to an empty limited bucket was wrongly
+ // rejected with ErrBucketLimitExceeded.
+ db.LoadAndAssertFixtures(t)
+ s := db.NewSession()
+ defer s.Close()
+
+ // A saved filter matching many tasks; the filter total is well above
+ // the bucket limit we set below.
+ sf := &SavedFilter{
+ Title: "limit-filter",
+ Filters: &TaskCollection{Filter: "done = false"},
+ }
+ err := sf.Create(s, u)
+ require.NoError(t, err)
+
+ filterProjectID := getProjectIDFromSavedFilterID(sf.ID)
+
+ view := &ProjectView{}
+ exists, err := s.Where("project_id = ? AND view_kind = ?", filterProjectID, ProjectViewKindKanban).Get(view)
+ require.NoError(t, err)
+ require.True(t, exists)
+
+ // All matching tasks are placed in the default bucket on creation;
+ // pick three of them to move into a fresh, empty bucket.
+ var defaultTasks []*TaskBucket
+ err = s.Where("project_view_id = ?", view.ID).Find(&defaultTasks)
+ require.NoError(t, err)
+ require.GreaterOrEqual(t, len(defaultTasks), 3, "filter must match enough tasks to exceed the bucket limit")
+
+ limitedBucket := &Bucket{
+ Title: "limited",
+ ProjectViewID: view.ID,
+ ProjectID: filterProjectID,
+ Limit: 2,
+ }
+ err = limitedBucket.Create(s, u)
+ require.NoError(t, err)
+
+ moveTaskToBucket := func(taskID int64) error {
+ tb := &TaskBucket{
+ TaskID: taskID,
+ BucketID: limitedBucket.ID,
+ ProjectViewID: view.ID,
+ ProjectID: filterProjectID,
+ }
+ return tb.Update(s, u)
+ }
+
+ // Moving the FIRST task into the empty bucket must succeed (0/2 -> 1/2).
+ require.NoError(t, moveTaskToBucket(defaultTasks[0].TaskID))
+ // The second one fills the bucket up to the limit (1/2 -> 2/2).
+ require.NoError(t, moveTaskToBucket(defaultTasks[1].TaskID))
+ // The third one would exceed the limit and must be rejected.
+ err = moveTaskToBucket(defaultTasks[2].TaskID)
+ require.Error(t, err)
+ assert.True(t, IsErrBucketLimitExceeded(err))
+ })
+
t.Run("keep done timestamp when moving task between projects", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
u := &user.User{ID: 1}
diff --git a/pkg/models/notifications_database.go b/pkg/models/notifications_database.go
index aaa71103c..75beef580 100644
--- a/pkg/models/notifications_database.go
+++ b/pkg/models/notifications_database.go
@@ -53,7 +53,13 @@ func (d *DatabaseNotifications) ReadAll(s *xorm.Session, a web.Auth, _ string, p
}
limit, start := getLimitFromPageIndex(page, perPage)
- return notifications.GetNotificationsForUser(s, a.GetID(), limit, start)
+ ns, resultCount, total, err := notifications.GetNotificationsForUser(s, a.GetID(), limit, start)
+ if err != nil {
+ return nil, 0, 0, err
+ }
+
+ refreshNotificationsUsers(s, ns)
+ return ns, resultCount, total, nil
}
// CanUpdate checks if a user can mark a notification as read.
diff --git a/pkg/models/notifications_refresh.go b/pkg/models/notifications_refresh.go
new file mode 100644
index 000000000..cc119aeae
--- /dev/null
+++ b/pkg/models/notifications_refresh.go
@@ -0,0 +1,110 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package models
+
+import (
+ "encoding/json"
+
+ "code.vikunja.io/api/pkg/log"
+ "code.vikunja.io/api/pkg/notifications"
+ "code.vikunja.io/api/pkg/user"
+
+ "xorm.io/xorm"
+)
+
+// refreshNotificationsUsers reloads each notification's embedded users from the
+// database. Notifications serialized before the acting user was resolved with
+// its full profile (#2720) stored only id+username, so without this they keep
+// rendering the auto-generated username instead of the display name. It runs at
+// read time and is not persisted; one cache is shared across the batch so a
+// user recurring across notifications is fetched only once.
+func refreshNotificationsUsers(s *xorm.Session, dbNotifications []*notifications.DatabaseNotification) {
+ cache := make(map[int64]*user.User)
+ for _, dbn := range dbNotifications {
+ refreshNotificationUsers(s, dbn, cache)
+ }
+}
+
+func refreshNotificationUsers(s *xorm.Session, dbn *notifications.DatabaseNotification, cache map[int64]*user.User) {
+ typed, ok := notifications.Lookup(dbn.Name)
+ if !ok {
+ return
+ }
+
+ raw, err := json.Marshal(dbn.Notification)
+ if err != nil {
+ log.Errorf("Could not marshal notification %d to refresh its users: %v", dbn.ID, err)
+ return
+ }
+ if err := json.Unmarshal(raw, typed); err != nil {
+ log.Errorf("Could not unmarshal notification %d to refresh its users: %v", dbn.ID, err)
+ return
+ }
+
+ for _, u := range notificationUsers(typed) {
+ refreshUser(s, u, cache)
+ }
+ dbn.Notification = typed
+}
+
+// notificationUsers returns the user fields a stored notification renders, so
+// they can be reloaded. New notification types carrying a user belong here.
+func notificationUsers(n notifications.Notification) []*user.User {
+ switch n := n.(type) {
+ case *TaskCommentNotification:
+ return []*user.User{n.Doer}
+ case *TaskAssignedNotification:
+ return []*user.User{n.Doer, n.Assignee}
+ case *TaskDeletedNotification:
+ return []*user.User{n.Doer}
+ case *ProjectCreatedNotification:
+ return []*user.User{n.Doer}
+ case *TeamMemberAddedNotification:
+ return []*user.User{n.Doer, n.Member}
+ case *UserMentionedInTaskNotification:
+ return []*user.User{n.Doer}
+ default:
+ return nil
+ }
+}
+
+// refreshUser overwrites the user in place with its current database row. A
+// disabled or locked account is still returned fully populated, so only a
+// missing user or a real database error leaves the stored value untouched.
+func refreshUser(s *xorm.Session, u *user.User, cache map[int64]*user.User) {
+ if u == nil || u.ID == 0 {
+ return
+ }
+
+ fresh, cached := cache[u.ID]
+ if !cached {
+ loaded, err := user.GetUserByID(s, u.ID)
+ if err != nil && !user.IsErrUserStatusError(err) {
+ if !user.IsErrUserDoesNotExist(err) {
+ log.Errorf("Could not refresh user %d for a notification: %v", u.ID, err)
+ }
+ cache[u.ID] = nil
+ return
+ }
+ fresh = loaded
+ cache[u.ID] = fresh
+ }
+
+ if fresh != nil {
+ *u = *fresh
+ }
+}
diff --git a/pkg/models/notifications_refresh_test.go b/pkg/models/notifications_refresh_test.go
new file mode 100644
index 000000000..b0cef2a43
--- /dev/null
+++ b/pkg/models/notifications_refresh_test.go
@@ -0,0 +1,109 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package models
+
+import (
+ "encoding/json"
+ "testing"
+
+ "code.vikunja.io/api/pkg/db"
+ "code.vikunja.io/api/pkg/notifications"
+ "code.vikunja.io/api/pkg/user"
+
+ "github.com/stretchr/testify/require"
+ "xorm.io/xorm"
+)
+
+// TestDatabaseNotifications_ReadAll_RefreshesUsers guards #2720 for notifications
+// already in the database: those were serialized with a partial doer (id +
+// username, no display Name), so reading them must reload the embedded users so
+// the display name is shown. The fix in the dispatch path only helps new
+// notifications; old rows are healed here at read time.
+func TestDatabaseNotifications_ReadAll_RefreshesUsers(t *testing.T) {
+ t.Run("fills in the display name from the database", func(t *testing.T) {
+ db.LoadAndAssertFixtures(t)
+ s := db.NewSession()
+ defer s.Close()
+
+ // user12 has the display name "Name with spaces" in the fixtures.
+ insertStoredNotification(t, s, 1, &TaskAssignedNotification{
+ Doer: &user.User{ID: 12, Username: "user12"},
+ Assignee: &user.User{ID: 12, Username: "user12"},
+ Task: &Task{ID: 1},
+ })
+
+ got := readAssignedNotification(t, s, 1)
+ require.Equal(t, "Name with spaces", got.Doer.GetName())
+ require.Equal(t, "Name with spaces", got.Assignee.GetName())
+ })
+
+ t.Run("keeps the stored value when the user no longer exists", func(t *testing.T) {
+ db.LoadAndAssertFixtures(t)
+ s := db.NewSession()
+ defer s.Close()
+
+ insertStoredNotification(t, s, 1, &TaskAssignedNotification{
+ Doer: &user.User{ID: 999999, Username: "ghost"},
+ Task: &Task{ID: 1},
+ })
+
+ got := readAssignedNotification(t, s, 1)
+ require.Equal(t, "ghost", got.Doer.Username)
+ })
+
+ t.Run("refreshes a disabled user", func(t *testing.T) {
+ db.LoadAndAssertFixtures(t)
+ s := db.NewSession()
+ defer s.Close()
+
+ // user17 is disabled in the fixtures; the reload must still win over the
+ // stale stored value.
+ insertStoredNotification(t, s, 1, &TaskAssignedNotification{
+ Doer: &user.User{ID: 17, Username: "stale"},
+ Task: &Task{ID: 1},
+ })
+
+ got := readAssignedNotification(t, s, 1)
+ require.Equal(t, "user17", got.Doer.Username)
+ })
+}
+
+func insertStoredNotification(t *testing.T, s *xorm.Session, notifiableID int64, n notifications.Notification) {
+ t.Helper()
+ content, err := json.Marshal(n)
+ require.NoError(t, err)
+ _, err = s.Insert(¬ifications.DatabaseNotification{
+ NotifiableID: notifiableID,
+ Notification: json.RawMessage(content),
+ Name: n.Name(),
+ })
+ require.NoError(t, err)
+}
+
+func readAssignedNotification(t *testing.T, s *xorm.Session, notifiableID int64) *TaskAssignedNotification {
+ t.Helper()
+ result, _, _, err := (&DatabaseNotifications{}).ReadAll(s, &user.User{ID: notifiableID}, "", 1, 50)
+ require.NoError(t, err)
+
+ for _, dbn := range result.([]*notifications.DatabaseNotification) {
+ if n, is := dbn.Notification.(*TaskAssignedNotification); is {
+ return n
+ }
+ }
+ t.Fatal("no task.assigned notification was returned")
+ return nil
+}
diff --git a/pkg/models/saved_filter_positions_test.go b/pkg/models/saved_filter_positions_test.go
index d233460d2..91bf09d9c 100644
--- a/pkg/models/saved_filter_positions_test.go
+++ b/pkg/models/saved_filter_positions_test.go
@@ -79,8 +79,17 @@ func TestCronInsertsNonZeroPosition(t *testing.T) {
require.NoError(t, err)
require.True(t, exists)
+ // Force the task to a zero position in this view to simulate the unhealed
+ // state. A task only ever has one position row per view, so update it if it
+ // already exists (e.g. created with the filter) instead of inserting a duplicate.
tp := &TaskPosition{TaskID: task.ID, ProjectViewID: view.ID, Position: 0}
- _, err = s.Insert(tp)
+ hasPosition, err := s.Where("task_id = ? AND project_view_id = ?", task.ID, view.ID).Exist(&TaskPosition{})
+ require.NoError(t, err)
+ if hasPosition {
+ _, err = s.Where("task_id = ? AND project_view_id = ?", task.ID, view.ID).Cols("position").Update(tp)
+ } else {
+ _, err = s.Insert(tp)
+ }
require.NoError(t, err)
_, err = calculateNewPositionForTask(s, u, task, view)
diff --git a/pkg/models/task_position.go b/pkg/models/task_position.go
index 07c2839cc..bcc09884b 100644
--- a/pkg/models/task_position.go
+++ b/pkg/models/task_position.go
@@ -33,9 +33,9 @@ const MinPositionSpacing = 0.01
type TaskPosition struct {
// The ID of the task this position is for
- TaskID int64 `xorm:"bigint not null index" json:"task_id" param:"task" readOnly:"true" doc:"The numeric id of the task this position belongs to. Taken from the URL; ignored in the request body."`
+ TaskID int64 `xorm:"bigint not null index unique(task_view)" json:"task_id" param:"task" readOnly:"true" doc:"The numeric id of the task this position belongs to. Taken from the URL; ignored in the request body."`
// The project view this task is related to
- ProjectViewID int64 `xorm:"bigint not null index" json:"project_view_id" doc:"The id of the project view this position applies to. Positions are stored per view, so the same task has an independent position in each of its project's views."`
+ ProjectViewID int64 `xorm:"bigint not null index unique(task_view)" json:"project_view_id" doc:"The id of the project view this position applies to. Positions are stored per view, so the same task has an independent position in each of its project's views."`
// The position of the task - any task project can be sorted as usual by this parameter.
// When accessing tasks via kanban buckets, this is primarily used to sort them based on a range
// We're using a float64 here to make it possible to put any task within any two other tasks (by changing the number).
@@ -341,6 +341,57 @@ func calculateNewPositionForTask(s *xorm.Session, a web.Auth, t *Task, view *Pro
}, nil
}
+type taskPositionKey struct {
+ taskID int64
+ viewID int64
+}
+
+// filterNewTaskPositions returns the positions whose (task_id, project_view_id)
+// row does not exist yet, also deduplicating within the slice. Position creation
+// during task creation can trigger a full recalculation (calculateNewPositionForTask
+// or moveTaskToDoneBuckets) that already persists rows for the new task, so inserting
+// the queued positions unconditionally would violate the unique index on
+// (task_id, project_view_id).
+func filterNewTaskPositions(s *xorm.Session, positions []*TaskPosition) ([]*TaskPosition, error) {
+ if len(positions) == 0 {
+ return positions, nil
+ }
+
+ taskIDs := make([]int64, 0, len(positions))
+ seenTask := make(map[int64]bool, len(positions))
+ for _, p := range positions {
+ if seenTask[p.TaskID] {
+ continue
+ }
+ seenTask[p.TaskID] = true
+ taskIDs = append(taskIDs, p.TaskID)
+ }
+
+ // Fetch all existing rows for the involved tasks in one query so this stays
+ // cheap when createTask runs in a loop (bulk import, project duplication).
+ existing := []*TaskPosition{}
+ err := s.In("task_id", taskIDs).Find(&existing)
+ if err != nil {
+ return nil, err
+ }
+
+ seen := make(map[taskPositionKey]bool, len(positions)+len(existing))
+ for _, e := range existing {
+ seen[taskPositionKey{taskID: e.TaskID, viewID: e.ProjectViewID}] = true
+ }
+
+ filtered := make([]*TaskPosition, 0, len(positions))
+ for _, p := range positions {
+ key := taskPositionKey{taskID: p.TaskID, viewID: p.ProjectViewID}
+ if seen[key] {
+ continue
+ }
+ seen[key] = true
+ filtered = append(filtered, p)
+ }
+ return filtered, nil
+}
+
// DeleteOrphanedTaskPositions removes task position records that reference
// tasks or project views that no longer exist.
// If dryRun is true, it counts the orphaned records without deleting them.
diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go
index ee5eda824..a49e4175e 100644
--- a/pkg/models/tasks.go
+++ b/pkg/models/tasks.go
@@ -830,9 +830,15 @@ func checkBucketLimit(s *xorm.Session, a web.Auth, t *Task, bucket *Bucket) (tas
}
if view.ProjectID < 0 || (view.Filter != nil && view.Filter.Filter != "") {
+ // For saved filters or views with a filter, the count must be scoped to
+ // this bucket *and* the filter: raw task_buckets rows can include tasks
+ // that no longer match the filter (#355), while the unscoped filter total
+ // counts tasks across all buckets, not just this one (#2672). ReadAll
+ // combines the bucket_id condition with the saved-filter / view filter.
tc := &TaskCollection{
ProjectID: view.ProjectID,
ProjectViewID: bucket.ProjectViewID,
+ Filter: "bucket_id = " + strconv.FormatInt(bucket.ID, 10),
}
_, _, taskCount, err = tc.ReadAll(s, a, "", 1, 1)
@@ -978,6 +984,13 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool, setB
return err
}
+ if len(positions) > 0 {
+ positions, err = filterNewTaskPositions(s, positions)
+ if err != nil {
+ return err
+ }
+ }
+
if len(positions) > 0 {
_, err = s.Insert(&positions)
if err != nil {
diff --git a/pkg/modules/auth/auth.go b/pkg/modules/auth/auth.go
index 97429aa13..f94537158 100644
--- a/pkg/modules/auth/auth.go
+++ b/pkg/modules/auth/auth.go
@@ -100,46 +100,75 @@ func ClearRefreshTokenCookie(c *echo.Context) {
SetRefreshTokenCookie(c, "", -1)
}
-// NewUserAuthTokenResponse creates a new user auth token response from a user object.
-func NewUserAuthTokenResponse(u *user.User, c *echo.Context, long bool) error {
+// IssuedUserToken bundles a freshly minted access token with the matching
+// refresh token and the cookie max-age both v1 and v2 use to set the
+// HttpOnly refresh cookie.
+type IssuedUserToken struct {
+ AccessToken string
+ RefreshToken string
+ CookieMaxAge int
+}
+
+// IssueUserToken creates a session for the user and mints a JWT access token plus
+// a refresh token for it. It is the transport-agnostic core both v1 (which writes
+// the echo response) and v2 (Huma) call; callers set the refresh cookie and the
+// Cache-Control header themselves via WriteUserAuthCookies.
+func IssueUserToken(ctx context.Context, u *user.User, deviceInfo, ipAddress string, long bool) (*IssuedUserToken, error) {
s := db.NewSession()
defer s.Close()
- deviceInfo := c.Request().UserAgent()
- ipAddress := c.RealIP()
-
session, err := models.CreateSession(s, u.ID, deviceInfo, ipAddress, long)
if err != nil {
_ = s.Rollback()
- return err
+ return nil, err
}
t, err := NewUserJWTAuthtoken(u, session.ID)
if err != nil {
_ = s.Rollback()
- return err
+ return nil, err
}
if err := s.Commit(); err != nil {
_ = s.Rollback()
- return err
+ return nil, err
}
- if err := events.DispatchWithContext(c.Request().Context(), &user.LoginSucceededEvent{User: u}); err != nil {
+ if err := events.DispatchWithContext(ctx, &user.LoginSucceededEvent{User: u}); err != nil {
log.Errorf("Could not dispatch login succeeded event: %s", err)
}
- // Set the refresh token as an HttpOnly cookie. The cookie is path-scoped
- // to the refresh endpoint, so the browser only sends it there. JavaScript
- // never sees the refresh token — this protects it from XSS.
cookieMaxAge := int(config.ServiceJWTTTL.GetInt64())
if long {
cookieMaxAge = int(config.ServiceJWTTTLLong.GetInt64())
}
- SetRefreshTokenCookie(c, session.RefreshToken, cookieMaxAge)
+ return &IssuedUserToken{
+ AccessToken: t,
+ RefreshToken: session.RefreshToken,
+ CookieMaxAge: cookieMaxAge,
+ }, nil
+}
+
+// WriteUserAuthCookies sets the HttpOnly refresh-token cookie and the
+// Cache-Control: no-store header on a response. The cookie is path-scoped to the
+// refresh endpoint, so the browser only sends it there; JavaScript never sees the
+// refresh token, which protects it from XSS. Shared by the v1 echo handlers and
+// the v2 Huma handlers (which reach the echo context via humaecho5.Unwrap).
+func WriteUserAuthCookies(c *echo.Context, token *IssuedUserToken) {
+ SetRefreshTokenCookie(c, token.RefreshToken, token.CookieMaxAge)
c.Response().Header().Set("Cache-Control", "no-store")
- return c.JSON(http.StatusOK, Token{Token: t})
+}
+
+// NewUserAuthTokenResponse creates a new user auth token response from a user object.
+func NewUserAuthTokenResponse(u *user.User, c *echo.Context, long bool) error {
+ token, err := IssueUserToken(c.Request().Context(), u, c.Request().UserAgent(), c.RealIP(), long)
+ if err != nil {
+ return err
+ }
+
+ WriteUserAuthCookies(c, token)
+ return c.JSON(http.StatusOK, Token{Token: token.AccessToken})
}
// NewUserJWTAuthtoken generates and signs a new short-lived jwt token for a user.
@@ -392,6 +421,26 @@ func RefreshSession(rawRefreshToken string) (*RefreshResult, error) {
}, nil
}
+// SessionIDFromContext reads the session id (the `sid` claim) off the user JWT
+// in the echo context. It returns "" when there is no user JWT or no sid claim
+// (API tokens and link shares carry no session), which callers treat as a no-op.
+func SessionIDFromContext(c *echo.Context) string {
+ raw := c.Get("user")
+ if raw == nil {
+ return ""
+ }
+ jwtinf, ok := raw.(*jwt.Token)
+ if !ok {
+ return ""
+ }
+ claims, ok := jwtinf.Claims.(jwt.MapClaims)
+ if !ok {
+ return ""
+ }
+ sid, _ := claims["sid"].(string)
+ return sid
+}
+
// GetAuthFromContext retrieves the authenticated web.Auth from a plain
// context.Context, bridging Huma handlers to Vikunja's echo JWT flow. The
// humaecho5 adapter stashes the *echo.Context under EchoContextKey first.
diff --git a/pkg/modules/auth/openid/openid.go b/pkg/modules/auth/openid/openid.go
index b1fa3961a..47ade7dc9 100644
--- a/pkg/modules/auth/openid/openid.go
+++ b/pkg/modules/auth/openid/openid.go
@@ -168,8 +168,12 @@ func enforceTOTPIfRequired(s *xorm.Session, u *user.User, totpPasscode string) e
// @Failure 500 {object} models.Message "Internal error"
// @Router /auth/openid/{provider}/callback [post]
func HandleCallback(c *echo.Context) error {
+ cb := &Callback{}
+ if err := c.Bind(cb); err != nil {
+ return &models.ErrOpenIDBadRequest{Message: "Bad data"}
+ }
- provider, cb, oauthToken, idToken, err := getProviderAndOidcTokens(c)
+ u, err := AuthenticateCallback(c.Request().Context(), cb, c.Param("provider"))
if err != nil {
var detailedErr *models.ErrOpenIDBadRequestWithDetails
if errors.As(err, &detailedErr) {
@@ -181,9 +185,29 @@ func HandleCallback(c *echo.Context) error {
return err
}
- cl, err := getClaims(provider, oauthToken, idToken)
+ // Create token
+ return auth.NewUserAuthTokenResponse(u, c, false)
+}
+
+// AuthenticateCallback resolves an OpenID Connect callback to an authenticated
+// user: it exchanges the auth code, verifies the ID token, creates or updates the
+// matching local user, enforces the account-status and TOTP gates, and syncs the
+// user's external teams. It is the transport-agnostic core shared by the v1 echo
+// handler and the v2 Huma handler; the caller issues the auth token. The
+// ErrOpenIDBadRequestWithDetails error keeps its provider detail so v1 can render
+// its bespoke body and v2 can map it to RFC 9457.
+func AuthenticateCallback(ctx context.Context, cb *Callback, providerKey string) (*user.User, error) {
+ // ctx is threaded through only to dispatch the login event; the OIDC token
+ // exchange, claim verification and user/avatar sync run on their own
+ // background contexts, exactly as the v1 callback always did.
+ provider, oauthToken, idToken, err := exchangeOidcTokens(cb, providerKey) //nolint:contextcheck
if err != nil {
- return err
+ return nil, err
+ }
+
+ cl, err := getClaims(provider, oauthToken, idToken) //nolint:contextcheck
+ if err != nil {
+ return nil, err
}
s := db.NewSession()
@@ -193,20 +217,20 @@ func HandleCallback(c *echo.Context) error {
defer events.CleanupPending(s)
// Check if we have seen this user before
- u, err := getOrCreateUser(s, cl, provider, idToken)
+ u, err := getOrCreateUser(s, cl, provider, idToken) //nolint:contextcheck
if err != nil {
_ = s.Rollback()
log.Errorf("Error creating new user for provider %s: %v", provider.Name, err)
- return err
+ return nil, err
}
if u.Status == user.StatusDisabled {
_ = s.Rollback()
- return &user.ErrAccountDisabled{UserID: u.ID}
+ return nil, &user.ErrAccountDisabled{UserID: u.ID}
}
if u.Status == user.StatusAccountLocked {
_ = s.Rollback()
- return &user.ErrAccountLocked{UserID: u.ID}
+ return nil, &user.ErrAccountLocked{UserID: u.ID}
}
// Must run before team sync so a failed 2FA attempt cannot mutate team
@@ -218,32 +242,31 @@ func HandleCallback(c *echo.Context) error {
log.Errorf("Error committing session after failed OIDC TOTP attempt for user %d: %v", u.ID, commitErr)
} else {
// The user creation above was committed, so its events are real.
- events.DispatchPending(c.Request().Context(), s)
+ events.DispatchPending(ctx, s)
}
if user.IsErrInvalidTOTPPasscode(err) {
user.HandleFailedTOTPAuth(u)
}
- return err
+ return nil, err
}
teamData := getTeamDataFromToken(cl.VikunjaGroups, provider)
err = models.SyncExternalTeamsForUser(s, u, teamData, idToken.Issuer, provider.Name)
if err != nil {
- return err
+ return nil, err
}
err = s.Commit()
if err != nil {
_ = s.Rollback()
log.Errorf("Error creating new team for provider %s: %v", provider.Name, err)
- return err
+ return nil, err
}
- events.DispatchPending(c.Request().Context(), s)
+ events.DispatchPending(ctx, s)
- // Create token
- return auth.NewUserAuthTokenResponse(u, c, false)
+ return u, nil
}
func getTeamDataFromToken(groups []map[string]interface{}, provider *Provider) (teamData []*models.Team) {
@@ -516,21 +539,17 @@ func getClaims(provider *Provider, oauth2Token *oauth2.Token, idToken *oidc.IDTo
return cl, nil
}
-func getProviderAndOidcTokens(c *echo.Context) (*Provider, *Callback, *oauth2.Token, *oidc.IDToken, error) {
-
- cb := &Callback{}
- if err := c.Bind(cb); err != nil {
- return nil, nil, nil, nil, &models.ErrOpenIDBadRequest{Message: "Bad data"}
- }
-
- // Check if the provider exists
- providerKey := c.Param("provider")
+// exchangeOidcTokens resolves the provider, exchanges the callback's auth code,
+// and verifies the returned ID token. It takes an already-bound Callback so it
+// can be shared by the v1 echo handler (which binds from the request) and the v2
+// Huma handler (which binds via its typed body).
+func exchangeOidcTokens(cb *Callback, providerKey string) (*Provider, *oauth2.Token, *oidc.IDToken, error) {
provider, err := GetProvider(providerKey)
if err != nil {
- return nil, cb, nil, nil, err
+ return nil, nil, nil, err
}
if provider == nil {
- return nil, cb, nil, nil, &models.ErrOpenIDBadRequest{Message: "Provider does not exist"}
+ return nil, nil, nil, &models.ErrOpenIDBadRequest{Message: "Provider does not exist"}
}
log.Debugf("Trying to authenticate user using provider: %s", provider.Key)
@@ -546,25 +565,25 @@ func getProviderAndOidcTokens(c *echo.Context) (*Provider, *Callback, *oauth2.To
if err := json.Unmarshal(rerr.Body, &details); err != nil {
log.Errorf("Error unmarshalling token for provider %s: %v", provider.Name, err)
log.Debugf("Raw token value is %s", rerr.Body)
- return nil, cb, nil, nil, err
+ return nil, nil, nil, err
}
log.Errorf("Error retrieving token: %s", err)
log.Debugf("Raw token value is %s", rerr.Body)
- return nil, cb, nil, nil, &models.ErrOpenIDBadRequestWithDetails{
+ return nil, nil, nil, &models.ErrOpenIDBadRequestWithDetails{
Message: "Could not authenticate against third party.",
Details: details,
}
}
- return nil, cb, nil, nil, err
+ return nil, nil, nil, err
}
// Extract the ID Token from OAuth2 token.
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
log.Debugf("Could not get id_token, raw token is %v", oauth2Token)
- return nil, cb, nil, nil, &models.ErrOpenIDBadRequest{Message: "Missing token"}
+ return nil, nil, nil, &models.ErrOpenIDBadRequest{Message: "Missing token"}
}
verifier := provider.openIDProvider.Verifier(&oidc.Config{ClientID: provider.ClientID})
@@ -573,8 +592,8 @@ func getProviderAndOidcTokens(c *echo.Context) (*Provider, *Callback, *oauth2.To
idToken, err := verifier.Verify(context.Background(), rawIDToken)
if err != nil {
log.Errorf("Error verifying token for provider %s: %v", provider.Name, err)
- return nil, cb, nil, nil, err
+ return nil, nil, nil, err
}
- return provider, cb, oauth2Token, idToken, nil
+ return provider, oauth2Token, idToken, nil
}
diff --git a/pkg/modules/background/handler/background.go b/pkg/modules/background/handler/background.go
index afe7901e5..da9d0e522 100644
--- a/pkg/modules/background/handler/background.go
+++ b/pkg/modules/background/handler/background.go
@@ -31,6 +31,7 @@ import (
"image"
"io"
"net/http"
+ "os"
"strconv"
"strings"
@@ -43,6 +44,7 @@ import (
"code.vikunja.io/api/pkg/modules/background/unsplash"
"code.vikunja.io/api/pkg/modules/background/upload"
"code.vikunja.io/api/pkg/web"
+ webfiles "code.vikunja.io/api/pkg/web/files"
"github.com/bbrks/go-blurhash"
"github.com/gabriel-vasile/mimetype"
@@ -385,54 +387,47 @@ func GetProjectBackground(c *echo.Context) error {
return err
}
- if project.BackgroundFileID == 0 {
- _ = s.Rollback()
- return echo.NewHTTPError(http.StatusNotFound, "Project background not found")
- }
-
- // Get the file
- bgFile := &files.File{
- ID: project.BackgroundFileID,
- }
- if err := bgFile.LoadFileByID(); err != nil {
- _ = s.Rollback()
- return err
- }
- stat, err := files.FileStat(bgFile)
+ bgFile, stat, err := LoadProjectBackgroundForDownload(s, project)
if err != nil {
_ = s.Rollback()
+ if models.IsErrProjectHasNoBackground(err) {
+ return echo.NewHTTPError(http.StatusNotFound, "Project background not found")
+ }
return err
}
- // Unsplash requires pingbacks as per their api usage guidelines.
- // To do this in a privacy-preserving manner, we do the ping from inside of Vikunja to not expose any user details.
- // FIXME: This should use an event once we have events
- unsplash.Pingback(s, bgFile)
-
if err := s.Commit(); err != nil {
_ = s.Rollback()
return err
}
- // Override the global no-store directive so browsers can cache background images.
- // no-cache allows caching but requires revalidation via If-Modified-Since.
- c.Response().Header().Set("Cache-Control", "no-cache")
+ webfiles.WriteProjectBackground(c.Response(), c.Request(), bgFile, stat)
+ return nil
+}
- // Set Last-Modified header if we have the file stat, so clients can decide whether to use cached files
- if stat != nil {
- modTime := stat.ModTime().UTC()
- c.Response().Header().Set(echo.HeaderLastModified, modTime.Format(http.TimeFormat))
-
- // Check If-Modified-Since and return 304 if the file hasn't changed
- if ifModSince := c.Request().Header.Get("If-Modified-Since"); ifModSince != "" {
- if t, err := http.ParseTime(ifModSince); err == nil && !modTime.After(t) {
- return c.NoContent(http.StatusNotModified)
- }
- }
+// LoadProjectBackgroundForDownload opens the project's background file (bytes ready to
+// read) and stats it for the modtime the download uses for caching. It also fires the
+// Unsplash pingback side effect, required by Unsplash's API guidelines and done
+// server-side so no user details are exposed. Returns ErrProjectHasNoBackground when the
+// project has none; the caller owns committing the session and closing bgFile.File.
+func LoadProjectBackgroundForDownload(s *xorm.Session, project *models.Project) (bgFile *files.File, stat os.FileInfo, err error) {
+ if project.BackgroundFileID == 0 {
+ return nil, nil, &models.ErrProjectHasNoBackground{ProjectID: project.ID}
}
- // Serve the file
- return c.Stream(http.StatusOK, "image/jpg", bgFile.File)
+ bgFile = &files.File{ID: project.BackgroundFileID}
+ if err := bgFile.LoadFileByID(); err != nil {
+ return nil, nil, err
+ }
+ stat, err = files.FileStat(bgFile)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // FIXME: This should use an event once we have events
+ unsplash.Pingback(s, bgFile)
+
+ return bgFile, stat, nil
}
// RemoveProjectBackground removes a project background, no matter the background provider
diff --git a/pkg/modules/background/unsplash/proxy.go b/pkg/modules/background/unsplash/proxy.go
index 69fb23716..1d0b11607 100644
--- a/pkg/modules/background/unsplash/proxy.go
+++ b/pkg/modules/background/unsplash/proxy.go
@@ -18,32 +18,95 @@ package unsplash
import (
"context"
+ "errors"
+ "io"
"net/http"
"strings"
"code.vikunja.io/api/pkg/utils"
+ "code.vikunja.io/api/pkg/web"
"github.com/labstack/echo/v5"
)
-func unsplashImage(url string, c *echo.Context) error {
+// ErrUnsplashImageDoesNotExist is returned when Unsplash answers an image proxy fetch
+// with a non-success status, mirroring v1's echo.ErrNotFound. It satisfies
+// web.HTTPErrorProcessor so the v2 error bridge maps it to a 404.
+type ErrUnsplashImageDoesNotExist struct{}
+
+// IsErrUnsplashImageDoesNotExist checks if an error is ErrUnsplashImageDoesNotExist.
+func IsErrUnsplashImageDoesNotExist(err error) bool {
+ var target *ErrUnsplashImageDoesNotExist
+ return errors.As(err, &target)
+}
+
+func (err *ErrUnsplashImageDoesNotExist) Error() string {
+ return "Unsplash image does not exist"
+}
+
+// HTTPError holds the http error description.
+func (err *ErrUnsplashImageDoesNotExist) HTTPError() web.HTTPError {
+ return web.HTTPError{HTTPCode: http.StatusNotFound, Message: "Not Found"}
+}
+
+// fetchUnsplashImage fetches an image from Unsplash through the SSRF-safe client and
+// returns its still-open response body for the caller to stream and close. The url is
+// rebased onto the hardcoded images.unsplash.com host (stripping any client-supplied
+// host) so the proxy can only ever reach Unsplash. It returns
+// ErrUnsplashImageDoesNotExist on a non-success upstream status.
+func fetchUnsplashImage(url string) (io.ReadCloser, error) {
// Replacing and appending the url for security reasons
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "https://images.unsplash.com/"+strings.Replace(url, "https://images.unsplash.com/", "", 1), nil)
if err != nil {
- return err
+ return nil, err
}
resp, err := utils.NewSSRFSafeHTTPClient().Do(req) //nolint:gosec // SSRF protection is handled by the SSRF-safe client
if err != nil {
- return err
+ return nil, err
}
- defer resp.Body.Close()
if resp.StatusCode > 399 {
- return echo.ErrNotFound
+ _ = resp.Body.Close()
+ return nil, &ErrUnsplashImageDoesNotExist{}
}
- return c.Stream(http.StatusOK, "image/jpg", resp.Body)
+ return resp.Body, nil
}
-// ProxyUnsplashImage proxies a thumbnail from unsplash for privacy reasons.
+// FetchUnsplashImageByID resolves an Unsplash image by id, fires the required pingback,
+// and returns the full-resolution image body for the caller to stream and close.
+func FetchUnsplashImageByID(imageID string) (io.ReadCloser, error) {
+ photo, err := getUnsplashPhotoInfoByID(imageID)
+ if err != nil {
+ return nil, err
+ }
+ pingbackByPhotoID(photo.ID)
+ return fetchUnsplashImage(photo.Urls.Raw)
+}
+
+// FetchUnsplashThumbByID resolves an Unsplash image by id, fires the required pingback,
+// and returns a thumbnail (max width 200px) body for the caller to stream and close.
+func FetchUnsplashThumbByID(imageID string) (io.ReadCloser, error) {
+ photo, err := getUnsplashPhotoInfoByID(imageID)
+ if err != nil {
+ return nil, err
+ }
+ pingbackByPhotoID(photo.ID)
+ return fetchUnsplashImage("https://images.unsplash.com/" + getImageID(photo.Urls.Raw) + "?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjcyODAwfQ")
+}
+
+// streamUnsplashImage streams a fetched image body to the v1 echo response, mapping the
+// not-found sentinel back to echo.ErrNotFound so v1's wire response is unchanged.
+func streamUnsplashImage(body io.ReadCloser, err error, c *echo.Context) error {
+ if err != nil {
+ if IsErrUnsplashImageDoesNotExist(err) {
+ return echo.ErrNotFound
+ }
+ return err
+ }
+ defer body.Close()
+ return c.Stream(http.StatusOK, "image/jpg", body)
+}
+
+// ProxyUnsplashImage proxies an image from unsplash for privacy reasons.
// @Summary Get an unsplash image
// @Description Get an unsplash image. **Returns json on error.**
// @tags project
@@ -55,12 +118,8 @@ func unsplashImage(url string, c *echo.Context) error {
// @Failure 500 {object} models.Message "Internal error"
// @Router /backgrounds/unsplash/image/{image} [get]
func ProxyUnsplashImage(c *echo.Context) error {
- photo, err := getUnsplashPhotoInfoByID(c.Param("image"))
- if err != nil {
- return err
- }
- pingbackByPhotoID(photo.ID)
- return unsplashImage(photo.Urls.Raw, c)
+ body, err := FetchUnsplashImageByID(c.Param("image"))
+ return streamUnsplashImage(body, err, c)
}
// ProxyUnsplashThumb proxies a thumbnail from unsplash for privacy reasons.
@@ -75,10 +134,6 @@ func ProxyUnsplashImage(c *echo.Context) error {
// @Failure 500 {object} models.Message "Internal error"
// @Router /backgrounds/unsplash/image/{image}/thumb [get]
func ProxyUnsplashThumb(c *echo.Context) error {
- photo, err := getUnsplashPhotoInfoByID(c.Param("image"))
- if err != nil {
- return err
- }
- pingbackByPhotoID(photo.ID)
- return unsplashImage("https://images.unsplash.com/"+getImageID(photo.Urls.Raw)+"?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjcyODAwfQ", c)
+ body, err := FetchUnsplashThumbByID(c.Param("image"))
+ return streamUnsplashImage(body, err, c)
}
diff --git a/pkg/routes/api/shared/auth.go b/pkg/routes/api/shared/auth.go
index 925a533d8..153d851e8 100644
--- a/pkg/routes/api/shared/auth.go
+++ b/pkg/routes/api/shared/auth.go
@@ -26,7 +26,11 @@ import (
"code.vikunja.io/api/pkg/metrics"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/auth"
+ "code.vikunja.io/api/pkg/modules/auth/ldap"
+ "code.vikunja.io/api/pkg/modules/keyvalue"
"code.vikunja.io/api/pkg/user"
+
+ "xorm.io/xorm"
)
// UserRegister carries the fields accepted by the public registration endpoint:
@@ -78,6 +82,136 @@ func RegisterUser(ctx context.Context, in *UserRegister) (*user.User, error) {
return newUser, nil
}
+// AuthenticateUserCredentials verifies a login against local (and, if configured,
+// LDAP) credentials and enforces the account-status and TOTP gates, returning the
+// authenticated user on success. It is the transport-agnostic core of the login
+// flow shared by v1 and v2; the caller issues the token and sets the cookie. The
+// returned errors carry their own HTTP semantics (wrong credentials, disabled
+// account, missing/invalid TOTP) so both APIs surface them identically.
+func AuthenticateUserCredentials(ctx context.Context, login *user.Login) (*user.User, error) {
+ s := db.NewSession()
+ defer s.Close()
+ // Discards events queued during a rolled-back transaction (e.g. LDAP user
+ // creation); a no-op once DispatchPending has run.
+ defer events.CleanupPending(s)
+
+ u, err := resolveLoginUser(ctx, s, login)
+ if err != nil {
+ _ = s.Rollback()
+ return nil, err
+ }
+
+ if u.Status == user.StatusDisabled {
+ _ = s.Rollback()
+ return nil, &user.ErrAccountDisabled{UserID: u.ID}
+ }
+ if u.Status == user.StatusAccountLocked {
+ _ = s.Rollback()
+ return nil, &user.ErrAccountLocked{UserID: u.ID}
+ }
+
+ if err := enforceLoginTOTP(s, u, login.TOTPPasscode); err != nil {
+ return nil, err
+ }
+
+ if err := keyvalue.Del(u.GetFailedTOTPAttemptsKey()); err != nil {
+ return nil, err
+ }
+ if err := keyvalue.Del(u.GetFailedPasswordAttemptsKey()); err != nil {
+ return nil, err
+ }
+
+ if err := s.Commit(); err != nil {
+ _ = s.Rollback()
+ return nil, err
+ }
+
+ events.DispatchPending(ctx, s)
+
+ return u, nil
+}
+
+// resolveLoginUser authenticates the credentials against LDAP (when enabled) and
+// then against local accounts, mirroring v1's order so local users keep working
+// alongside LDAP. Bots are rejected before bcrypt runs because they have no
+// password hash.
+func resolveLoginUser(ctx context.Context, s *xorm.Session, login *user.Login) (*user.User, error) {
+ if config.AuthLdapEnabled.GetBool() {
+ u, err := ldap.AuthenticateUserInLDAP(s, login.Username, login.Password, config.AuthLdapGroupSyncEnabled.GetBool(), config.AuthLdapAvatarSyncAttribute.GetString())
+ if err != nil && !user.IsErrWrongUsernameOrPassword(err) {
+ return nil, err
+ }
+ if u != nil {
+ return u, nil
+ }
+ }
+
+ existingUser, lookupErr := user.GetUserByUsername(s, login.Username)
+ if lookupErr == nil && existingUser.IsBot() {
+ return nil, &user.ErrAccountIsBot{UserID: existingUser.ID}
+ }
+
+ return user.CheckUserCredentials(ctx, s, login)
+}
+
+// enforceLoginTOTP runs the TOTP gate for users who have it enabled, mirroring
+// v1: a missing passcode is rejected, and a wrong one trips the failed-attempt
+// lockout via HandleFailedTOTPAuth. The session is rolled back before
+// HandleFailedTOTPAuth so its dedicated session can acquire a write lock on
+// SQLite shared-cache (the lockout write is decoupled from this transaction —
+// see GHSA-fgfv-pv97-6cmj).
+func enforceLoginTOTP(s *xorm.Session, u *user.User, passcode string) error {
+ totpEnabled, err := user.TOTPEnabledForUser(s, u)
+ if err != nil {
+ _ = s.Rollback()
+ return err
+ }
+ if !totpEnabled {
+ return nil
+ }
+
+ if passcode == "" {
+ _ = s.Rollback()
+ return user.ErrInvalidTOTPPasscode{}
+ }
+
+ _, err = user.ValidateTOTPPasscode(s, &user.TOTPPasscode{User: u, Passcode: passcode})
+ if err != nil {
+ _ = s.Rollback()
+ if user.IsErrInvalidTOTPPasscode(err) {
+ user.HandleFailedTOTPAuth(u)
+ }
+ return err
+ }
+
+ return nil
+}
+
+// DeleteSession removes the session with the given id, logging the user out
+// server-side. An empty sid is a no-op (the token carried no session, e.g. an
+// API token or a link share), matching v1. Shared by v1 and v2; the caller is
+// responsible for clearing the refresh cookie.
+func DeleteSession(sid string) error {
+ if sid == "" {
+ return nil
+ }
+
+ s := db.NewSession()
+ defer s.Close()
+
+ if _, err := s.Where("id = ?", sid).Delete(&models.Session{}); err != nil {
+ _ = s.Rollback()
+ return err
+ }
+
+ if err := s.Commit(); err != nil {
+ _ = s.Rollback()
+ return err
+ }
+
+ return nil
+}
+
// ResetPassword resets a user's password from a previously issued reset token
// and invalidates all of that user's sessions, so a leaked password cannot be
// used after a reset. Shared by v1 and v2.
diff --git a/pkg/routes/api/shared/testing.go b/pkg/routes/api/shared/testing.go
new file mode 100644
index 000000000..ba9118e5a
--- /dev/null
+++ b/pkg/routes/api/shared/testing.go
@@ -0,0 +1,92 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package shared
+
+import (
+ "code.vikunja.io/api/pkg/db"
+ "code.vikunja.io/api/pkg/events"
+ "code.vikunja.io/api/pkg/license"
+ "code.vikunja.io/api/pkg/log"
+)
+
+// dependentTestingTables lists tables that reference a reset table by ID and
+// must be truncated alongside it. Without foreign key cascades, stale rows
+// would persist and pollute subsequent tests that reuse the same
+// auto-increment IDs.
+var dependentTestingTables = map[string][]string{
+ "users": {"notifications"},
+}
+
+// ReplaceTableContents resets a single table to the provided rows for the e2e
+// testing endpoint and returns the table's resulting contents. When truncate is
+// true the table (and any dependent tables) is emptied first; otherwise the rows
+// are restored on top of existing data. Callers must already have verified the
+// testing token.
+func ReplaceTableContents(table string, content []map[string]interface{}, truncate bool) ([]map[string]interface{}, error) {
+ // Wait for all async event handlers from the previous test to complete
+ // before modifying the database. Without this, handlers hold SQLite
+ // connections and starve this request's truncate/insert operations.
+ events.WaitForPendingHandlers()
+
+ var err error
+ if truncate {
+ for _, dep := range dependentTestingTables[table] {
+ if err = db.RestoreAndTruncate(dep, nil); err != nil {
+ return nil, err
+ }
+ }
+ err = db.RestoreAndTruncate(table, content)
+ } else {
+ err = db.Restore(table, content)
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ // License state is cached at startup; re-apply so tests take effect without a restart.
+ if table == "license_status" {
+ if err := license.ReloadFromCache(); err != nil {
+ return nil, err
+ }
+ }
+
+ s := db.NewSession()
+ defer s.Close()
+ data := []map[string]interface{}{}
+ if err := s.Table(table).Find(&data); err != nil {
+ return nil, err
+ }
+ return data, nil
+}
+
+// TruncateAllTestingTables empties every Vikunja table for the e2e testing
+// endpoint. Callers must already have verified the testing token.
+func TruncateAllTestingTables() error {
+ events.WaitForPendingHandlers()
+
+ if err := db.TruncateAllTables(); err != nil {
+ return err
+ }
+
+ // Reload after truncate; otherwise features enabled by a prior test outlive
+ // the now-empty license_status table. A reload failure here is non-fatal —
+ // the truncate already succeeded — so it is logged and swallowed.
+ if err := license.ReloadFromCache(); err != nil {
+ log.Errorf("Error reloading license after truncate: %v", err)
+ }
+ return nil
+}
diff --git a/pkg/routes/api/v1/login.go b/pkg/routes/api/v1/login.go
index ae92e1d72..2d740ffdf 100644
--- a/pkg/routes/api/v1/login.go
+++ b/pkg/routes/api/v1/login.go
@@ -25,8 +25,7 @@ import (
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/auth"
- "code.vikunja.io/api/pkg/modules/auth/ldap"
- "code.vikunja.io/api/pkg/modules/keyvalue"
+ "code.vikunja.io/api/pkg/routes/api/shared"
user2 "code.vikunja.io/api/pkg/user"
"github.com/golang-jwt/jwt/v5"
@@ -51,87 +50,11 @@ func Login(c *echo.Context) (err error) {
return c.JSON(http.StatusBadRequest, models.Message{Message: "Please provide a username and password."})
}
- s := db.NewSession()
- defer s.Close()
- // Discards events queued during a rolled-back transaction (e.g. LDAP user
- // creation); a no-op once DispatchPending has run.
- defer events.CleanupPending(s)
-
- var user *user2.User
- if config.AuthLdapEnabled.GetBool() {
- user, err = ldap.AuthenticateUserInLDAP(s, u.Username, u.Password, config.AuthLdapGroupSyncEnabled.GetBool(), config.AuthLdapAvatarSyncAttribute.GetString())
- if err != nil && !user2.IsErrWrongUsernameOrPassword(err) {
- _ = s.Rollback()
- return err
- }
- }
-
- if user == nil {
- // Check if the user is a bot before attempting password verification,
- // because bots have no password hash and bcrypt would fail with a
- // misleading error.
- existingUser, lookupErr := user2.GetUserByUsername(s, u.Username)
- if lookupErr == nil && existingUser.IsBot() {
- _ = s.Rollback()
- return &user2.ErrAccountIsBot{UserID: existingUser.ID}
- }
-
- // This allows us to still have local users while ldap is enabled
- user, err = user2.CheckUserCredentials(c.Request().Context(), s, &u)
- if err != nil {
- _ = s.Rollback()
- return err
- }
- }
-
- if user.Status == user2.StatusDisabled || user.Status == user2.StatusAccountLocked {
- _ = s.Rollback()
- return &user2.ErrAccountDisabled{UserID: user.ID}
- }
-
- totpEnabled, err := user2.TOTPEnabledForUser(s, user)
+ user, err := shared.AuthenticateUserCredentials(c.Request().Context(), &u)
if err != nil {
- _ = s.Rollback()
return err
}
- if totpEnabled {
- if u.TOTPPasscode == "" {
- _ = s.Rollback()
- return user2.ErrInvalidTOTPPasscode{}
- }
-
- _, err = user2.ValidateTOTPPasscode(s, &user2.TOTPPasscode{
- User: user,
- Passcode: u.TOTPPasscode,
- })
- if err != nil {
- // Rollback before HandleFailedTOTPAuth so its dedicated session
- // can acquire a write lock on SQLite shared-cache. The lockout
- // write is decoupled from this handler's transaction — see
- // GHSA-fgfv-pv97-6cmj.
- _ = s.Rollback()
- if user2.IsErrInvalidTOTPPasscode(err) {
- user2.HandleFailedTOTPAuth(user)
- }
- return err
- }
- }
-
- if err := keyvalue.Del(user.GetFailedTOTPAttemptsKey()); err != nil {
- return err
- }
- if err := keyvalue.Del(user.GetFailedPasswordAttemptsKey()); err != nil {
- return err
- }
-
- if err := s.Commit(); err != nil {
- _ = s.Rollback()
- return err
- }
-
- events.DispatchPending(c.Request().Context(), s)
-
// Create token
return auth.NewUserAuthTokenResponse(user, c, u.LongToken)
}
@@ -254,21 +177,7 @@ func Logout(c *echo.Context) (err error) {
}
}
- if sid == "" {
- return c.JSON(http.StatusOK, models.Message{Message: "Successfully logged out."})
- }
-
- s := db.NewSession()
- defer s.Close()
-
- _, err = s.Where("id = ?", sid).Delete(&models.Session{})
- if err != nil {
- _ = s.Rollback()
- return err
- }
-
- if err := s.Commit(); err != nil {
- _ = s.Rollback()
+ if err := shared.DeleteSession(sid); err != nil {
return err
}
diff --git a/pkg/routes/api/v1/testing.go b/pkg/routes/api/v1/testing.go
index 98f5aeca1..62d5f5206 100644
--- a/pkg/routes/api/v1/testing.go
+++ b/pkg/routes/api/v1/testing.go
@@ -22,10 +22,8 @@ import (
"net/http"
"code.vikunja.io/api/pkg/config"
- "code.vikunja.io/api/pkg/db"
- "code.vikunja.io/api/pkg/events"
- "code.vikunja.io/api/pkg/license"
"code.vikunja.io/api/pkg/log"
+ "code.vikunja.io/api/pkg/routes/api/shared"
"github.com/labstack/echo/v5"
)
@@ -63,36 +61,8 @@ func HandleTesting(c *echo.Context) error {
})
}
- // Wait for all async event handlers from the previous test to complete
- // before modifying the database. Without this, handlers hold SQLite
- // connections and starve this request's truncate/insert operations.
- events.WaitForPendingHandlers()
-
truncate := c.QueryParam("truncate")
- if truncate == "true" || truncate == "" {
- // When truncating certain tables, also truncate dependent tables
- // whose rows reference the truncated table by user/entity ID.
- // Without foreign key cascades, stale rows would persist and
- // pollute subsequent tests that reuse the same auto-increment IDs.
- dependentTables := map[string][]string{
- "users": {"notifications"},
- }
- if deps, ok := dependentTables[table]; ok {
- for _, dep := range deps {
- if err = db.RestoreAndTruncate(dep, nil); err != nil {
- log.Errorf("Error truncating dependent table %s: %v", dep, err)
- return c.JSON(http.StatusInternalServerError, map[string]interface{}{
- "error": true,
- "message": err.Error(),
- })
- }
- }
- }
- err = db.RestoreAndTruncate(table, content)
- } else {
- err = db.Restore(table, content)
- }
-
+ data, err := shared.ReplaceTableContents(table, content, truncate == "true" || truncate == "")
if err != nil {
log.Errorf("Error replacing table data: %v", err)
return c.JSON(http.StatusInternalServerError, map[string]interface{}{
@@ -101,29 +71,6 @@ func HandleTesting(c *echo.Context) error {
})
}
- // License state is cached at startup; re-apply so tests take effect without a restart.
- if table == "license_status" {
- if err := license.ReloadFromCache(); err != nil {
- log.Errorf("Error reloading license from seeded cache: %v", err)
- return c.JSON(http.StatusInternalServerError, map[string]interface{}{
- "error": true,
- "message": err.Error(),
- })
- }
- }
-
- s := db.NewSession()
- defer s.Close()
- data := []map[string]interface{}{}
- err = s.Table(table).Find(&data)
- if err != nil {
- log.Errorf("Error fetching table data: %v", err)
- return c.JSON(http.StatusInternalServerError, map[string]interface{}{
- "error": true,
- "message": err.Error(),
- })
- }
-
return c.JSON(http.StatusCreated, data)
}
@@ -142,9 +89,7 @@ func HandleTestingTruncateAll(c *echo.Context) error {
return echo.ErrForbidden
}
- events.WaitForPendingHandlers()
-
- if err := db.TruncateAllTables(); err != nil {
+ if err := shared.TruncateAllTestingTables(); err != nil {
log.Errorf("Error truncating all tables: %v", err)
return c.JSON(http.StatusInternalServerError, map[string]interface{}{
"error": true,
@@ -152,11 +97,6 @@ func HandleTestingTruncateAll(c *echo.Context) error {
})
}
- // Reload after truncate; otherwise features enabled by a prior test outlive the now-empty license_status table.
- if err := license.ReloadFromCache(); err != nil {
- log.Errorf("Error reloading license after truncate: %v", err)
- }
-
return c.JSON(http.StatusOK, map[string]string{
"message": "ok",
})
diff --git a/pkg/routes/api/v1/user_export.go b/pkg/routes/api/v1/user_export.go
index 6efc311c0..b01b1fdf3 100644
--- a/pkg/routes/api/v1/user_export.go
+++ b/pkg/routes/api/v1/user_export.go
@@ -19,14 +19,11 @@ package v1
import (
"io"
"net/http"
- "os"
"strconv"
- "time"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/events"
- "code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
"github.com/labstack/echo/v5"
@@ -127,28 +124,18 @@ func DownloadUserDataExport(c *echo.Context) error {
return err
}
- // Check if user has an export file
- exportNotFoundError := echo.NewHTTPError(http.StatusNotFound, "No user data export found.")
- if u.ExportFileID == 0 {
- return exportNotFoundError
+ exportFile, err := models.GetUserDataExportFile(u)
+ if err != nil {
+ if models.IsErrUserDataExportDoesNotExist(err) {
+ return echo.NewHTTPError(http.StatusNotFound, "No user data export found.")
+ }
+ return err
}
+ defer func() { _ = exportFile.File.Close() }()
- // Download
- exportFile := &files.File{ID: u.ExportFileID}
- err = exportFile.LoadFileMetaByID()
- if err != nil {
- if files.IsErrFileDoesNotExist(err) {
- return exportNotFoundError
- }
- return err
- }
- err = exportFile.LoadFileByID()
- if err != nil {
- if os.IsNotExist(err) {
- return exportNotFoundError
- }
- return err
- }
+ // Downloads must never be cached; no-cache overrides the global no-store
+ // directive while still allowing revalidation.
+ c.Response().Header().Set("Cache-Control", "no-cache")
if config.FilesType.GetString() == "s3" {
c.Response().Header().Set("Content-Disposition", "attachment; filename=\""+exportFile.Name+"\"")
@@ -163,19 +150,12 @@ func DownloadUserDataExport(c *echo.Context) error {
return nil
}
-type UserExportStatus struct {
- ID int64 `json:"id"`
- Size uint64 `json:"size"`
- Created time.Time `json:"created"`
- Expires time.Time `json:"expires"`
-}
-
// GetUserExportStatus returns metadata about the current user export if it exists
// @Summary Get current user data export
// @tags user
// @Produce json
// @Security JWTKeyAuth
-// @Success 200 {object} v1.UserExportStatus
+// @Success 200 {object} models.UserExportStatus
// @Router /user/export [get]
func GetUserExportStatus(c *echo.Context) error {
s := db.NewSession()
@@ -186,20 +166,12 @@ func GetUserExportStatus(c *echo.Context) error {
return err
}
- if u.ExportFileID == 0 {
- return c.JSON(http.StatusOK, struct{}{})
- }
-
- exportFile := &files.File{ID: u.ExportFileID}
- if err := exportFile.LoadFileMetaByID(); err != nil {
+ status, err := models.GetUserDataExportStatus(u)
+ if err != nil {
return err
}
-
- status := UserExportStatus{
- ID: exportFile.ID,
- Size: exportFile.Size,
- Created: exportFile.Created,
- Expires: exportFile.Created.Add(7 * 24 * time.Hour),
+ if status == nil {
+ return c.JSON(http.StatusOK, struct{}{})
}
return c.JSON(http.StatusOK, status)
diff --git a/pkg/routes/api/v1/user_totp.go b/pkg/routes/api/v1/user_totp.go
index e3c0ae076..a3c9fc8c4 100644
--- a/pkg/routes/api/v1/user_totp.go
+++ b/pkg/routes/api/v1/user_totp.go
@@ -17,10 +17,8 @@
package v1
import (
- "bytes"
"errors"
"fmt"
- "image/jpeg"
"net/http"
"code.vikunja.io/api/pkg/db"
@@ -202,14 +200,7 @@ func UserTOTPQrCode(c *echo.Context) error {
}
defer s.Close()
- qrcode, err := user.GetTOTPQrCodeForUser(s, u)
- if err != nil {
- _ = s.Rollback()
- return err
- }
-
- buff := &bytes.Buffer{}
- err = jpeg.Encode(buff, qrcode, nil)
+ qrcode, err := user.GetTOTPQrCodeAsJpegForUser(s, u)
if err != nil {
_ = s.Rollback()
return err
@@ -220,7 +211,7 @@ func UserTOTPQrCode(c *echo.Context) error {
return err
}
- return c.Blob(http.StatusOK, "image/jpeg", buff.Bytes())
+ return c.Blob(http.StatusOK, "image/jpeg", qrcode)
}
// UserTOTP returns the current totp implementation if any is enabled.
diff --git a/pkg/routes/api/v2/auth_login.go b/pkg/routes/api/v2/auth_login.go
new file mode 100644
index 000000000..d6ff0ff19
--- /dev/null
+++ b/pkg/routes/api/v2/auth_login.go
@@ -0,0 +1,129 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package apiv2
+
+import (
+ "context"
+ "net/http"
+
+ "code.vikunja.io/api/pkg/modules/auth"
+ "code.vikunja.io/api/pkg/modules/humaecho5"
+ "code.vikunja.io/api/pkg/routes/api/shared"
+ "code.vikunja.io/api/pkg/user"
+
+ "github.com/danielgtaylor/huma/v2"
+ "github.com/labstack/echo/v5"
+)
+
+// authTokenBody wraps the issued user JWT. The token is inlined rather than
+// embedding auth.Token because Huma derives schema names from the bare Go type
+// name and a top-level auth.Token body would collide with user.Token (the
+// caldav-token schema, also named "Token"). The refresh token is delivered out
+// of band as an HttpOnly cookie, so it is intentionally absent from the schema.
+type authTokenBody struct {
+ // Cache-Control: no-store keeps the access token out of any shared cache.
+ CacheControl string `header:"Cache-Control"`
+ Body struct {
+ Token string `json:"token" readOnly:"true" doc:"The short-lived JWT auth token. Send it as a bearer token on subsequent requests."`
+ }
+}
+
+// logoutBody confirms a successful logout.
+type logoutBody struct {
+ Body struct {
+ Message string `json:"message" readOnly:"true" doc:"A human-readable confirmation message."`
+ }
+}
+
+func init() { AddRouteRegistrar(RegisterLoginRoutes) }
+
+// RegisterLoginRoutes wires the local/LDAP login and logout endpoints. Login is
+// always registered (LDAP-only deployments still log in here); logout inherits
+// the global JWT auth.
+func RegisterLoginRoutes(api huma.API) {
+ tags := []string{"auth"}
+
+ Register(api, huma.Operation{
+ OperationID: "auth-login",
+ Summary: "Login",
+ Description: "Logs a user in with username and password (and a TOTP passcode when 2FA is enabled), returning a short-lived JWT. A long-lived refresh token is set as an HttpOnly cookie scoped to the refresh endpoint.",
+ Method: http.MethodPost,
+ Path: "/login",
+ DefaultStatus: http.StatusOK,
+ Tags: tags,
+ Security: publicSecurity,
+ }, authLogin)
+
+ Register(api, huma.Operation{
+ OperationID: "auth-logout",
+ Summary: "Logout",
+ Description: "Destroys the current session server-side and clears the refresh-token cookie. A no-op for API tokens and link shares, which carry no session.",
+ Method: http.MethodPost,
+ Path: "/logout",
+ DefaultStatus: http.StatusOK,
+ Tags: tags,
+ }, authLogout)
+}
+
+func authLogin(ctx context.Context, in *struct{ Body user.Login }) (*authTokenBody, error) {
+ u, err := shared.AuthenticateUserCredentials(ctx, &in.Body)
+ if err != nil {
+ return nil, translateDomainError(err)
+ }
+
+ deviceInfo, ipAddress := requestClientInfo(ctx)
+ token, err := auth.IssueUserToken(ctx, u, deviceInfo, ipAddress, in.Body.LongToken)
+ if err != nil {
+ return nil, translateDomainError(err)
+ }
+
+ if ec := echoContextFromCtx(ctx); ec != nil {
+ auth.WriteUserAuthCookies(ec, token)
+ }
+
+ out := &authTokenBody{CacheControl: "no-store"}
+ out.Body.Token = token.AccessToken
+ return out, nil
+}
+
+func authLogout(ctx context.Context, _ *struct{}) (*logoutBody, error) {
+ var sid string
+ if ec := echoContextFromCtx(ctx); ec != nil {
+ auth.ClearRefreshTokenCookie(ec)
+ sid = auth.SessionIDFromContext(ec)
+ }
+
+ if err := shared.DeleteSession(sid); err != nil {
+ return nil, translateDomainError(err)
+ }
+
+ out := &logoutBody{}
+ out.Body.Message = "Successfully logged out."
+ return out, nil
+}
+
+// echoContextFromCtx pulls the underlying *echo.Context off a Huma request
+// context so a handler can set cookies and headers the OpenAPI schema does not
+// model (the refresh-token cookie). Returns nil when the context carries no echo
+// context (it always does under the humaecho5 adapter).
+func echoContextFromCtx(ctx context.Context) *echo.Context {
+ ec, ok := ctx.Value(humaecho5.EchoContextKey).(*echo.Context)
+ if !ok || ec == nil {
+ return nil
+ }
+ return ec
+}
diff --git a/pkg/routes/api/v2/auth_openid.go b/pkg/routes/api/v2/auth_openid.go
new file mode 100644
index 000000000..b52d7dca1
--- /dev/null
+++ b/pkg/routes/api/v2/auth_openid.go
@@ -0,0 +1,93 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package apiv2
+
+import (
+ "context"
+ "errors"
+ "net/http"
+
+ "code.vikunja.io/api/pkg/config"
+ "code.vikunja.io/api/pkg/models"
+ "code.vikunja.io/api/pkg/modules/auth"
+ "code.vikunja.io/api/pkg/modules/auth/openid"
+
+ "github.com/danielgtaylor/huma/v2"
+)
+
+func init() { AddRouteRegistrar(RegisterOpenIDRoutes) }
+
+// RegisterOpenIDRoutes wires the OpenID Connect callback endpoint. It is only
+// registered when OpenID is enabled; individual providers are still resolved per
+// request, so an unknown provider key 404s even when others are configured.
+func RegisterOpenIDRoutes(api huma.API) {
+ if !config.AuthOpenIDEnabled.GetBool() {
+ return
+ }
+
+ Register(api, huma.Operation{
+ OperationID: "auth-openid-callback",
+ Summary: "Authenticate with OpenID Connect",
+ Description: "Exchanges the authorization code returned by an OpenID Connect provider for a Vikunja JWT, creating or updating the matching user. A long-lived refresh token is set as an HttpOnly cookie. When the resolved user has 2FA enabled, the call returns 412 and must be retried with totp_passcode set.",
+ Method: http.MethodPost,
+ Path: "/auth/openid/{provider}/callback",
+ DefaultStatus: http.StatusOK,
+ Tags: []string{"auth"},
+ Security: publicSecurity,
+ }, authOpenIDCallback)
+}
+
+func authOpenIDCallback(ctx context.Context, in *struct {
+ Provider string `path:"provider" doc:"The OpenID Connect provider key as returned by the /info endpoint."`
+ Body openid.Callback `doc:"The provider callback, carrying the authorization code."`
+}) (*authTokenBody, error) {
+ u, err := openid.AuthenticateCallback(ctx, &in.Body, in.Provider) //nolint:contextcheck // resolves providers from a cached, context-less map and runs OIDC discovery on its own background context, like the v1 callback.
+ if err != nil {
+ return nil, translateOpenIDError(err)
+ }
+
+ deviceInfo, ipAddress := requestClientInfo(ctx)
+ // OIDC logins are not "remember me" sessions; v1 always issues a short one.
+ token, err := auth.IssueUserToken(ctx, u, deviceInfo, ipAddress, false)
+ if err != nil {
+ return nil, translateDomainError(err)
+ }
+
+ if ec := echoContextFromCtx(ctx); ec != nil {
+ auth.WriteUserAuthCookies(ec, token)
+ }
+
+ out := &authTokenBody{CacheControl: "no-store"}
+ out.Body.Token = token.AccessToken
+ return out, nil
+}
+
+// translateOpenIDError maps OIDC callback errors to RFC 9457 responses.
+// ErrOpenIDBadRequestWithDetails carries no HTTP semantics of its own (v1 renders
+// it with a bespoke {message, details} body), so v2 maps it to a 400 with the
+// provider detail attached as a structured error detail rather than porting the
+// bespoke shape. Everything else flows through translateDomainError.
+func translateOpenIDError(err error) error {
+ var detailedErr *models.ErrOpenIDBadRequestWithDetails
+ if errors.As(err, &detailedErr) {
+ return huma.Error400BadRequest(detailedErr.Message, &huma.ErrorDetail{
+ Message: "The identity provider rejected the request.",
+ Value: detailedErr.Details,
+ })
+ }
+ return translateDomainError(err)
+}
diff --git a/pkg/routes/api/v2/auth_refresh.go b/pkg/routes/api/v2/auth_refresh.go
new file mode 100644
index 000000000..d264da7c1
--- /dev/null
+++ b/pkg/routes/api/v2/auth_refresh.go
@@ -0,0 +1,75 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package apiv2
+
+import (
+ "context"
+ "net/http"
+
+ "code.vikunja.io/api/pkg/config"
+ "code.vikunja.io/api/pkg/modules/auth"
+ user2 "code.vikunja.io/api/pkg/user"
+
+ "github.com/danielgtaylor/huma/v2"
+)
+
+func init() { AddRouteRegistrar(RegisterRefreshTokenRoutes) }
+
+// RegisterRefreshTokenRoutes wires the refresh-token endpoint. It authenticates
+// via the HttpOnly refresh cookie rather than a JWT, so it is a public operation.
+func RegisterRefreshTokenRoutes(api huma.API) {
+ Register(api, huma.Operation{
+ OperationID: "auth-refresh-token",
+ Summary: "Refresh user token",
+ Description: "Exchanges the refresh-token cookie for a new short-lived JWT. The refresh token is rotated on every call, so the previous one stops working. A new HttpOnly refresh cookie is set on the response.",
+ Method: http.MethodPost,
+ Path: "/user/token/refresh",
+ DefaultStatus: http.StatusOK,
+ Tags: []string{"auth"},
+ Security: publicSecurity,
+ }, authRefreshToken)
+}
+
+func authRefreshToken(ctx context.Context, _ *struct{}) (*authTokenBody, error) {
+ ec := echoContextFromCtx(ctx)
+ if ec == nil {
+ return nil, huma.Error401Unauthorized("No refresh token provided.")
+ }
+
+ cookie, err := ec.Cookie(auth.RefreshTokenCookieName)
+ if err != nil || cookie.Value == "" {
+ return nil, huma.Error401Unauthorized("No refresh token provided.")
+ }
+
+ result, err := auth.RefreshSession(cookie.Value)
+ if err != nil {
+ if user2.IsErrUserStatusError(err) {
+ auth.ClearRefreshTokenCookie(ec)
+ }
+ return nil, translateDomainError(err)
+ }
+
+ cookieMaxAge := int(config.ServiceJWTTTL.GetInt64())
+ if result.IsLongSession {
+ cookieMaxAge = int(config.ServiceJWTTTLLong.GetInt64())
+ }
+ auth.SetRefreshTokenCookie(ec, result.NewRefreshToken, cookieMaxAge)
+
+ out := &authTokenBody{CacheControl: "no-store"}
+ out.Body.Token = result.AccessToken
+ return out, nil
+}
diff --git a/pkg/routes/api/v2/backgrounds.go b/pkg/routes/api/v2/backgrounds.go
index f01fcb4e3..4d7b5befe 100644
--- a/pkg/routes/api/v2/backgrounds.go
+++ b/pkg/routes/api/v2/backgrounds.go
@@ -18,6 +18,7 @@ package apiv2
import (
"context"
+ "io"
"net/http"
"code.vikunja.io/api/pkg/config"
@@ -26,6 +27,8 @@ import (
"code.vikunja.io/api/pkg/modules/background"
backgroundHandler "code.vikunja.io/api/pkg/modules/background/handler"
"code.vikunja.io/api/pkg/modules/background/unsplash"
+ "code.vikunja.io/api/pkg/modules/humaecho5"
+ webfiles "code.vikunja.io/api/pkg/web/files"
"github.com/danielgtaylor/huma/v2"
)
@@ -55,6 +58,26 @@ func RegisterBackgroundRoutes(api huma.API) {
Tags: tags,
}, backgroundRemove)
+ Register(api, huma.Operation{
+ OperationID: "projects-background-get",
+ Summary: "Get a project background",
+ Description: "Streams a project's background image, whichever provider set it. Requires read access to the project. Always served as image/jpeg with a revalidation Last-Modified header, so a conditional If-Modified-Since request gets a 304. Returns 404 when the project has no background.",
+ Method: http.MethodGet,
+ Path: "/projects/{project}/background",
+ Tags: tags,
+ // Spell out the binary response; the default would be modeled as JSON.
+ Responses: map[string]*huma.Response{
+ "200": {
+ Description: "The project background as a jpeg image.",
+ Content: map[string]*huma.MediaType{
+ "image/jpeg": {
+ Schema: &huma.Schema{Type: huma.TypeString, Format: "binary"},
+ },
+ },
+ },
+ },
+ }, backgroundGet)
+
if config.BackgroundsUploadEnabled.GetBool() {
Register(api, huma.Operation{
OperationID: "projects-background-upload",
@@ -89,6 +112,39 @@ func RegisterBackgroundRoutes(api huma.API) {
Path: "/projects/{project}/backgrounds/unsplash",
Tags: tags,
}, backgroundUnsplashSet)
+
+ unsplashProxyResponses := map[string]*huma.Response{
+ "200": {
+ Description: "The proxied Unsplash image as a jpeg image.",
+ Content: map[string]*huma.MediaType{
+ "image/jpeg": {
+ Schema: &huma.Schema{Type: huma.TypeString, Format: "binary"},
+ },
+ },
+ },
+ }
+
+ Register(api, huma.Operation{
+ OperationID: "backgrounds-unsplash-image",
+ Summary: "Proxy a full-resolution Unsplash image",
+ Description: "Proxies the full-resolution Unsplash image for the given image id through Vikunja, so the client never contacts Unsplash directly (privacy). Vikunja fires the required Unsplash pingback as a side effect. Returns 404 if the image does not exist.",
+ Method: http.MethodGet,
+ Path: "/backgrounds/unsplash/images/{image}",
+ Tags: tags,
+ // Spell out the binary response; the default would be modeled as JSON.
+ Responses: unsplashProxyResponses,
+ }, backgroundUnsplashImage)
+
+ Register(api, huma.Operation{
+ OperationID: "backgrounds-unsplash-thumb",
+ Summary: "Proxy an Unsplash image thumbnail",
+ Description: "Proxies a thumbnail (max width 200px) of the Unsplash image for the given image id through Vikunja, so the client never contacts Unsplash directly (privacy). Vikunja fires the required Unsplash pingback as a side effect. Returns 404 if the image does not exist.",
+ Method: http.MethodGet,
+ Path: "/backgrounds/unsplash/images/{image}/thumb",
+ Tags: tags,
+ // Spell out the binary response; the default would be modeled as JSON.
+ Responses: unsplashProxyResponses,
+ }, backgroundUnsplashThumb)
}
}
@@ -227,6 +283,92 @@ func backgroundUpload(ctx context.Context, in *backgroundUploadInput) (*singleBo
return &singleBody[models.Project]{Body: project}, nil
}
+// backgroundGet owns auth, the session and the permission check because there is no
+// handler.Do* for a file body. CanRead hydrates the project (including its
+// BackgroundFileID), which the shared loader then needs.
+func backgroundGet(ctx context.Context, in *struct {
+ ProjectID int64 `path:"project" doc:"The id of the project whose background to fetch."`
+}) (*huma.StreamResponse, error) {
+ a, err := authFromCtx(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ s := db.NewSession()
+ defer s.Close()
+
+ project := &models.Project{ID: in.ProjectID}
+ can, _, err := project.CanRead(s, a)
+ if err != nil {
+ _ = s.Rollback()
+ return nil, translateDomainError(err)
+ }
+ if !can {
+ _ = s.Rollback()
+ return nil, huma.Error403Forbidden("forbidden")
+ }
+
+ bgFile, stat, err := backgroundHandler.LoadProjectBackgroundForDownload(s, project)
+ if err != nil {
+ _ = s.Rollback()
+ return nil, translateDomainError(err)
+ }
+
+ // The file reader comes from object storage, not the DB session, so it stays
+ // valid after the commit; the StreamResponse callback runs after this returns.
+ if err := s.Commit(); err != nil {
+ _ = s.Rollback()
+ // The stream callback (which closes the reader) won't run on this error path.
+ _ = bgFile.File.Close()
+ return nil, translateDomainError(err)
+ }
+
+ return &huma.StreamResponse{Body: func(hctx huma.Context) {
+ defer func() { _ = bgFile.File.Close() }()
+ c := humaecho5.Unwrap(hctx)
+ webfiles.WriteProjectBackground((*c).Response(), (*c).Request(), bgFile, stat)
+ }}, nil
+}
+
+func backgroundUnsplashImage(ctx context.Context, in *struct {
+ ImageID string `path:"image" doc:"The Unsplash image id, from a prior background search."`
+}) (*huma.StreamResponse, error) {
+ if _, err := authFromCtx(ctx); err != nil {
+ return nil, err
+ }
+ body, err := unsplash.FetchUnsplashImageByID(in.ImageID)
+ if err != nil {
+ return nil, translateDomainError(err)
+ }
+ return streamUnsplashProxy(body), nil
+}
+
+func backgroundUnsplashThumb(ctx context.Context, in *struct {
+ ImageID string `path:"image" doc:"The Unsplash image id, from a prior background search."`
+}) (*huma.StreamResponse, error) {
+ if _, err := authFromCtx(ctx); err != nil {
+ return nil, err
+ }
+ body, err := unsplash.FetchUnsplashThumbByID(in.ImageID)
+ if err != nil {
+ return nil, translateDomainError(err)
+ }
+ return streamUnsplashProxy(body), nil
+}
+
+// streamUnsplashProxy copies the open upstream Unsplash body to the response as
+// image/jpeg and closes it, mirroring v1's c.Stream.
+func streamUnsplashProxy(body io.ReadCloser) *huma.StreamResponse {
+ return &huma.StreamResponse{Body: func(hctx huma.Context) {
+ defer func() { _ = body.Close() }()
+ c := humaecho5.Unwrap(hctx)
+ resp := (*c).Response()
+ resp.Header().Set("Content-Type", "image/jpg")
+ resp.WriteHeader(http.StatusOK)
+ _, _ = io.Copy(resp, body)
+ }}
+}
+
func backgroundRemove(ctx context.Context, in *struct {
ProjectID int64 `path:"project"`
}) (*singleBody[models.Project], error) {
diff --git a/pkg/routes/api/v2/errors.go b/pkg/routes/api/v2/errors.go
index 3292b2e2b..24b73a19d 100644
--- a/pkg/routes/api/v2/errors.go
+++ b/pkg/routes/api/v2/errors.go
@@ -28,6 +28,7 @@ import (
"code.vikunja.io/api/pkg/web"
"github.com/danielgtaylor/huma/v2"
+ "github.com/labstack/echo/v5"
)
// authFromCtx retrieves the authed user from a Huma handler context,
@@ -80,6 +81,17 @@ func translateDomainError(err error) error {
}
return se
}
+ // Shared transport-agnostic cores (e.g. auth.RefreshSession) signal HTTP
+ // semantics with *echo.HTTPError. v1 lets echo's error handler render it;
+ // without this it would fall through as a 500 on v2.
+ var he *echo.HTTPError
+ if errors.As(err, &he) {
+ msg := he.Message
+ if msg == "" {
+ msg = http.StatusText(he.Code)
+ }
+ return huma.NewError(he.Code, msg)
+ }
return err
}
diff --git a/pkg/routes/api/v2/health.go b/pkg/routes/api/v2/health.go
new file mode 100644
index 000000000..674dc7b85
--- /dev/null
+++ b/pkg/routes/api/v2/health.go
@@ -0,0 +1,61 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package apiv2
+
+import (
+ "context"
+ "net/http"
+
+ "code.vikunja.io/api/pkg/health"
+
+ "github.com/danielgtaylor/huma/v2"
+)
+
+type healthBody struct {
+ Body struct {
+ Status string `json:"status" doc:"\"OK\" when the service and its dependencies are reachable." example:"OK"`
+ }
+}
+
+// RegisterHealthRoutes wires the public healthcheck endpoint onto the Huma API.
+func RegisterHealthRoutes(api huma.API) {
+ Register(api, huma.Operation{
+ OperationID: "health",
+ Summary: "Healthcheck",
+ Description: "Reports whether the service and its dependencies (database) are reachable. Returns 200 with status \"OK\" when healthy, 500 otherwise. Public — no authentication required.",
+ Method: http.MethodGet,
+ Path: "/health",
+ Tags: []string{"service"},
+ // Public: opt out of the globally-applied auth. The path is also listed
+ // in unauthenticatedAPIPaths so the token middleware lets it through.
+ Security: []map[string][]string{},
+ }, healthcheck)
+}
+
+func init() { AddRouteRegistrar(RegisterHealthRoutes) }
+
+func healthcheck(_ context.Context, _ *struct{}) (*healthBody, error) {
+ //nolint:contextcheck // health.Check is the shared v1/v2 probe; it takes no context and uses background contexts for its own pings.
+ if err := health.Check(); err != nil {
+ // Mirror v1: a failed check is an internal error; the cause is logged,
+ // not leaked to the client.
+ return nil, huma.Error500InternalServerError("Internal server error", err)
+ }
+ out := &healthBody{}
+ out.Body.Status = "OK"
+ return out, nil
+}
diff --git a/pkg/routes/api/v2/huma.go b/pkg/routes/api/v2/huma.go
index 7a7dc3514..9c674c1a7 100644
--- a/pkg/routes/api/v2/huma.go
+++ b/pkg/routes/api/v2/huma.go
@@ -104,6 +104,14 @@ func NewAPI(e *echo.Echo, g *echo.Group) huma.API {
Scheme: "bearer",
Description: "Vikunja API token (tk_ prefix) with scoped permissions. Created via /api/v1/tokens.",
}
+ // HTTP Basic, used only by the notifications Atom feed: feed readers can't
+ // carry a bearer header, so the feed accepts the API token as the Basic
+ // password (username = token owner). See notifications_feed.go.
+ oapi.Components.SecuritySchemes["BasicAuth"] = &huma.SecurityScheme{
+ Type: "http",
+ Scheme: "basic",
+ Description: "HTTP Basic auth used by the notifications Atom feed: the username is the token owner and the password is a feeds-scoped Vikunja API token (tk_ prefix).",
+ }
// Applied globally; public endpoints (spec, docs) opt out with an empty Security list.
oapi.Security = []map[string][]string{
{"JWTKeyAuth": {}},
diff --git a/pkg/routes/api/v2/notifications_feed.go b/pkg/routes/api/v2/notifications_feed.go
new file mode 100644
index 000000000..d6195def2
--- /dev/null
+++ b/pkg/routes/api/v2/notifications_feed.go
@@ -0,0 +1,103 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package apiv2
+
+import (
+ "context"
+ "net/http"
+
+ "code.vikunja.io/api/pkg/db"
+ "code.vikunja.io/api/pkg/modules/humaecho5"
+ "code.vikunja.io/api/pkg/routes/feeds"
+
+ "github.com/danielgtaylor/huma/v2"
+ "github.com/labstack/echo/v5"
+)
+
+// RegisterNotificationsFeedRoutes wires the Atom notifications feed onto the
+// Huma API. It documents HTTP Basic auth (a feeds-scoped API token) because
+// feed readers can't carry a bearer header.
+func RegisterNotificationsFeedRoutes(api huma.API) {
+ Register(api, huma.Operation{
+ OperationID: "notifications-atom-feed",
+ Summary: "Notifications Atom feed",
+ Description: "Returns the authenticated user's latest notifications as an Atom feed. Authenticated with HTTP Basic auth: the username is the token owner and the password is a feeds-scoped Vikunja API token (tk_ prefix) — password and LDAP credentials are rejected because feed URLs are commonly shared or cached. Fetching the feed does not mark notifications as read.",
+ Method: http.MethodGet,
+ Path: "/notifications.atom",
+ Tags: []string{"service"},
+ // This op carries its own HTTP Basic auth instead of the global bearer
+ // schemes; the path is in unauthenticatedAPIPaths so the JWT middleware
+ // lets it through and the handler authenticates itself.
+ Security: []map[string][]string{{"BasicAuth": {}}},
+ Responses: map[string]*huma.Response{
+ "200": {
+ Description: "The notifications Atom feed.",
+ Content: map[string]*huma.MediaType{
+ "application/atom+xml": {
+ Schema: &huma.Schema{Type: huma.TypeString, Format: "binary"},
+ },
+ },
+ },
+ },
+ }, notificationsAtomFeed)
+}
+
+func init() { AddRouteRegistrar(RegisterNotificationsFeedRoutes) }
+
+// notificationsAtomFeed authenticates with HTTP Basic (sharing the feeds
+// validator) and streams the Atom feed; there is no handler.Do* for a non-JSON
+// body and the auth can't ride the group's JWT middleware.
+func notificationsAtomFeed(ctx context.Context, _ *struct{}) (*huma.StreamResponse, error) {
+ c, ok := ctx.Value(humaecho5.EchoContextKey).(*echo.Context)
+ if !ok {
+ return nil, huma.Error500InternalServerError("could not resolve request context")
+ }
+
+ username, password, ok := (*c).Request().BasicAuth()
+ if !ok {
+ return nil, basicAuthChallenge(c)
+ }
+
+ s := db.NewSession()
+ defer s.Close()
+
+ u, err := feeds.AuthenticateFeedToken(s, username, password)
+ if err != nil {
+ return nil, translateDomainError(err)
+ }
+ if u == nil {
+ return nil, basicAuthChallenge(c)
+ }
+
+ atom, err := feeds.BuildNotificationsAtomFeed(s, u)
+ if err != nil {
+ return nil, translateDomainError(err)
+ }
+
+ return &huma.StreamResponse{Body: func(hctx huma.Context) {
+ ec := humaecho5.Unwrap(hctx)
+ (*ec).Response().Header().Set(echo.HeaderContentType, feeds.AtomContentType)
+ _, _ = (*ec).Response().Write([]byte(atom))
+ }}, nil
+}
+
+// basicAuthChallenge returns a 401 carrying a WWW-Authenticate Basic challenge,
+// mirroring v1's BasicAuth middleware so feed readers prompt for credentials.
+func basicAuthChallenge(c *echo.Context) error {
+ (*c).Response().Header().Set(echo.HeaderWWWAuthenticate, `Basic realm="Restricted"`)
+ return huma.Error401Unauthorized(http.StatusText(http.StatusUnauthorized))
+}
diff --git a/pkg/routes/api/v2/oauth.go b/pkg/routes/api/v2/oauth.go
index 45d1efe57..a67441ad3 100644
--- a/pkg/routes/api/v2/oauth.go
+++ b/pkg/routes/api/v2/oauth.go
@@ -21,11 +21,9 @@ import (
"net/http"
"code.vikunja.io/api/pkg/modules/auth/oauth2server"
- "code.vikunja.io/api/pkg/modules/humaecho5"
"code.vikunja.io/api/pkg/user"
"github.com/danielgtaylor/huma/v2"
- "github.com/labstack/echo/v5"
)
// oauthTokenBody wraps the OAuth 2.0 token response.
@@ -101,11 +99,12 @@ func oauthAuthorize(ctx context.Context, in *struct{ Body oauth2server.Authorize
}
// requestClientInfo pulls the user agent and client IP off the underlying Echo
-// request so the authorization_code grant can record them on the session it
-// creates, mirroring v1. Both fall back to "" when the context is unavailable.
+// request so the authorization_code grant (and login) can record them on the
+// session they create, mirroring v1. Both fall back to "" when the context is
+// unavailable.
func requestClientInfo(ctx context.Context) (deviceInfo, ipAddress string) {
- ec, ok := ctx.Value(humaecho5.EchoContextKey).(*echo.Context)
- if !ok || ec == nil {
+ ec := echoContextFromCtx(ctx)
+ if ec == nil {
return "", ""
}
return (*ec).Request().UserAgent(), (*ec).RealIP()
diff --git a/pkg/routes/api/v2/testing.go b/pkg/routes/api/v2/testing.go
new file mode 100644
index 000000000..2f753f3fe
--- /dev/null
+++ b/pkg/routes/api/v2/testing.go
@@ -0,0 +1,129 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package apiv2
+
+import (
+ "context"
+ "net/http"
+
+ "code.vikunja.io/api/pkg/config"
+ "code.vikunja.io/api/pkg/log"
+ "code.vikunja.io/api/pkg/routes/api/shared"
+
+ "github.com/danielgtaylor/huma/v2"
+)
+
+// testingReplaceInput is the request for resetting a single table. The
+// Authorization header carries the configured testing token (not a JWT or API
+// token); the endpoint is public and checks it in-handler like v1.
+type testingReplaceInput struct {
+ Table string `path:"table" doc:"The table to reset."`
+ // String (not bool) so absent is distinguishable from an explicit "false":
+ // like v1, an absent truncate parameter means truncate. Huma does not
+ // support *bool params, and a bool with default:"true" silently ignores an
+ // explicit ?truncate=false, so the parameter is read as a raw string and
+ // interpreted in the handler exactly like v1 does.
+ Truncate string `query:"truncate" enum:"true,false" doc:"Empty the table (and its dependents) before inserting the rows. Defaults to true; pass false to restore on top of existing data."`
+ Authorization string `header:"Authorization" doc:"The configured testing token."`
+ Body []map[string]any `doc:"The rows to write into the table. Free-form objects matching the table's columns."`
+}
+
+type testingReplaceBody struct {
+ Body []map[string]any `doc:"The table's contents after the reset."`
+}
+
+type testingTruncateAllInput struct {
+ Authorization string `header:"Authorization" doc:"The configured testing token."`
+}
+
+type testingTruncateAllBody struct {
+ Body struct {
+ Message string `json:"message" doc:"Always \"ok\" on success."`
+ }
+}
+
+// RegisterTestingRoutes wires the e2e testing-support endpoints onto the Huma
+// API. They are only mounted when the testing token is configured, matching v1.
+func RegisterTestingRoutes(api huma.API) {
+ if config.ServiceTestingtoken.GetString() == "" {
+ return
+ }
+
+ tags := []string{"testing"}
+ // Public: opt out of the globally-applied JWT/API-token auth — these
+ // authenticate with the testing token via the Authorization header
+ // instead. Their paths are also listed in unauthenticatedAPIPaths so the
+ // token middleware lets them through.
+ noAuth := []map[string][]string{}
+
+ Register(api, huma.Operation{
+ OperationID: "testing-truncate-all",
+ Summary: "Truncate all tables",
+ Description: "Removes all data from every Vikunja table. Used by e2e tests to ensure a clean state before each test. Authenticates with the configured testing token via the Authorization header, not a JWT or API token.",
+ Method: http.MethodDelete,
+ Path: "/test/all",
+ Tags: tags,
+ Security: noAuth,
+ // v1 returns 200 with a body rather than the 204 a DELETE would default to.
+ DefaultStatus: http.StatusOK,
+ }, testingTruncateAll)
+
+ Register(api, huma.Operation{
+ OperationID: "testing-replace-table",
+ Summary: "Reset a table to a defined state",
+ Description: "Replaces the contents of the named table with the rows in the payload and returns the resulting contents. Used by e2e tests to seed fixtures. Authenticates with the configured testing token via the Authorization header, not a JWT or API token.",
+ Method: http.MethodPut,
+ Path: "/test/{table}",
+ Tags: tags,
+ Security: noAuth,
+ // Mirror v1's 201 for a successful reset.
+ DefaultStatus: http.StatusCreated,
+ }, testingReplaceTable)
+}
+
+func init() { AddRouteRegistrar(RegisterTestingRoutes) }
+
+func testingReplaceTable(_ context.Context, in *testingReplaceInput) (*testingReplaceBody, error) {
+ if in.Authorization != config.ServiceTestingtoken.GetString() {
+ return nil, huma.Error403Forbidden("forbidden")
+ }
+
+ // Mirror v1: absent or "true" truncates; only an explicit "false" appends.
+ truncate := in.Truncate == "true" || in.Truncate == ""
+ data, err := shared.ReplaceTableContents(in.Table, in.Body, truncate)
+ if err != nil {
+ log.Errorf("Error replacing table data: %v", err)
+ return nil, huma.Error500InternalServerError("could not replace table data")
+ }
+
+ return &testingReplaceBody{Body: data}, nil
+}
+
+func testingTruncateAll(_ context.Context, in *testingTruncateAllInput) (*testingTruncateAllBody, error) {
+ if in.Authorization != config.ServiceTestingtoken.GetString() {
+ return nil, huma.Error403Forbidden("forbidden")
+ }
+
+ if err := shared.TruncateAllTestingTables(); err != nil {
+ log.Errorf("Error truncating all tables: %v", err)
+ return nil, huma.Error500InternalServerError("could not truncate tables")
+ }
+
+ out := &testingTruncateAllBody{}
+ out.Body.Message = "ok"
+ return out, nil
+}
diff --git a/pkg/routes/api/v2/user_export.go b/pkg/routes/api/v2/user_export.go
new file mode 100644
index 000000000..952f8127b
--- /dev/null
+++ b/pkg/routes/api/v2/user_export.go
@@ -0,0 +1,181 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package apiv2
+
+import (
+ "context"
+ "net/http"
+
+ "code.vikunja.io/api/pkg/db"
+ "code.vikunja.io/api/pkg/events"
+ "code.vikunja.io/api/pkg/models"
+ "code.vikunja.io/api/pkg/modules/humaecho5"
+ "code.vikunja.io/api/pkg/user"
+ webfiles "code.vikunja.io/api/pkg/web/files"
+
+ "github.com/danielgtaylor/huma/v2"
+ "xorm.io/xorm"
+)
+
+type userExportPasswordBody struct {
+ Body struct {
+ Password string `json:"password" doc:"The authenticated user's password. Required for local users; ignored for users authenticated via an external provider."`
+ }
+}
+
+type userExportStatusBody struct {
+ Body *models.UserExportStatus
+}
+
+func RegisterUserExportRoutes(api huma.API) {
+ tags := []string{"user"}
+
+ Register(api, huma.Operation{
+ OperationID: "user-export-request",
+ Summary: "Request a data export",
+ Description: "Starts building a full export of the authenticated user's data. Local users must confirm with their password. The export runs in the background; an email is sent when it is ready to download.",
+ Method: http.MethodPost,
+ Path: "/user/export/request",
+ Tags: tags,
+ DefaultStatus: http.StatusOK,
+ }, userExportRequest)
+
+ Register(api, huma.Operation{
+ OperationID: "user-export-download",
+ Summary: "Download the data export",
+ Description: "Streams the authenticated user's prepared data export as a zip file. Local users must confirm with their password. Fails with 404 if no export has been prepared. A POST (not GET) because the password is sent in the body.",
+ Method: http.MethodPost,
+ Path: "/user/export/download",
+ Tags: tags,
+ // Spell out the binary response; the default would be modeled as JSON.
+ Responses: map[string]*huma.Response{
+ "200": {
+ Description: "The data export as a zip file.",
+ Content: map[string]*huma.MediaType{
+ "application/zip": {
+ Schema: &huma.Schema{Type: huma.TypeString, Format: "binary"},
+ },
+ },
+ },
+ },
+ }, userExportDownload)
+
+ Register(api, huma.Operation{
+ OperationID: "user-export-status",
+ Summary: "Get the current data export",
+ Description: "Returns metadata about the authenticated user's current data export (id, size, creation and expiry time), or null if none has been prepared.",
+ Method: http.MethodGet,
+ Path: "/user/export",
+ Tags: tags,
+ }, userExportStatus)
+}
+
+func init() { AddRouteRegistrar(RegisterUserExportRoutes) }
+
+// confirmExportPassword resolves the full DB user and, for local accounts, verifies
+// the supplied password — mirroring v1's checkExportRequest. External-provider users
+// cannot supply a password and are passed through, as in v1.
+func confirmExportPassword(ctx context.Context, s *xorm.Session, password string) (*user.User, error) {
+ u, err := authUserFromCtx(ctx, s)
+ if err != nil {
+ return nil, err
+ }
+ if u.IsLocalUser() {
+ if err := user.CheckUserPassword(u, password); err != nil {
+ return nil, translateDomainError(err)
+ }
+ }
+ return u, nil
+}
+
+func userExportRequest(ctx context.Context, in *userExportPasswordBody) (*messageBody, error) {
+ s := db.NewSession()
+ defer s.Close()
+
+ u, err := confirmExportPassword(ctx, s, in.Body.Password)
+ if err != nil {
+ _ = s.Rollback()
+ return nil, err
+ }
+
+ events.DispatchOnCommit(s, &models.UserDataExportRequestedEvent{User: u})
+
+ if err := s.Commit(); err != nil {
+ _ = s.Rollback()
+ events.CleanupPending(s)
+ return nil, translateDomainError(err)
+ }
+ events.DispatchPending(ctx, s)
+
+ out := &messageBody{}
+ out.Body.Message = "Successfully requested data export. We will send you an email when it's ready."
+ return out, nil
+}
+
+func userExportDownload(ctx context.Context, in *userExportPasswordBody) (*huma.StreamResponse, error) {
+ s := db.NewSession()
+ defer s.Close()
+
+ u, err := confirmExportPassword(ctx, s, in.Body.Password)
+ if err != nil {
+ _ = s.Rollback()
+ return nil, err
+ }
+
+ exportFile, err := models.GetUserDataExportFile(u)
+ if err != nil {
+ _ = s.Rollback()
+ return nil, translateDomainError(err)
+ }
+
+ // The file reader comes from object storage, not the DB session, so it stays
+ // valid after the commit; the StreamResponse callback runs after this returns.
+ if err := s.Commit(); err != nil {
+ _ = s.Rollback()
+ // The stream callback (which closes the reader) won't run on this error path.
+ _ = exportFile.File.Close()
+ return nil, translateDomainError(err)
+ }
+
+ return &huma.StreamResponse{Body: func(hctx huma.Context) {
+ defer func() { _ = exportFile.File.Close() }()
+ c := humaecho5.Unwrap(hctx)
+ webfiles.WriteFileDownload((*c).Response(), (*c).Request(), exportFile)
+ }}, nil
+}
+
+func userExportStatus(ctx context.Context, _ *struct{}) (*userExportStatusBody, error) {
+ s := db.NewSession()
+ defer s.Close()
+
+ u, err := authUserFromCtx(ctx, s)
+ if err != nil {
+ _ = s.Rollback()
+ return nil, err
+ }
+
+ status, err := models.GetUserDataExportStatus(u)
+ if err != nil {
+ _ = s.Rollback()
+ return nil, translateDomainError(err)
+ }
+ if err := s.Commit(); err != nil {
+ return nil, translateDomainError(err)
+ }
+
+ return &userExportStatusBody{Body: status}, nil
+}
diff --git a/pkg/routes/api/v2/user_totp.go b/pkg/routes/api/v2/user_totp.go
index d998a524e..dd3b0c575 100644
--- a/pkg/routes/api/v2/user_totp.go
+++ b/pkg/routes/api/v2/user_totp.go
@@ -49,10 +49,16 @@ type totpMessageBody struct {
Body models.Message
}
+// totpQrCodeResponse carries the qr code jpeg bytes plus a fixed Content-Type.
+// Huma writes the []byte Body straight to the wire; the header field overrides
+// content negotiation so image/jpeg reaches the client (matching v1).
+type totpQrCodeResponse struct {
+ ContentType string `header:"Content-Type"`
+ Body []byte
+}
+
// RegisterTOTPRoutes wires the current-user totp (2FA) operations onto the Huma
// API. Totp is a local-account feature; every handler rejects OIDC/LDAP users.
-// The QR-code blob endpoint is intentionally not ported here (binary streaming,
-// handled in a later wave).
func RegisterTOTPRoutes(api huma.API) {
if !config.ServiceEnableTotp.GetBool() {
return
@@ -100,6 +106,27 @@ func RegisterTOTPRoutes(api huma.API) {
DefaultStatus: http.StatusOK,
Tags: tags,
}, totpDisable)
+
+ Register(api, huma.Operation{
+ OperationID: "totp-qrcode",
+ Summary: "Get the totp enrollment qr code",
+ Description: "Returns the qr code for the authenticated user's enrolled totp setting as a jpeg image, for scanning into an authenticator app. Requires a prior enrollment. Local accounts only.",
+ Method: http.MethodGet,
+ Path: "/user/settings/totp/qrcode",
+ Tags: tags,
+ // Spell out the binary response; a bare []byte Body would otherwise be
+ // modeled as a base64 JSON string instead of binary image data.
+ Responses: map[string]*huma.Response{
+ "200": {
+ Description: "The qr code as a jpeg image.",
+ Content: map[string]*huma.MediaType{
+ "image/jpeg": {
+ Schema: &huma.Schema{Type: huma.TypeString, Format: "binary"},
+ },
+ },
+ },
+ },
+ }, totpQrCode)
}
func init() { AddRouteRegistrar(RegisterTOTPRoutes) }
@@ -208,3 +235,21 @@ func totpDisable(ctx context.Context, in *totpDisableBody) (*totpMessageBody, er
}
return &totpMessageBody{Body: models.Message{Message: "TOTP was disabled successfully."}}, nil
}
+
+func totpQrCode(ctx context.Context, _ *struct{}) (*totpQrCodeResponse, error) {
+ u, s, err := localUserFromCtx(ctx)
+ if err != nil {
+ return nil, err
+ }
+ defer s.Close()
+
+ qrcode, err := user.GetTOTPQrCodeAsJpegForUser(s, u)
+ if err != nil {
+ _ = s.Rollback()
+ return nil, translateDomainError(err)
+ }
+ if err := s.Commit(); err != nil {
+ return nil, translateDomainError(err)
+ }
+ return &totpQrCodeResponse{ContentType: "image/jpeg", Body: qrcode}, nil
+}
diff --git a/pkg/routes/feeds/auth.go b/pkg/routes/feeds/auth.go
index 419fd2ccd..a0142317b 100644
--- a/pkg/routes/feeds/auth.go
+++ b/pkg/routes/feeds/auth.go
@@ -23,9 +23,9 @@ import (
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
- "xorm.io/xorm"
"github.com/labstack/echo/v5"
+ "xorm.io/xorm"
)
func checkAPIToken(s *xorm.Session, username, token string) (*user.User, error) {
@@ -50,35 +50,48 @@ func checkAPIToken(s *xorm.Session, username, token string) (*user.User, error)
return u, nil
}
-// BasicAuth authenticates feed requests. Only API tokens are accepted —
-// password and LDAP credentials are rejected outright because feed URLs are
-// commonly exported, shared, or cached by feed readers.
-func BasicAuth(c *echo.Context, username, password string) (bool, error) {
+// AuthenticateFeedToken validates feed credentials against an existing session.
+// Only API tokens are accepted — password and LDAP credentials are rejected
+// outright because feed URLs are commonly exported, shared, or cached by feed
+// readers. It returns the authenticated user, or nil for any rejection so
+// callers can treat "invalid" and "unknown" identically.
+func AuthenticateFeedToken(s *xorm.Session, username, password string) (*user.User, error) {
if !strings.HasPrefix(password, models.APITokenPrefix) {
- return false, nil
+ return nil, nil
}
// GetTokenFromTokenString slices password[len-8:] without a length check,
// so a stray "tk_" or other short prefix-only string would panic before
// the credentials could be rejected. Real tokens are far longer than
// prefix+8, so anything shorter is invalid by construction.
if len(password) < len(models.APITokenPrefix)+8 {
- return false, nil
+ return nil, nil
}
- s := db.NewSession()
- defer s.Close()
-
u, err := checkAPIToken(s, username, password)
if err != nil {
log.Errorf("Error during API token auth for feeds: %v", err)
- return false, nil
+ return nil, nil
}
if u == nil {
- return false, nil
+ return nil, nil
}
if u.IsBot() {
log.Warningf("Feed auth rejected for bot user %d", u.ID)
- return false, nil
+ return nil, nil
+ }
+
+ return u, nil
+}
+
+// BasicAuth authenticates feed requests for echo's BasicAuth middleware. The
+// validation logic is shared with the v2 handler via AuthenticateFeedToken.
+func BasicAuth(c *echo.Context, username, password string) (bool, error) {
+ s := db.NewSession()
+ defer s.Close()
+
+ u, err := AuthenticateFeedToken(s, username, password)
+ if err != nil || u == nil {
+ return false, err
}
c.Set("userBasicAuth", u)
diff --git a/pkg/routes/feeds/handler.go b/pkg/routes/feeds/handler.go
index 9d0794c76..2a5ce289d 100644
--- a/pkg/routes/feeds/handler.go
+++ b/pkg/routes/feeds/handler.go
@@ -30,24 +30,22 @@ import (
"github.com/gorilla/feeds"
"github.com/labstack/echo/v5"
+ "xorm.io/xorm"
)
const feedItemLimit = 50
-// NotificationsAtomFeed serves the authenticated user's notifications as an
-// Atom feed. Notifications are not marked as read by being fetched here.
-func NotificationsAtomFeed(c *echo.Context) error {
- u, ok := c.Get("userBasicAuth").(*user.User)
- if !ok {
- return echo.NewHTTPError(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
- }
-
- s := db.NewSession()
- defer s.Close()
+// AtomContentType is the content type of the notifications Atom feed. Shared so
+// the v1 echo handler and the v2 Huma op set the same header.
+const AtomContentType = "application/atom+xml; charset=utf-8"
+// BuildNotificationsAtomFeed renders the user's latest notifications as Atom XML
+// against an existing session. Notifications are not marked as read by being
+// fetched here. Shared by the v1 echo handler and the v2 Huma op.
+func BuildNotificationsAtomFeed(s *xorm.Session, u *user.User) (string, error) {
rows, _, _, err := notifications.GetNotificationsForUser(s, u.ID, feedItemLimit, 0)
if err != nil {
- return err
+ return "", err
}
publicURL := config.ServicePublicURL.GetString()
@@ -85,11 +83,25 @@ func NotificationsAtomFeed(c *echo.Context) error {
})
}
- atom, err := feed.ToAtom()
+ return feed.ToAtom()
+}
+
+// NotificationsAtomFeed serves the authenticated user's notifications as an
+// Atom feed. Notifications are not marked as read by being fetched here.
+func NotificationsAtomFeed(c *echo.Context) error {
+ u, ok := c.Get("userBasicAuth").(*user.User)
+ if !ok {
+ return echo.NewHTTPError(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
+ }
+
+ s := db.NewSession()
+ defer s.Close()
+
+ atom, err := BuildNotificationsAtomFeed(s, u)
if err != nil {
return err
}
- c.Response().Header().Set(echo.HeaderContentType, "application/atom+xml; charset=utf-8")
+ c.Response().Header().Set(echo.HeaderContentType, AtomContentType)
return c.String(http.StatusOK, atom)
}
diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go
index 9f6af5af3..bcdea1bdb 100644
--- a/pkg/routes/routes.go
+++ b/pkg/routes/routes.go
@@ -354,12 +354,31 @@ var unauthenticatedAPIPaths = map[string]bool{
"/api/v2/schemas/:schema": true,
"/api/v2/info": true,
- "/api/v2/register": true,
- "/api/v2/user/password/token": true,
- "/api/v2/user/password/reset": true,
- "/api/v2/user/confirm": true,
- "/api/v2/shares/:share/auth": true,
- "/api/v2/oauth/token": true,
+ "/api/v2/register": true,
+ "/api/v2/user/password/token": true,
+ "/api/v2/user/password/reset": true,
+ "/api/v2/user/confirm": true,
+ "/api/v2/shares/:share/auth": true,
+ "/api/v2/oauth/token": true,
+ "/api/v2/login": true,
+ "/api/v2/user/token/refresh": true,
+ "/api/v2/auth/openid/:provider/callback": true,
+
+ // Testing endpoints authenticate with the testing token via a custom
+ // Authorization header, not a JWT; mounted only when that token is set.
+ "/api/v2/test/all": true,
+ "/api/v2/test/:table": true,
+
+ // Public infra healthcheck (a Huma op that opts out of the global auth).
+ "/api/v2/health": true,
+
+ // Atom feed (a Huma op) authenticates itself with HTTP Basic auth (a
+ // feeds-scoped API token), like its /feeds counterpart, not a JWT.
+ "/api/v2/notifications.atom": true,
+
+ // WebSocket upgrade (a raw echo route — OpenAPI can't model WebSockets);
+ // it authenticates via its first message, so the upgrade needs no JWT.
+ "/api/v2/ws": true,
}
// collectRoutesForAPITokens collects all routes for API token permission checking.
@@ -432,6 +451,13 @@ func registerAPIRoutesV2(e *echo.Echo, a *echo.Group) {
a.GET("/docs", apiv2.ScalarUI)
a.GET("/docs/scalar.standalone.js", apiv2.ScalarJS)
+ // WebSockets can't be modeled in OpenAPI and Huma has no WS support, so the
+ // upgrade endpoint stays a raw echo route (outside the Huma spec). It
+ // authenticates via its first message, so unauthenticatedAPIPaths exempts it
+ // from the group's JWT middleware. Health and the Atom feed are Huma ops and
+ // self-register via init()/RegisterAll.
+ a.GET("/ws", ws.UpgradeHandler)
+
// Resources self-register via init(); RegisterAll runs them all + AutoPatch.
apiv2.RegisterAll(api)
}
diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go
index 8f6f0e673..e976b4d74 100644
--- a/pkg/swagger/docs.go
+++ b/pkg/swagger/docs.go
@@ -7342,7 +7342,7 @@ const docTemplate = `{
"200": {
"description": "OK",
"schema": {
- "$ref": "#/definitions/v1.UserExportStatus"
+ "$ref": "#/definitions/models.UserExportStatus"
}
}
}
@@ -10579,6 +10579,23 @@ const docTemplate = `{
}
}
},
+ "models.UserExportStatus": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "string"
+ },
+ "expires": {
+ "type": "string"
+ },
+ "id": {
+ "type": "integer"
+ },
+ "size": {
+ "type": "integer"
+ }
+ }
+ },
"models.UserGeneralSettings": {
"type": "object",
"properties": {
@@ -11141,23 +11158,6 @@ const docTemplate = `{
}
}
},
- "v1.UserExportStatus": {
- "type": "object",
- "properties": {
- "created": {
- "type": "string"
- },
- "expires": {
- "type": "string"
- },
- "id": {
- "type": "integer"
- },
- "size": {
- "type": "integer"
- }
- }
- },
"v1.UserPassword": {
"type": "object",
"properties": {
diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json
index 4cab9fb5e..98f528565 100644
--- a/pkg/swagger/swagger.json
+++ b/pkg/swagger/swagger.json
@@ -7334,7 +7334,7 @@
"200": {
"description": "OK",
"schema": {
- "$ref": "#/definitions/v1.UserExportStatus"
+ "$ref": "#/definitions/models.UserExportStatus"
}
}
}
@@ -10571,6 +10571,23 @@
}
}
},
+ "models.UserExportStatus": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "string"
+ },
+ "expires": {
+ "type": "string"
+ },
+ "id": {
+ "type": "integer"
+ },
+ "size": {
+ "type": "integer"
+ }
+ }
+ },
"models.UserGeneralSettings": {
"type": "object",
"properties": {
@@ -11133,23 +11150,6 @@
}
}
},
- "v1.UserExportStatus": {
- "type": "object",
- "properties": {
- "created": {
- "type": "string"
- },
- "expires": {
- "type": "string"
- },
- "id": {
- "type": "integer"
- },
- "size": {
- "type": "integer"
- }
- }
- },
"v1.UserPassword": {
"type": "object",
"properties": {
diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml
index af2b209dd..a926e18b8 100644
--- a/pkg/swagger/swagger.yaml
+++ b/pkg/swagger/swagger.yaml
@@ -1292,6 +1292,17 @@ definitions:
this value.
type: string
type: object
+ models.UserExportStatus:
+ properties:
+ created:
+ type: string
+ expires:
+ type: string
+ id:
+ type: integer
+ size:
+ type: integer
+ type: object
models.UserGeneralSettings:
properties:
default_project_id:
@@ -1708,17 +1719,6 @@ definitions:
token:
type: string
type: object
- v1.UserExportStatus:
- properties:
- created:
- type: string
- expires:
- type: string
- id:
- type: integer
- size:
- type: integer
- type: object
v1.UserPassword:
properties:
new_password:
@@ -6860,7 +6860,7 @@ paths:
"200":
description: OK
schema:
- $ref: '#/definitions/v1.UserExportStatus'
+ $ref: '#/definitions/models.UserExportStatus'
security:
- JWTKeyAuth: []
summary: Get current user data export
diff --git a/pkg/user/totp.go b/pkg/user/totp.go
index e18948443..98c4327cb 100644
--- a/pkg/user/totp.go
+++ b/pkg/user/totp.go
@@ -17,8 +17,10 @@
package user
import (
+ "bytes"
"fmt"
"image"
+ "image/jpeg"
"strconv"
"time"
@@ -198,6 +200,21 @@ func GetTOTPQrCodeForUser(s *xorm.Session, user *User) (qrcode image.Image, err
return key.Image(300, 300)
}
+// GetTOTPQrCodeAsJpegForUser renders the user's totp qr code to jpeg bytes, the
+// wire format both API versions serve.
+func GetTOTPQrCodeAsJpegForUser(s *xorm.Session, user *User) ([]byte, error) {
+ qrcode, err := GetTOTPQrCodeForUser(s, user)
+ if err != nil {
+ return nil, err
+ }
+
+ buff := &bytes.Buffer{}
+ if err := jpeg.Encode(buff, qrcode, nil); err != nil {
+ return nil, err
+ }
+ return buff.Bytes(), nil
+}
+
// HandleFailedTOTPAuth records a failed TOTP attempt and locks the account
// after 10 consecutive failures.
//
diff --git a/pkg/web/files/file.go b/pkg/web/files/file.go
new file mode 100644
index 000000000..461fe2780
--- /dev/null
+++ b/pkg/web/files/file.go
@@ -0,0 +1,61 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package files
+
+import (
+ "io"
+ "mime"
+ "net/http"
+ "strconv"
+
+ "code.vikunja.io/api/pkg/files"
+)
+
+// WriteFileDownload streams a loaded file (its .File reader must be open) to the
+// response as an attachment download: http.ServeContent for seekable local files
+// (Range + If-Modified-Since for free), a manual 304 + io.Copy otherwise. It does
+// not close the reader; the caller owns it.
+func WriteFileDownload(w http.ResponseWriter, r *http.Request, f *files.File) {
+ // Downloads must never be cached. no-cache overrides the global no-store
+ // directive so revalidation (If-Modified-Since) still works.
+ w.Header().Set("Cache-Control", "no-cache")
+
+ mimeToReturn := f.Mime
+ if mimeToReturn == "" {
+ mimeToReturn = "application/octet-stream"
+ }
+ w.Header().Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename": f.Name}))
+ w.Header().Set("Content-Type", mimeToReturn)
+ w.Header().Set("Content-Length", strconv.FormatUint(f.Size, 10))
+ w.Header().Set("Last-Modified", f.Created.UTC().Format(http.TimeFormat))
+
+ // Local files are *os.File (seekable), so ServeContent gives Range +
+ // If-Modified-Since for free; s3 (and the in-memory test storage) return a
+ // non-seekable reader, so check If-Modified-Since manually and io.Copy.
+ if seeker, ok := f.File.(io.ReadSeeker); ok {
+ http.ServeContent(w, r, f.Name, f.Created, seeker)
+ return
+ }
+
+ if ifModSince := r.Header.Get("If-Modified-Since"); ifModSince != "" {
+ if t, parseErr := http.ParseTime(ifModSince); parseErr == nil && !f.Created.UTC().After(t) {
+ w.WriteHeader(http.StatusNotModified)
+ return
+ }
+ }
+ _, _ = io.Copy(w, f.File)
+}
diff --git a/pkg/web/files/project_background.go b/pkg/web/files/project_background.go
new file mode 100644
index 000000000..aeda725a7
--- /dev/null
+++ b/pkg/web/files/project_background.go
@@ -0,0 +1,55 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package files
+
+import (
+ "io"
+ "net/http"
+ "os"
+
+ "code.vikunja.io/api/pkg/files"
+)
+
+// WriteProjectBackground streams a project's background file (its .File reader must be
+// open) to the response, shared by the v1 and v2 background handlers. It does not close
+// the reader; the caller owns it.
+//
+// The wire shape differs from WriteFileDownload on purpose and must stay byte-identical
+// to v1: backgrounds are always served as image/jpg (no Content-Disposition, no
+// Content-Length), with a cache-revalidation Last-Modified from the storage modtime
+// rather than the file's DB Created timestamp.
+func WriteProjectBackground(w http.ResponseWriter, r *http.Request, bgFile *files.File, stat os.FileInfo) {
+ // Override the global no-store directive so browsers can cache background images.
+ // no-cache allows caching but requires revalidation via If-Modified-Since.
+ w.Header().Set("Cache-Control", "no-cache")
+
+ if stat != nil {
+ modTime := stat.ModTime().UTC()
+ w.Header().Set("Last-Modified", modTime.Format(http.TimeFormat))
+
+ if ifModSince := r.Header.Get("If-Modified-Since"); ifModSince != "" {
+ if t, err := http.ParseTime(ifModSince); err == nil && !modTime.After(t) {
+ w.WriteHeader(http.StatusNotModified)
+ return
+ }
+ }
+ }
+
+ w.Header().Set("Content-Type", "image/jpg")
+ w.WriteHeader(http.StatusOK)
+ _, _ = io.Copy(w, bgFile.File)
+}
diff --git a/pkg/web/files/task_attachment.go b/pkg/web/files/task_attachment.go
index 3db78e62f..55945fe23 100644
--- a/pkg/web/files/task_attachment.go
+++ b/pkg/web/files/task_attachment.go
@@ -21,8 +21,6 @@
package files
import (
- "io"
- "mime"
"net/http"
"strconv"
@@ -62,43 +60,20 @@ func toAttachmentUploadError(err error) AttachmentUploadError {
return AttachmentUploadError{Message: err.Error()}
}
-// WriteAttachmentDownload streams the attachment (or its preview) to the response:
-// http.ServeContent for seekable local files (Range + If-Modified-Since for free),
-// a manual 304 + io.Copy otherwise. It closes the file reader.
+// WriteAttachmentDownload streams the attachment (or its inline image preview) to
+// the response and closes the file reader. The non-preview path delegates to
+// WriteFileDownload, which sets Cache-Control: no-cache; the preview branch returns
+// early, so it sets the same header itself.
func WriteAttachmentDownload(w http.ResponseWriter, r *http.Request, ta *models.TaskAttachment, preview []byte) {
defer func() { _ = ta.File.File.Close() }()
if preview != nil {
+ w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Content-Type", "image/png")
w.Header().Set("Content-Length", strconv.Itoa(len(preview)))
_, _ = w.Write(preview)
return
}
- mimeToReturn := ta.File.Mime
- if mimeToReturn == "" {
- mimeToReturn = "application/octet-stream"
- }
- w.Header().Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename": ta.File.Name}))
- w.Header().Set("Content-Type", mimeToReturn)
- w.Header().Set("Content-Length", strconv.FormatUint(ta.File.Size, 10))
- w.Header().Set("Last-Modified", ta.File.Created.UTC().Format(http.TimeFormat))
- // Override the global no-store directive so browsers can cache attachments.
- w.Header().Set("Cache-Control", "no-cache")
-
- // Local files are *os.File (seekable), so ServeContent gives Range +
- // If-Modified-Since for free; s3 (and the in-memory test storage) return a
- // non-seekable reader, so check If-Modified-Since manually and io.Copy.
- if seeker, ok := ta.File.File.(io.ReadSeeker); ok {
- http.ServeContent(w, r, ta.File.Name, ta.File.Created, seeker)
- return
- }
-
- if ifModSince := r.Header.Get("If-Modified-Since"); ifModSince != "" {
- if t, parseErr := http.ParseTime(ifModSince); parseErr == nil && !ta.File.Created.UTC().After(t) {
- w.WriteHeader(http.StatusNotModified)
- return
- }
- }
- _, _ = io.Copy(w, ta.File.File)
+ WriteFileDownload(w, r, ta.File)
}
diff --git a/pkg/webtests/huma_auth_login_test.go b/pkg/webtests/huma_auth_login_test.go
new file mode 100644
index 000000000..ad83fc811
--- /dev/null
+++ b/pkg/webtests/huma_auth_login_test.go
@@ -0,0 +1,196 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package webtests
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+ "time"
+
+ "code.vikunja.io/api/pkg/config"
+ "code.vikunja.io/api/pkg/db"
+ "code.vikunja.io/api/pkg/models"
+ "code.vikunja.io/api/pkg/modules/auth"
+ "code.vikunja.io/api/pkg/user"
+
+ "github.com/pquerna/otp/totp"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// refreshCookie returns the Set-Cookie value for the refresh-token cookie, or ""
+// if the response set no such cookie.
+func refreshCookie(rec *httptest.ResponseRecorder) *http.Cookie {
+ for _, c := range rec.Result().Cookies() {
+ if c.Name == auth.RefreshTokenCookieName {
+ return c
+ }
+ }
+ return nil
+}
+
+// TestHumaLogin ports the v1 login coverage to /api/v2: it asserts the token
+// response, the HttpOnly refresh cookie, the no-store header, and the credential
+// and TOTP gates.
+func TestHumaLogin(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+
+ login := func(body string) *httptest.ResponseRecorder {
+ return humaRequest(t, e, http.MethodPost, "/api/v2/login", body, "", "")
+ }
+
+ t.Run("normal login", func(t *testing.T) {
+ rec := login(`{"username":"user1","password":"12345678"}`)
+ require.Equal(t, http.StatusOK, rec.Code, rec.Body.String())
+ assert.Contains(t, rec.Body.String(), `"token":"`)
+
+ assert.Equal(t, "no-store", rec.Header().Get("Cache-Control"))
+
+ cookie := refreshCookie(rec)
+ require.NotNil(t, cookie, "login must set the refresh-token cookie")
+ assert.NotEmpty(t, cookie.Value)
+ assert.True(t, cookie.HttpOnly, "refresh cookie must be HttpOnly")
+ })
+
+ t.Run("wrong password", func(t *testing.T) {
+ rec := login(`{"username":"user1","password":"wrong"}`)
+ assert.Equal(t, http.StatusForbidden, rec.Code)
+ assert.Equal(t, user.ErrCodeWrongUsernameOrPassword, problemCode(t, rec))
+ assert.Nil(t, refreshCookie(rec), "a failed login must not set a refresh cookie")
+ })
+
+ t.Run("nonexistent user", func(t *testing.T) {
+ rec := login(`{"username":"userWhichDoesNotExist","password":"12345678"}`)
+ assert.Equal(t, http.StatusForbidden, rec.Code)
+ assert.Equal(t, user.ErrCodeWrongUsernameOrPassword, problemCode(t, rec))
+ })
+
+ t.Run("unconfirmed email", func(t *testing.T) {
+ rec := login(`{"username":"user5","password":"12345678"}`)
+ assert.Equal(t, http.StatusPreconditionFailed, rec.Code)
+ assert.Equal(t, user.ErrCodeEmailNotConfirmed, problemCode(t, rec))
+ })
+
+ t.Run("disabled account", func(t *testing.T) {
+ rec := login(`{"username":"user17","password":"12345678"}`)
+ assert.Equal(t, http.StatusPreconditionFailed, rec.Code)
+ assert.Equal(t, user.ErrCodeAccountDisabled, problemCode(t, rec))
+ })
+
+ t.Run("locked account", func(t *testing.T) {
+ rec := login(`{"username":"user18","password":"12345678"}`)
+ assert.Equal(t, http.StatusPreconditionFailed, rec.Code)
+ assert.Equal(t, user.ErrCodeAccountLocked, problemCode(t, rec))
+ })
+
+ t.Run("TOTP required but missing", func(t *testing.T) {
+ rec := login(`{"username":"user10","password":"12345678"}`)
+ assert.Equal(t, http.StatusPreconditionFailed, rec.Code)
+ assert.Equal(t, user.ErrCodeInvalidTOTPPasscode, problemCode(t, rec))
+ })
+
+ t.Run("TOTP wrong", func(t *testing.T) {
+ rec := login(`{"username":"user10","password":"12345678","totp_passcode":"000000"}`)
+ assert.Equal(t, http.StatusPreconditionFailed, rec.Code)
+ assert.Equal(t, user.ErrCodeInvalidTOTPPasscode, problemCode(t, rec))
+ })
+
+ t.Run("TOTP correct", func(t *testing.T) {
+ code, err := totp.GenerateCode("JBSWY3DPEHPK3PXP", time.Now())
+ require.NoError(t, err)
+ rec := login(`{"username":"user10","password":"12345678","totp_passcode":"` + code + `"}`)
+ require.Equal(t, http.StatusOK, rec.Code, rec.Body.String())
+ assert.Contains(t, rec.Body.String(), `"token":"`)
+ assert.NotNil(t, refreshCookie(rec))
+ })
+}
+
+// TestHumaLogout proves the v2 logout deletes the session server-side and clears
+// the refresh-token cookie.
+func TestHumaLogout(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+
+ // Create a session so logout has something to delete, then mint a JWT whose
+ // sid claim points at it.
+ s := db.NewSession()
+ session, err := models.CreateSession(s, testuser1.ID, "test", "127.0.0.1", false)
+ require.NoError(t, err)
+ require.NoError(t, s.Commit())
+ require.NoError(t, s.Close())
+
+ token, err := auth.NewUserJWTAuthtoken(&testuser1, session.ID)
+ require.NoError(t, err)
+
+ rec := humaRequest(t, e, http.MethodPost, "/api/v2/logout", "", token, "")
+ require.Equal(t, http.StatusOK, rec.Code, rec.Body.String())
+ assert.Contains(t, rec.Body.String(), "Successfully logged out.")
+
+ cookie := refreshCookie(rec)
+ require.NotNil(t, cookie, "logout must clear the refresh cookie")
+ assert.Empty(t, cookie.Value, "cleared cookie has no value")
+ assert.Negative(t, cookie.MaxAge, "cleared cookie is expired")
+
+ // The session must be gone.
+ check := db.NewSession()
+ defer check.Close()
+ exists, err := check.Where("id = ?", session.ID).Exist(&models.Session{})
+ require.NoError(t, err)
+ assert.False(t, exists, "logout must delete the session")
+}
+
+// TestHumaLoginUnauthenticated proves login needs no token (it is a public op).
+func TestHumaLoginUnauthenticated(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+
+ rec := humaRequest(t, e, http.MethodPost, "/api/v2/login", `{"username":"user1","password":"12345678"}`, "", "")
+ require.Equal(t, http.StatusOK, rec.Code, rec.Body.String())
+}
+
+// TestHumaOpenIDGating proves the OIDC callback route only exists when OpenID is
+// enabled, mirroring the registrar gate.
+func TestHumaOpenIDGating(t *testing.T) {
+ body := `{"code":"abc","redirect_url":"https://example.com"}`
+
+ t.Run("disabled returns 404", func(t *testing.T) {
+ config.AuthOpenIDEnabled.Set(false)
+
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+
+ rec := humaRequest(t, e, http.MethodPost, "/api/v2/auth/openid/test/callback", body, "", "")
+ assert.Equal(t, http.StatusNotFound, rec.Code)
+ })
+
+ t.Run("enabled does not require auth", func(t *testing.T) {
+ config.AuthOpenIDEnabled.Set(true)
+ defer config.AuthOpenIDEnabled.Set(false)
+
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+
+ // No provider is configured, so the call fails downstream — but it must
+ // not 404 as an unknown route nor 401 for missing auth, which proves the
+ // public route is registered.
+ rec := humaRequest(t, e, http.MethodPost, "/api/v2/auth/openid/doesnotexist/callback", body, "", "")
+ assert.NotEqual(t, http.StatusNotFound, rec.Code)
+ assert.NotEqual(t, http.StatusUnauthorized, rec.Code)
+ })
+}
diff --git a/pkg/webtests/huma_auth_refresh_test.go b/pkg/webtests/huma_auth_refresh_test.go
new file mode 100644
index 000000000..48fad31c6
--- /dev/null
+++ b/pkg/webtests/huma_auth_refresh_test.go
@@ -0,0 +1,92 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package webtests
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "code.vikunja.io/api/pkg/modules/auth"
+
+ "github.com/labstack/echo/v5"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// refreshRequest posts to the v2 refresh endpoint with the given refresh-token
+// cookie value (empty value omits the cookie entirely), driving the full
+// echo+Huma stack so cookie reading and Set-Cookie writing are exercised.
+func refreshRequest(e *echo.Echo, refreshToken string) *httptest.ResponseRecorder {
+ req := httptest.NewRequest(http.MethodPost, "/api/v2/user/token/refresh", strings.NewReader(""))
+ req.Header.Set("Content-Type", "application/json")
+ if refreshToken != "" {
+ req.AddCookie(&http.Cookie{Name: auth.RefreshTokenCookieName, Value: refreshToken})
+ }
+ rec := httptest.NewRecorder()
+ e.ServeHTTP(rec, req)
+ return rec
+}
+
+// TestHumaRefreshToken ports the v1 refresh-token coverage to /api/v2: a valid
+// cookie yields a new JWT and a rotated HttpOnly cookie, the old token then stops
+// working, and missing/invalid cookies map to the same 401 v1 returns.
+func TestHumaRefreshToken(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+
+ t.Run("valid refresh token", func(t *testing.T) {
+ rec := refreshRequest(e, "testtoken_session1")
+ require.Equal(t, http.StatusOK, rec.Code, rec.Body.String())
+ assert.Contains(t, rec.Body.String(), `"token":"`)
+ assert.Equal(t, "no-store", rec.Header().Get("Cache-Control"))
+
+ cookie := refreshCookie(rec)
+ require.NotNil(t, cookie, "refresh must set a new refresh-token cookie")
+ assert.NotEmpty(t, cookie.Value)
+ assert.NotEqual(t, "testtoken_session1", cookie.Value, "refresh token must be rotated")
+ assert.True(t, cookie.HttpOnly, "refresh cookie must be HttpOnly")
+ })
+
+ t.Run("rotation invalidates the old token", func(t *testing.T) {
+ // session2 is a separate session so this case does not depend on the
+ // one above. The first refresh succeeds and rotates the token.
+ first := refreshRequest(e, "testtoken_session2")
+ require.Equal(t, http.StatusOK, first.Code, first.Body.String())
+ newCookie := refreshCookie(first)
+ require.NotNil(t, newCookie)
+
+ // Replaying the now-rotated token must fail.
+ replay := refreshRequest(e, "testtoken_session2")
+ assert.Equal(t, http.StatusUnauthorized, replay.Code)
+
+ // The freshly rotated token still works.
+ next := refreshRequest(e, newCookie.Value)
+ assert.Equal(t, http.StatusOK, next.Code, next.Body.String())
+ })
+
+ t.Run("missing cookie", func(t *testing.T) {
+ rec := refreshRequest(e, "")
+ assert.Equal(t, http.StatusUnauthorized, rec.Code)
+ })
+
+ t.Run("invalid cookie", func(t *testing.T) {
+ rec := refreshRequest(e, "garbage")
+ assert.Equal(t, http.StatusUnauthorized, rec.Code)
+ })
+}
diff --git a/pkg/webtests/huma_background_download_test.go b/pkg/webtests/huma_background_download_test.go
new file mode 100644
index 000000000..e4c542a26
--- /dev/null
+++ b/pkg/webtests/huma_background_download_test.go
@@ -0,0 +1,162 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package webtests
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "code.vikunja.io/api/pkg/config"
+ "code.vikunja.io/api/pkg/routes"
+
+ "github.com/labstack/echo/v5"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// getBackgroundRequest issues a GET against the background download route with an
+// optional If-Modified-Since header (humaRequest can't set arbitrary headers).
+func getBackgroundRequest(t *testing.T, e *echo.Echo, project, token, ifModifiedSince string) *httptest.ResponseRecorder {
+ t.Helper()
+ req := httptest.NewRequest(http.MethodGet, "/api/v2/projects/"+project+"/background", nil)
+ if token != "" {
+ req.Header.Set("Authorization", "Bearer "+token)
+ }
+ if ifModifiedSince != "" {
+ req.Header.Set("If-Modified-Since", ifModifiedSince)
+ }
+ rec := httptest.NewRecorder()
+ e.ServeHTTP(rec, req)
+ return rec
+}
+
+// TestHumaProjectBackgroundDownload covers GET /projects/{project}/background. The
+// fixture file row (project 35, background_file_id 1) carries no bytes, so the happy
+// path uploads a real background first (the "upload-then-download" pattern) before
+// fetching it back.
+func TestHumaProjectBackgroundDownload(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+
+ t.Run("Owner uploads then downloads the background", func(t *testing.T) {
+ // testuser1 owns project 1, which starts without a background.
+ body, contentType := multipartFileBody(t, "background", "bg.png", pngBytes(t))
+ up := uploadBackgroundRequest(t, e, "1", humaTokenFor(t, &testuser1), body, contentType)
+ require.Equal(t, http.StatusOK, up.Code, "upload body: %s", up.Body.String())
+
+ rec := getBackgroundRequest(t, e, "1", humaTokenFor(t, &testuser1), "")
+ require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
+ assert.Equal(t, "image/jpg", rec.Header().Get("Content-Type"))
+ assert.Equal(t, "no-cache", rec.Header().Get("Cache-Control"))
+ assert.NotEmpty(t, rec.Body.Bytes(), "the download must return the stored bytes")
+ })
+
+ t.Run("If-Modified-Since returns 304", func(t *testing.T) {
+ // The in-memory test storage reports a zero modtime, so any valid
+ // If-Modified-Since is not-before it and yields a 304.
+ body, contentType := multipartFileBody(t, "background", "bg.png", pngBytes(t))
+ up := uploadBackgroundRequest(t, e, "1", humaTokenFor(t, &testuser1), body, contentType)
+ require.Equal(t, http.StatusOK, up.Code, "upload body: %s", up.Body.String())
+
+ rec := getBackgroundRequest(t, e, "1", humaTokenFor(t, &testuser1), "Wed, 21 Oct 2015 07:28:00 GMT")
+ assert.Equal(t, http.StatusNotModified, rec.Code, "body: %s", rec.Body.String())
+ assert.Empty(t, rec.Body.Bytes(), "a 304 must not carry a body")
+ })
+
+ t.Run("Project without a background returns 404", func(t *testing.T) {
+ // testuser1 owns project 21, which has no background and isn't uploaded to
+ // by any other subtest (project 1 is, and subtests share this env).
+ rec := getBackgroundRequest(t, e, "21", humaTokenFor(t, &testuser1), "")
+ assert.Equal(t, http.StatusNotFound, rec.Code, "body: %s", rec.Body.String())
+ })
+
+ t.Run("Read-only user may download", func(t *testing.T) {
+ // testuser6 owns project 35 and uploads a real background; testuser15 has
+ // read-only access, which CanRead allows for the download. Uploading first
+ // gives the file real bytes (the fixture row has none).
+ body, contentType := multipartFileBody(t, "background", "bg.png", pngBytes(t))
+ up := uploadBackgroundRequest(t, e, "35", humaTokenFor(t, &testuser6), body, contentType)
+ require.Equal(t, http.StatusOK, up.Code, "upload body: %s", up.Body.String())
+
+ rec := getBackgroundRequest(t, e, "35", humaTokenFor(t, &testuser15), "")
+ require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
+ assert.NotEmpty(t, rec.Body.Bytes(), "the read-only user must receive the bytes")
+ })
+
+ t.Run("No access at all is forbidden", func(t *testing.T) {
+ // testuser1 has no access to project 35.
+ rec := getBackgroundRequest(t, e, "35", humaTokenFor(t, &testuser1), "")
+ assert.Equal(t, http.StatusForbidden, rec.Code, "body: %s", rec.Body.String())
+ })
+
+ t.Run("Unauthenticated", func(t *testing.T) {
+ rec := getBackgroundRequest(t, e, "35", "", "")
+ assert.Equal(t, http.StatusUnauthorized, rec.Code, "body: %s", rec.Body.String())
+ })
+}
+
+// TestHumaProjectBackgroundDownloadDisabledByConfig verifies the download route is
+// absent (404) when project backgrounds are disabled.
+func TestHumaProjectBackgroundDownloadDisabledByConfig(t *testing.T) {
+ _, err := setupTestEnv()
+ require.NoError(t, err)
+
+ config.BackgroundsEnabled.Set(false)
+ defer config.BackgroundsEnabled.Set(true)
+
+ e := routes.NewEcho()
+ routes.RegisterRoutes(e)
+
+ rec := getBackgroundRequest(t, e, "35", humaTokenFor(t, &testuser6), "")
+ assert.Equal(t, http.StatusNotFound, rec.Code, "route must be absent when backgrounds are disabled; body: %s", rec.Body.String())
+}
+
+// TestHumaUnsplashProxy covers the Unsplash image/thumb proxy routes' gating and auth.
+// They only register when the unsplash provider is enabled (off by default), so the
+// router is rebuilt with the flag on. The proxy's happy path needs the live Unsplash
+// API and is therefore not covered here, matching v1 (which has no proxy tests).
+func TestHumaUnsplashProxy(t *testing.T) {
+ _, err := setupTestEnv()
+ require.NoError(t, err)
+
+ t.Run("Routes absent when unsplash is disabled", func(t *testing.T) {
+ // Unsplash is disabled by default; the proxy routes must not exist.
+ e := routes.NewEcho()
+ routes.RegisterRoutes(e)
+
+ rec := humaRequest(t, e, http.MethodGet, "/api/v2/backgrounds/unsplash/images/abc", "", humaTokenFor(t, &testuser1), "")
+ assert.Equal(t, http.StatusNotFound, rec.Code, "image proxy must be absent when unsplash is disabled; body: %s", rec.Body.String())
+
+ rec = humaRequest(t, e, http.MethodGet, "/api/v2/backgrounds/unsplash/images/abc/thumb", "", humaTokenFor(t, &testuser1), "")
+ assert.Equal(t, http.StatusNotFound, rec.Code, "thumb proxy must be absent when unsplash is disabled; body: %s", rec.Body.String())
+ })
+
+ t.Run("Proxies require auth when unsplash is enabled", func(t *testing.T) {
+ config.BackgroundsUnsplashEnabled.Set(true)
+ defer config.BackgroundsUnsplashEnabled.Set(false)
+
+ e := routes.NewEcho()
+ routes.RegisterRoutes(e)
+
+ rec := humaRequest(t, e, http.MethodGet, "/api/v2/backgrounds/unsplash/images/abc", "", "", "")
+ assert.Equal(t, http.StatusUnauthorized, rec.Code, "image proxy body: %s", rec.Body.String())
+
+ rec = humaRequest(t, e, http.MethodGet, "/api/v2/backgrounds/unsplash/images/abc/thumb", "", "", "")
+ assert.Equal(t, http.StatusUnauthorized, rec.Code, "thumb proxy body: %s", rec.Body.String())
+ })
+}
diff --git a/pkg/webtests/huma_non_crud_aliases_test.go b/pkg/webtests/huma_non_crud_aliases_test.go
new file mode 100644
index 000000000..1377507cd
--- /dev/null
+++ b/pkg/webtests/huma_non_crud_aliases_test.go
@@ -0,0 +1,151 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package webtests
+
+import (
+ "encoding/json"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "github.com/labstack/echo/v5"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// feedsTokenUser13 is a feeds-scoped API token for user 13 (see the feeds
+// fixtures); it authenticates the v2 notifications Atom feed via HTTP Basic.
+const feedsTokenUser13 = "tk_feeds_access_token_user_0013_feed0013"
+
+// TestHumaNonCRUDAliases covers the three non-REST endpoints mounted under
+// /api/v2. Health and the Atom feed are Huma operations (so they appear in the
+// OpenAPI spec); the WebSocket upgrade stays a raw echo route (OpenAPI can't
+// model WebSockets). Each authenticates itself, so the group's JWT middleware
+// must let them through.
+func TestHumaNonCRUDAliases(t *testing.T) {
+ t.Run("health is public and returns OK", func(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+
+ rec := humaRequest(t, e, http.MethodGet, "/api/v2/health", "", "", "")
+ require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
+ assert.Contains(t, rec.Body.String(), "OK")
+ })
+
+ t.Run("ws is reachable without a JWT", func(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+
+ // A plain GET without the upgrade headers makes websocket.Accept reject
+ // the request (typically 400). The point is that it reaches the handler
+ // at all — not a 401 from the JWT middleware nor a 404 for an unmounted
+ // route.
+ rec := humaRequest(t, e, http.MethodGet, "/api/v2/ws", "", "", "")
+ assert.NotEqual(t, http.StatusUnauthorized, rec.Code, "ws must not be blocked by v2 JWT auth")
+ assert.NotEqual(t, http.StatusNotFound, rec.Code, "ws must be mounted under /api/v2")
+ })
+
+ t.Run("atom feed is basic-auth-gated, not JWT-gated", func(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+
+ t.Run("without credentials returns a basic-auth challenge", func(t *testing.T) {
+ req := httptest.NewRequest(http.MethodGet, "/api/v2/notifications.atom", nil)
+ rec := httptest.NewRecorder()
+ e.ServeHTTP(rec, req)
+
+ // The JWT middleware skips this path, so the handler's own HTTP Basic
+ // auth gates it instead: a 401 carrying a Basic challenge, not the JWT
+ // middleware's JSON error.
+ require.Equal(t, http.StatusUnauthorized, rec.Code)
+ assert.Contains(t, strings.ToLower(rec.Header().Get(echo.HeaderWWWAuthenticate)), "basic",
+ "expected a Basic auth challenge, got %q", rec.Header().Get(echo.HeaderWWWAuthenticate))
+ })
+
+ t.Run("with a feeds API token returns an atom feed", func(t *testing.T) {
+ req := httptest.NewRequest(http.MethodGet, "/api/v2/notifications.atom", nil)
+ req.SetBasicAuth("user13", feedsTokenUser13)
+ rec := httptest.NewRecorder()
+ e.ServeHTTP(rec, req)
+
+ require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
+ assert.True(t, strings.HasPrefix(rec.Header().Get(echo.HeaderContentType), "application/atom+xml"),
+ "expected atom content type, got %q", rec.Header().Get(echo.HeaderContentType))
+ assert.Contains(t, rec.Body.String(), ".
+
+package webtests
+
+import (
+ "encoding/json"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "code.vikunja.io/api/pkg/config"
+ "code.vikunja.io/api/pkg/db"
+ "code.vikunja.io/api/pkg/events"
+ "code.vikunja.io/api/pkg/files"
+ "code.vikunja.io/api/pkg/license"
+ "code.vikunja.io/api/pkg/log"
+ "code.vikunja.io/api/pkg/models"
+ "code.vikunja.io/api/pkg/modules/keyvalue"
+ "code.vikunja.io/api/pkg/modules/migration"
+ "code.vikunja.io/api/pkg/routes"
+ "code.vikunja.io/api/pkg/user"
+
+ "github.com/labstack/echo/v5"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "src.techknowlogick.com/xormigrate"
+)
+
+const testingToken = "test-testing-token"
+
+// setupTestingEnv mirrors setupTestEnv but sets the testing token before
+// registering routes, so the config-gated /api/v2/test/* endpoints mount.
+// When token is empty the endpoints stay unmounted (the disabled case).
+func setupTestingEnv(t *testing.T, token string) *echo.Echo {
+ t.Helper()
+ config.InitDefaultConfig()
+ config.ServicePublicURL.Set("https://localhost")
+ config.ServiceTestingtoken.Set(token)
+ t.Cleanup(func() { config.ServiceTestingtoken.Set("") })
+
+ log.InitLogger()
+ files.InitTests()
+ user.InitTests()
+ models.SetupTests()
+ events.Fake()
+ keyvalue.InitStorage()
+
+ // models.SetupTests only syncs models + notifications tables, but
+ // TruncateAllTables walks *every* registered table — including ones created
+ // by migration in production (license_status, migration_status) plus
+ // xormigrate's "migration" tracking table. Create them here so truncate-all
+ // doesn't hit "no such table" (the same gap that kept v1 from testing it).
+ engine, err := db.CreateTestEngine()
+ require.NoError(t, err)
+ extraTables := append(append([]any{new(xormigrate.Migration)}, license.GetTables()...), migration.GetTables()...)
+ require.NoError(t, engine.Sync2(extraTables...))
+
+ require.NoError(t, db.LoadFixtures())
+
+ e := routes.NewEcho()
+ routes.RegisterRoutes(e)
+ return e
+}
+
+// testingRequest dispatches a request to a /api/v2/test/* endpoint, sending the
+// raw token in the Authorization header (not a Bearer JWT).
+func testingRequest(e *echo.Echo, method, path, body, token string) *httptest.ResponseRecorder {
+ req := httptest.NewRequest(method, path, strings.NewReader(body))
+ req.Header.Set("Content-Type", "application/json")
+ if token != "" {
+ req.Header.Set("Authorization", token)
+ }
+ rec := httptest.NewRecorder()
+ e.ServeHTTP(rec, req)
+ return rec
+}
+
+func countRows(t *testing.T, table string) int {
+ t.Helper()
+ s := db.NewSession()
+ defer s.Close()
+ rows := []map[string]interface{}{}
+ require.NoError(t, s.Table(table).Find(&rows))
+ return len(rows)
+}
+
+func TestTesting(t *testing.T) {
+ t.Run("replace table contents", func(t *testing.T) {
+ e := setupTestingEnv(t, testingToken)
+ t.Cleanup(func() { _ = db.LoadFixtures() })
+
+ body := `[{"id":1,"title":"only label","created_by_id":1,"created":"2020-01-01 00:00:00","updated":"2020-01-01 00:00:00"}]`
+ rec := testingRequest(e, http.MethodPut, "/api/v2/test/labels", body, testingToken)
+ require.Equal(t, http.StatusCreated, rec.Code, "body: %s", rec.Body.String())
+
+ var data []map[string]any
+ require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &data))
+ require.Len(t, data, 1)
+ assert.EqualValues(t, "only label", data[0]["title"])
+ assert.Equal(t, 1, countRows(t, "labels"), "table should hold exactly the seeded rows")
+ })
+
+ t.Run("replace without truncate keeps existing rows", func(t *testing.T) {
+ e := setupTestingEnv(t, testingToken)
+ t.Cleanup(func() { _ = db.LoadFixtures() })
+
+ before := countRows(t, "labels")
+ require.Positive(t, before, "fixtures should seed some labels")
+
+ body := `[{"id":9999,"title":"added label","created_by_id":1,"created":"2020-01-01 00:00:00","updated":"2020-01-01 00:00:00"}]`
+ rec := testingRequest(e, http.MethodPut, "/api/v2/test/labels?truncate=false", body, testingToken)
+ require.Equal(t, http.StatusCreated, rec.Code, "body: %s", rec.Body.String())
+
+ assert.Equal(t, before+1, countRows(t, "labels"), "row should be added on top of existing data")
+ })
+
+ t.Run("truncate all tables", func(t *testing.T) {
+ e := setupTestingEnv(t, testingToken)
+ t.Cleanup(func() { _ = db.LoadFixtures() })
+
+ require.Positive(t, countRows(t, "labels"))
+
+ rec := testingRequest(e, http.MethodDelete, "/api/v2/test/all", "", testingToken)
+ require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
+
+ var resp struct {
+ Message string `json:"message"`
+ }
+ require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
+ assert.Equal(t, "ok", resp.Message)
+ assert.Equal(t, 0, countRows(t, "labels"), "every table should be empty after truncate")
+ })
+
+ t.Run("wrong token is forbidden", func(t *testing.T) {
+ e := setupTestingEnv(t, testingToken)
+
+ rec := testingRequest(e, http.MethodPut, "/api/v2/test/labels", `[]`, "wrong-token")
+ assert.Equal(t, http.StatusForbidden, rec.Code, "body: %s", rec.Body.String())
+
+ rec = testingRequest(e, http.MethodDelete, "/api/v2/test/all", "", "wrong-token")
+ assert.Equal(t, http.StatusForbidden, rec.Code, "body: %s", rec.Body.String())
+ })
+
+ t.Run("missing token is forbidden", func(t *testing.T) {
+ e := setupTestingEnv(t, testingToken)
+
+ rec := testingRequest(e, http.MethodPut, "/api/v2/test/labels", `[]`, "")
+ assert.Equal(t, http.StatusForbidden, rec.Code, "body: %s", rec.Body.String())
+
+ rec = testingRequest(e, http.MethodDelete, "/api/v2/test/all", "", "")
+ assert.Equal(t, http.StatusForbidden, rec.Code, "body: %s", rec.Body.String())
+ })
+}
+
+func TestTesting_DisabledConfig(t *testing.T) {
+ e := setupTestingEnv(t, "")
+
+ rec := testingRequest(e, http.MethodPut, "/api/v2/test/labels", `[]`, "")
+ assert.Equal(t, http.StatusNotFound, rec.Code, "endpoint must be absent when no testing token is configured")
+
+ rec = testingRequest(e, http.MethodDelete, "/api/v2/test/all", "", "")
+ assert.Equal(t, http.StatusNotFound, rec.Code, "endpoint must be absent when no testing token is configured")
+}
+
+func TestTesting_BodySchemaIsArrayOfObjects(t *testing.T) {
+ e := setupTestingEnv(t, testingToken)
+
+ req := httptest.NewRequest(http.MethodGet, "/api/v2/openapi.json", nil)
+ rec := httptest.NewRecorder()
+ e.ServeHTTP(rec, req)
+ require.Equal(t, http.StatusOK, rec.Code)
+
+ var spec map[string]any
+ require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &spec))
+
+ paths, _ := spec["paths"].(map[string]any)
+ op, _ := paths["/test/{table}"].(map[string]any)
+ put, ok := op["put"].(map[string]any)
+ require.True(t, ok, "PUT /test/{table} must be in the spec")
+
+ reqBody, _ := put["requestBody"].(map[string]any)
+ content, _ := reqBody["content"].(map[string]any)
+ appJSON, _ := content["application/json"].(map[string]any)
+ schema, _ := appJSON["schema"].(map[string]any)
+ // FieldsOptionalByDefault makes the array nullable, so `type` may be the
+ // string "array" or the list ["array","null"]. Either is honest; assert it
+ // describes an array (not, say, a base64 string as json.RawMessage would).
+ assert.Contains(t, schemaTypes(schema["type"]), "array", "request body must be modeled as an array")
+}
+
+// schemaTypes normalises an OpenAPI `type` value (a string or a list of
+// strings when nullable) into a slice for assertion.
+func schemaTypes(v any) []string {
+ switch t := v.(type) {
+ case string:
+ return []string{t}
+ case []any:
+ out := make([]string, 0, len(t))
+ for _, e := range t {
+ if s, ok := e.(string); ok {
+ out = append(out, s)
+ }
+ }
+ return out
+ default:
+ return nil
+ }
+}
diff --git a/pkg/webtests/huma_user_export_test.go b/pkg/webtests/huma_user_export_test.go
new file mode 100644
index 000000000..ee9104d5b
--- /dev/null
+++ b/pkg/webtests/huma_user_export_test.go
@@ -0,0 +1,126 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package webtests
+
+import (
+ "bytes"
+ "net/http"
+ "testing"
+
+ "code.vikunja.io/api/pkg/files"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// TestHumaUserExport covers the v2 data-export endpoints. Fixture topology
+// (pkg/db/fixtures/users.yml + files.yml):
+// - user1: local, password 12345678, export_file_id 1 (file row exists, no bytes).
+// - user14: non-local (OIDC), no password to confirm.
+// - user15: local, no export.
+func TestHumaUserExport(t *testing.T) {
+ t.Run("Request with the correct password", func(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+ rec := humaRequest(t, e, http.MethodPost, "/api/v2/user/export/request",
+ `{"password":"12345678"}`, humaTokenFor(t, &testuser1), "")
+ require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
+ assert.Contains(t, rec.Body.String(), "requested data export")
+ })
+
+ t.Run("Request with a wrong password is refused", func(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+ rec := humaRequest(t, e, http.MethodPost, "/api/v2/user/export/request",
+ `{"password":"wrong-password"}`, humaTokenFor(t, &testuser1), "")
+ require.NotEqual(t, http.StatusOK, rec.Code,
+ "a wrong password must not start an export; body: %s", rec.Body.String())
+ })
+
+ t.Run("Request as a non-local user skips the password", func(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+ rec := humaRequest(t, e, http.MethodPost, "/api/v2/user/export/request",
+ `{}`, humaTokenFor(t, &testuser14), "")
+ require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
+ })
+
+ t.Run("Download streams the export bytes", func(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+ // user1's export points at file 1; setupTestEnv resets storage, so write
+ // real bytes for it (size matches the fixture's declared 100 bytes).
+ content := bytes.Repeat([]byte("v"), 100)
+ require.NoError(t, (&files.File{ID: 1, Size: uint64(len(content))}).Save(bytes.NewReader(content)))
+
+ rec := humaRequest(t, e, http.MethodPost, "/api/v2/user/export/download",
+ `{"password":"12345678"}`, humaTokenFor(t, &testuser1), "")
+ require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
+ assert.Equal(t, content, rec.Body.Bytes(), "the streamed export bytes must match")
+ assert.Contains(t, rec.Header().Get("Content-Disposition"), "test")
+ assert.Equal(t, "no-cache", rec.Header().Get("Cache-Control"), "downloads must never be cached")
+ })
+
+ t.Run("Download with a wrong password is refused", func(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+ rec := humaRequest(t, e, http.MethodPost, "/api/v2/user/export/download",
+ `{"password":"wrong-password"}`, humaTokenFor(t, &testuser1), "")
+ require.NotEqual(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
+ })
+
+ t.Run("Download without an export returns 404", func(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+ rec := humaRequest(t, e, http.MethodPost, "/api/v2/user/export/download",
+ `{"password":"12345678"}`, humaTokenFor(t, &testuser15), "")
+ assert.Equal(t, http.StatusNotFound, rec.Code, "body: %s", rec.Body.String())
+ })
+
+ t.Run("Download with a missing physical file returns 404", func(t *testing.T) {
+ // user1 has export_file_id 1, but setupTestEnv leaves its bytes unwritten.
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+ rec := humaRequest(t, e, http.MethodPost, "/api/v2/user/export/download",
+ `{"password":"12345678"}`, humaTokenFor(t, &testuser1), "")
+ assert.Equal(t, http.StatusNotFound, rec.Code, "body: %s", rec.Body.String())
+ })
+
+ t.Run("Status returns the export metadata", func(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+ rec := humaRequest(t, e, http.MethodGet, "/api/v2/user/export", "", humaTokenFor(t, &testuser1), "")
+ require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
+ assert.Contains(t, rec.Body.String(), `"id":1`)
+ assert.Contains(t, rec.Body.String(), `"expires"`)
+ })
+
+ t.Run("Status without an export returns null", func(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+ rec := humaRequest(t, e, http.MethodGet, "/api/v2/user/export", "", humaTokenFor(t, &testuser15), "")
+ require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
+ assert.JSONEq(t, "null", rec.Body.String())
+ })
+
+ t.Run("Unauthenticated request is rejected", func(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+ rec := humaRequest(t, e, http.MethodGet, "/api/v2/user/export", "", "", "")
+ assert.Equal(t, http.StatusUnauthorized, rec.Code, "body: %s", rec.Body.String())
+ })
+}
diff --git a/pkg/webtests/huma_user_totp_test.go b/pkg/webtests/huma_user_totp_test.go
index d5cc82f15..df244a23c 100644
--- a/pkg/webtests/huma_user_totp_test.go
+++ b/pkg/webtests/huma_user_totp_test.go
@@ -34,8 +34,7 @@ import (
var testuser14 = user.User{ID: 14, Username: "user14", Issuer: "https://some.service.com"}
// TestHumaTOTP mirrors v1's TestUserTOTPLocalUser and adds the enable/disable
-// flows plus the local-account-only guard. The QR-code endpoint is not ported
-// to v2 (binary streaming, later wave), so there is no test for it here.
+// flows, the qr-code blob endpoint, and the local-account-only guard.
//
// Fixture topology (pkg/db/fixtures/totp.yml + users.yml):
// - user1: totp enrolled, not enabled (secret HXDMVJEC…).
@@ -59,6 +58,15 @@ func TestHumaTOTP(t *testing.T) {
require.Equal(t, http.StatusPreconditionFailed, rec.Code, "body: %s", rec.Body.String())
})
+ t.Run("Get qr code for enrolled user", func(t *testing.T) {
+ e, err := setupTestEnv()
+ require.NoError(t, err)
+ rec := humaRequest(t, e, http.MethodGet, "/api/v2/user/settings/totp/qrcode", "", humaTokenFor(t, &testuser1), "")
+ require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
+ assert.Equal(t, "image/jpeg", rec.Header().Get("Content-Type"))
+ assert.NotEmpty(t, rec.Body.Bytes(), "the qr code jpeg must have bytes")
+ })
+
t.Run("Enroll a fresh user", func(t *testing.T) {
e, err := setupTestEnv()
require.NoError(t, err)
@@ -123,6 +131,7 @@ func TestHumaTOTP(t *testing.T) {
method, path, body string
}{
{http.MethodGet, "/api/v2/user/settings/totp", ""},
+ {http.MethodGet, "/api/v2/user/settings/totp/qrcode", ""},
{http.MethodPost, "/api/v2/user/settings/totp/enroll", ""},
{http.MethodPost, "/api/v2/user/settings/totp/enable", `{"passcode":"000000"}`},
{http.MethodPost, "/api/v2/user/settings/totp/disable", `{"password":"12345678"}`},
diff --git a/pkg/webtests/login_test.go b/pkg/webtests/login_test.go
index f271e1727..17b0f9b07 100644
--- a/pkg/webtests/login_test.go
+++ b/pkg/webtests/login_test.go
@@ -68,6 +68,22 @@ func TestLogin(t *testing.T) {
require.Error(t, err)
assertHandlerErrorCode(t, err, user.ErrCodeEmailNotConfirmed)
})
+ t.Run("disabled account", func(t *testing.T) {
+ _, err := newTestRequest(t, http.MethodPost, apiv1.Login, `{
+ "username": "user17",
+ "password": "12345678"
+}`, nil, nil)
+ require.Error(t, err)
+ assertHandlerErrorCode(t, err, user.ErrCodeAccountDisabled)
+ })
+ t.Run("locked account", func(t *testing.T) {
+ _, err := newTestRequest(t, http.MethodPost, apiv1.Login, `{
+ "username": "user18",
+ "password": "12345678"
+}`, nil, nil)
+ require.Error(t, err)
+ assertHandlerErrorCode(t, err, user.ErrCodeAccountLocked)
+ })
}
func TestLoginTOTPLockout(t *testing.T) {