diff --git a/package-lock.json b/package-lock.json index 8b46cdfee9..1fa7295c8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10524,6 +10524,40 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/@emnapi/core": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz", + "integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", + "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.3", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz", @@ -13205,6 +13239,19 @@ "node": ">= 10" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.5.tgz", + "integrity": "sha512-TBr9Cf9onSAS2LQ2+QHx6XcC6h9+RIzJgbqG3++9TUZSH204AwEy5jg3BTQ0VATsyoGj4ee49tN/y6rvaOOtcg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@tybys/wasm-util": "^0.10.1" + } + }, "node_modules/@nicolo-ribaudo/chokidar-2": { "version": "2.1.8-no-fsevents.3", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", @@ -14904,6 +14951,275 @@ "resolved": "services/web", "link": true }, + "node_modules/@oxc-resolver/binding-android-arm-eabi": { + "version": "11.8.4", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.8.4.tgz", + "integrity": "sha512-6BjMji0TcvQfJ4EoSunOSyu/SiyHKficBD0V3Y0NxF0beaNnnZ7GYEi2lHmRNnRCuIPK8IuVqQ6XizYau+CkKw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-android-arm64": { + "version": "11.8.4", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.8.4.tgz", + "integrity": "sha512-SxF4X6rzCBS9XNPXKZGoIHIABjfGmtQpEgRBDzpDHx5VTuLAUmwLTHXnVBAZoX5bmnhF79RiMElavzFdJ2cA1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-arm64": { + "version": "11.8.4", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.8.4.tgz", + "integrity": "sha512-8zWeERrzgscAniE6kh1TQ4E7GJyglYsvdoKrHYLBCbHWD+0/soffiwAYxZuckKEQSc2RXMSPjcu+JTCALaY0Dw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-x64": { + "version": "11.8.4", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.8.4.tgz", + "integrity": "sha512-BUwggKz8Hi5uEQ0AeVTSun1+sp4lzNcItn+L7fDsHu5Cx0Zueuo10BtVm+dIwmYVVPL5oGYOeD0fS7MKAazKiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-freebsd-x64": { + "version": "11.8.4", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.8.4.tgz", + "integrity": "sha512-fPO5TQhnn8gA6yP4o49lc4Gn8KeDwAp9uYd4PlE3Q00JVqU6cY9WecDhYHrWtiFcyoZ8UVBlIxuhRqT/DP4Z4A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { + "version": "11.8.4", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.8.4.tgz", + "integrity": "sha512-QuNbdUaVGiP0W0GrXsvCDZjqeL4lZGU7aXlx/S2tCvyTk3wh6skoiLJgqUf/eeqXfUPnzTfntYqyfolzCAyBYA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { + "version": "11.8.4", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.8.4.tgz", + "integrity": "sha512-p/zLMfza8OsC4BDKxqeZ9Qel+4eA/oiMSyKLRkMrTgt6OWQq1d5nHntjfG35Abcw4ev6Q9lRU3NOW5hj0xlUbw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { + "version": "11.8.4", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.8.4.tgz", + "integrity": "sha512-bvJF9wWxF1+a5YZATlS5JojpOMC7OsnTatA6sXVHoOb7MIigjledYB5ZMAeRrnWWexRMiEX3YSaA46oSfOzmOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-musl": { + "version": "11.8.4", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.8.4.tgz", + "integrity": "sha512-gf4nwGBfu+EFwOn5p7/T7VF4jmIdfodwJS9MRkOBHvuAm3LQgCX7O6d3Y80mm0TV7ZMRD/trfW628rHfd5++vQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { + "version": "11.8.4", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.8.4.tgz", + "integrity": "sha512-T120R5GIzRd41rYWWKCI6cSYrZjmRQzf3X4xeE1WX396Uabz5DX8KU7RnVHihSK+KDxccCVOFBxcH3ITd+IEpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { + "version": "11.8.4", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.8.4.tgz", + "integrity": "sha512-PVG7SxBFFjAaQ76p9O/0Xt5mTBlziRwpck+6cRNhy/hbWY/hSt8BFfPqw0EDSfnl40Uuh+NPsHFMnaWWyxbQEg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { + "version": "11.8.4", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.8.4.tgz", + "integrity": "sha512-L0OklUhM2qLGaKvPSyKmwWpoijfc++VJtPyVgz031ShOXyo0WjD0ZGzusyJMsA1a/gdulAmN6CQ/0Sf4LGXEcw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { + "version": "11.8.4", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.8.4.tgz", + "integrity": "sha512-18Ajz5hqO4cRGuoHzLFUsIPod9GIaIRDiXFg2m6CS3NgVdHx7iCZscplYH7KtjdE42M8nGWYMyyq5BOk7QVgPw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-gnu": { + "version": "11.8.4", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.8.4.tgz", + "integrity": "sha512-uHvH4RyYBdQ/lFGV9H+R1ScHg6EBnAhE3mnX+u+mO/btnalvg7j80okuHf8Qw0tLQiP5P1sEBoVeE6zviXY9IA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-musl": { + "version": "11.8.4", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.8.4.tgz", + "integrity": "sha512-X5z44qh5DdJfVhcqXAQFTDFUpcxdpf6DT/lHL5CFcdQGIZxatjc7gFUy05IXPI9xwfq39RValjJBvFovUk9XBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi": { + "version": "11.8.4", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.8.4.tgz", + "integrity": "sha512-z3906y+cd8RRhBGNwHRrRAFxnKjXsBeL3+rdQjZpBrUyrhhsaV5iKD/ROx64FNJ9GjL/9mfon8A5xx/McYIqHA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { + "version": "11.8.4", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.8.4.tgz", + "integrity": "sha512-70vXFs74uA3X5iYOkpclbkWlQEF+MI325uAQ+Or2n8HJip2T0SEmuBlyw/sRL2E8zLC4oocb+1g25fmzlDVkmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxc-resolver/binding-win32-ia32-msvc": { + "version": "11.8.4", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-11.8.4.tgz", + "integrity": "sha512-SEOUAzTvr+nyMia3nx1dMtD7YUxZwuhQ3QAPnxy21261Lj0yT3JY4EIfwWH54lAWWfMdRSRRMFuGeF/dq7XjEw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxc-resolver/binding-win32-x64-msvc": { + "version": "11.8.4", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.8.4.tgz", + "integrity": "sha512-1gARIQsOPOU7LJ7jvMyPmZEVMapL/PymeG3J7naOdLZDrIZKX6CTvgawJmETYKt+8icP8M6KbBinrVkKVqFd+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@paralleldrive/cuid2": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", @@ -18830,6 +19146,17 @@ "node": ">=10.13.0" } }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", @@ -19386,12 +19713,12 @@ } }, "node_modules/@types/node": { - "version": "24.3.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.1.tgz", - "integrity": "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==", + "version": "24.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", + "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", "license": "MIT", "dependencies": { - "undici-types": "~7.10.0" + "undici-types": "~7.12.0" } }, "node_modules/@types/node-fetch": { @@ -19413,9 +19740,9 @@ } }, "node_modules/@types/node/node_modules/undici-types": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", - "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", + "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", "license": "MIT" }, "node_modules/@types/normalize-package-data": { @@ -30562,6 +30889,42 @@ "node": ">=0.4.x" } }, + "node_modules/formatly": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/formatly/-/formatly-0.3.0.tgz", + "integrity": "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fd-package-json": "^2.0.0" + }, + "bin": { + "formatly": "bin/index.mjs" + }, + "engines": { + "node": ">=18.3.0" + } + }, + "node_modules/formatly/node_modules/fd-package-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz", + "integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "walk-up-path": "^4.0.0" + } + }, + "node_modules/formatly/node_modules/walk-up-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/formdata-node": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.3.2.tgz", @@ -34931,6 +35294,84 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/knip": { + "version": "5.64.1", + "resolved": "https://registry.npmjs.org/knip/-/knip-5.64.1.tgz", + "integrity": "sha512-80XnLsyeXuyxj1F4+NBtQFHxaRH0xWRw8EKwfQ6EkVZZ0bSz/kqqan08k/Qg8ajWsFPhFq+0S2RbLCBGIQtuOg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/webpro" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/knip" + } + ], + "license": "ISC", + "dependencies": { + "@nodelib/fs.walk": "^1.2.3", + "fast-glob": "^3.3.3", + "formatly": "^0.3.0", + "jiti": "^2.6.0", + "js-yaml": "^4.1.0", + "minimist": "^1.2.8", + "oxc-resolver": "^11.8.3", + "picocolors": "^1.1.1", + "picomatch": "^4.0.1", + "smol-toml": "^1.4.1", + "strip-json-comments": "5.0.2", + "zod": "^4.1.11" + }, + "bin": { + "knip": "bin/knip.js", + "knip-bun": "bin/knip-bun.js" + }, + "engines": { + "node": ">=18.18.0" + }, + "peerDependencies": { + "@types/node": ">=18", + "typescript": ">=5.0.4 <7" + } + }, + "node_modules/knip/node_modules/jiti": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.0.tgz", + "integrity": "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/knip/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/knip/node_modules/strip-json-comments": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.2.tgz", + "integrity": "sha512-4X2FR3UwhNUE9G49aIsJW5hRRR3GXGTBTZRMfv568O60ojM8HcWjV/VxAxCDW3SUND33O6ZY66ZuRcdkj73q2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/known-css-properties": { "version": "0.30.0", "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.30.0.tgz", @@ -37630,6 +38071,22 @@ "node": ">=0.10.0" } }, + "node_modules/napi-postinstall": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", + "integrity": "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, "node_modules/native-promise-only": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", @@ -38573,6 +39030,41 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oxc-resolver": { + "version": "11.8.4", + "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.8.4.tgz", + "integrity": "sha512-qpimS3tHHEf+kgESMAme+q+rj7aCzMya00u9YdKOKyX2o7q4lozjPo6d7ZTTi979KHEcVOPWdNTueAKdeNq72w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-resolver/binding-android-arm-eabi": "11.8.4", + "@oxc-resolver/binding-android-arm64": "11.8.4", + "@oxc-resolver/binding-darwin-arm64": "11.8.4", + "@oxc-resolver/binding-darwin-x64": "11.8.4", + "@oxc-resolver/binding-freebsd-x64": "11.8.4", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.8.4", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.8.4", + "@oxc-resolver/binding-linux-arm64-gnu": "11.8.4", + "@oxc-resolver/binding-linux-arm64-musl": "11.8.4", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.8.4", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.8.4", + "@oxc-resolver/binding-linux-riscv64-musl": "11.8.4", + "@oxc-resolver/binding-linux-s390x-gnu": "11.8.4", + "@oxc-resolver/binding-linux-x64-gnu": "11.8.4", + "@oxc-resolver/binding-linux-x64-musl": "11.8.4", + "@oxc-resolver/binding-wasm32-wasi": "11.8.4", + "@oxc-resolver/binding-win32-arm64-msvc": "11.8.4", + "@oxc-resolver/binding-win32-ia32-msvc": "11.8.4", + "@oxc-resolver/binding-win32-x64-msvc": "11.8.4" + } + }, "node_modules/p-event": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", @@ -44670,6 +45162,19 @@ "npm": ">= 3.0.0" } }, + "node_modules/smol-toml": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.4.2.tgz", + "integrity": "sha512-rInDH6lCNiEyn3+hH8KVGFdbjc099j47+OSgbMrfDYX1CmXLfdKd7qi6IfcWj2wFxvSVkuI46M+wPGYfEOEj6g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, "node_modules/snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -50694,9 +51199,9 @@ } }, "node_modules/zod": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz", - "integrity": "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.11.tgz", + "integrity": "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -52810,6 +53315,7 @@ "@types/express": "^4.17.23", "@types/mocha": "^9.1.0", "@types/mocha-each": "^2.0.4", + "@types/node": "^24.5.2", "@types/react": "^18.3.20", "@types/react-color": "^3.0.13", "@types/react-dom": "^18.3.6", @@ -52893,6 +53399,7 @@ "jscodeshift": "^17.0.0", "jsdom": "^19.0.0", "jsdom-global": "^3.0.2", + "knip": "^5.64.1", "match-sorter": "^6.2.0", "mathjax": "^3.2.2", "mediatr-ts": "^2.0.1", diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js index 99abdfb431..b90a098c98 100644 --- a/services/web/config/settings.defaults.js +++ b/services/web/config/settings.defaults.js @@ -978,7 +978,6 @@ module.exports = { tprFileViewRefreshButton: [], tprFileViewNotOriginalImporter: [], contactUsModal: [], - editorToolbarButtons: [], sourceEditorExtensions: [], sourceEditorComponents: [], pdfLogEntryHeaderActionComponents: [], diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index f9d31c4b05..5be4c50d05 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -101,7 +101,6 @@ "added_by_on": "", "adding": "", "additional_certificate": "", - "additional_licenses": "", "address_line_1": "", "address_second_line_optional": "", "adjust_column_width": "", @@ -334,7 +333,6 @@ "connection_lost_with_unsaved_changes": "", "contact_group_admin": "", "contact_sales": "", - "contact_support_to_change_group_subscription": "", "contact_us": "", "contacting_the_sales_team": "", "continue": "", diff --git a/services/web/frontend/js/features/editor-left-menu/components/settings-menu-dropdown.tsx b/services/web/frontend/js/features/editor-left-menu/components/settings-menu-dropdown.tsx deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/services/web/frontend/js/features/file-tree/util/safe-path.ts b/services/web/frontend/js/features/file-tree/util/safe-path.ts index 2228440298..fe96c127b4 100644 --- a/services/web/frontend/js/features/file-tree/util/safe-path.ts +++ b/services/web/frontend/js/features/file-tree/util/safe-path.ts @@ -58,17 +58,6 @@ prototype\ const MAX_PATH = 1024 // Maximum path length, in characters. This is fairly arbitrary. -export function clean(filename: string): string { - filename = filename.replace(BADCHAR_RX, '_') - // for BADFILE_RX replace any matches with an equal number of underscores - filename = filename.replace(BADFILE_RX, match => - new Array(match.length + 1).join('_') - ) - // replace blocked filenames 'prototype' with '@prototype' - filename = filename.replace(BLOCKEDFILE_RX, '@$1') - return filename -} - export function isCleanFilename(filename: string): boolean { return ( isAllowedLength(filename) && @@ -81,30 +70,6 @@ export function isBlockedFilename(filename: string): boolean { return BLOCKEDFILE_RX.test(filename) } -// returns whether a full path is 'clean' - e.g. is a full or relative path -// that points to a file, and each element passes the rules in 'isCleanFilename' -export function isCleanPath(path: string): boolean { - const elements = path.split('/') - - const lastElementIsEmpty = elements[elements.length - 1].length === 0 - if (lastElementIsEmpty) { - return false - } - - for (const element of Array.from(elements)) { - if (element.length > 0 && !isCleanFilename(element)) { - return false - } - } - - // check for a top-level reserved name - if (BLOCKEDFILE_RX.test(path.replace(/^\/?/, ''))) { - return false - } // remove leading slash if present - - return true -} - export function isAllowedLength(pathname: string): boolean { return pathname.length > 0 && pathname.length <= MAX_PATH } diff --git a/services/web/frontend/js/features/history/utils/wait-for.ts b/services/web/frontend/js/features/history/utils/wait-for.ts deleted file mode 100644 index 253fd1f276..0000000000 --- a/services/web/frontend/js/features/history/utils/wait-for.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { debugConsole } from '@/utils/debugging' - -export function waitFor( - testFunction: () => T, - timeout: number, - pollInterval = 500 -): Promise { - const iterationLimit = Math.floor(timeout / pollInterval) - let iterations = 0 - - return new Promise((resolve, reject) => { - const tryIteration = () => { - if (iterations > iterationLimit) { - const err = new Error( - `waiting too long, ${JSON.stringify({ timeout, pollInterval })}` - ) - debugConsole.error(err) - reject(err) - return - } - - iterations += 1 - const result = testFunction() - - if (result) { - resolve(result) - return - } - - setTimeout(tryIteration, pollInterval) - } - - tryIteration() - }) -} diff --git a/services/web/frontend/js/features/ide-react/editor/event-log.ts b/services/web/frontend/js/features/ide-react/editor/event-log.ts deleted file mode 100644 index bd65de621c..0000000000 --- a/services/web/frontend/js/features/ide-react/editor/event-log.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { debugConsole } from '@/utils/debugging' - -type EditorEvent = { type: string; meta: unknown; date: Date } - -// Record events and then do nothing with them. -export class EventLog { - private recentEvents: EditorEvent[] = [] - - pushEvent = (type: string, meta: unknown = {}) => { - debugConsole.log('event', type, meta) - this.recentEvents.push({ type, meta, date: new Date() }) - if (this.recentEvents.length > 100) { - return this.recentEvents.shift() - } - } -} diff --git a/services/web/frontend/js/features/ide-redesign/components/error-logs/old-error-pane.tsx b/services/web/frontend/js/features/ide-redesign/components/error-logs/old-error-pane.tsx deleted file mode 100644 index 7794747d30..0000000000 --- a/services/web/frontend/js/features/ide-redesign/components/error-logs/old-error-pane.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import PdfLogsViewer from '@/features/pdf-preview/components/pdf-logs-viewer' -import { PdfPreviewProvider } from '@/features/pdf-preview/components/pdf-preview-provider' - -export default function OldErrorPane() { - return ( - - - - ) -} diff --git a/services/web/frontend/js/features/ide-redesign/components/tooltip-promo.tsx b/services/web/frontend/js/features/ide-redesign/components/tooltip-promo.tsx index f5d21376b1..0ae88bf0bd 100644 --- a/services/web/frontend/js/features/ide-redesign/components/tooltip-promo.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/tooltip-promo.tsx @@ -6,6 +6,7 @@ import classNames from 'classnames' import { useCallback, useEffect } from 'react' import { Overlay, OverlayProps, Popover } from 'react-bootstrap' +/** @knipignore keep this file around even when there is no current promo using it */ export default function TooltipPromotion({ target, tutorialKey, diff --git a/services/web/frontend/js/features/monthly-texlive/rolling-compile-image-changed-alert.tsx b/services/web/frontend/js/features/monthly-texlive/rolling-compile-image-changed-alert.tsx index 170725be10..b2f1aa06a2 100644 --- a/services/web/frontend/js/features/monthly-texlive/rolling-compile-image-changed-alert.tsx +++ b/services/web/frontend/js/features/monthly-texlive/rolling-compile-image-changed-alert.tsx @@ -1,4 +1,4 @@ -import { useTutorial } from '@/shared/hooks/promotions/use-tutorial' +import useTutorial from '@/shared/hooks/promotions/use-tutorial' import { useEditorContext } from '@/shared/context/editor-context' import { useProjectContext } from '@/shared/context/project-context' import OLNotification from '@/shared/components/ol/ol-notification' diff --git a/services/web/frontend/js/features/project-list/components/dropdown/menu-item-button.tsx b/services/web/frontend/js/features/project-list/components/dropdown/menu-item-button.tsx deleted file mode 100644 index 200cdecf95..0000000000 --- a/services/web/frontend/js/features/project-list/components/dropdown/menu-item-button.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { ReactNode } from 'react' - -type MenuItemButtonProps = { - children: ReactNode - onClick?: (e?: React.MouseEvent) => void - className?: string - afterNode?: React.ReactNode -} - -export default function MenuItemButton({ - children, - onClick, - className, - afterNode, - ...buttonProps -}: MenuItemButtonProps) { - return ( -
  • - - {afterNode} -
  • - ) -} diff --git a/services/web/frontend/js/features/review-panel/components/track-changes-toggle.tsx b/services/web/frontend/js/features/review-panel/components/track-changes-toggle.tsx deleted file mode 100644 index efddc078f1..0000000000 --- a/services/web/frontend/js/features/review-panel/components/track-changes-toggle.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import OLFormSwitch from '@/shared/components/ol/ol-form-switch' - -type TrackChangesToggleProps = { - id: string - description: string - disabled: boolean - handleToggle: () => void - value: boolean -} - -function TrackChangesToggle({ - id, - description, - disabled, - handleToggle, - value, -}: TrackChangesToggleProps) { - return ( - - ) -} - -export default TrackChangesToggle diff --git a/services/web/frontend/js/features/settings/components/linking/enable-widget.tsx b/services/web/frontend/js/features/settings/components/linking/enable-widget.tsx index 68305d5bc0..c8fd68ba39 100644 --- a/services/web/frontend/js/features/settings/components/linking/enable-widget.tsx +++ b/services/web/frontend/js/features/settings/components/linking/enable-widget.tsx @@ -1,79 +1,11 @@ -import { ReactNode } from 'react' import { useTranslation } from 'react-i18next' import { sendMB } from '@/infrastructure/event-tracking' -import OLBadge from '@/shared/components/ol/ol-badge' import OLButton from '@/shared/components/ol/ol-button' function trackUpgradeClick() { sendMB('settings-upgrade-click') } -type EnableWidgetProps = { - logo: ReactNode - title: string - description: string - helpPath: string - helpTextOverride?: string - hasFeature?: boolean - isPremiumFeature?: boolean - statusIndicator?: ReactNode - children?: ReactNode - linked?: boolean - handleLinkClick: () => void - handleUnlinkClick: () => void - disabled?: boolean -} - -export function EnableWidget({ - logo, - title, - description, - helpPath, - helpTextOverride, - hasFeature, - isPremiumFeature, - statusIndicator, - linked, - handleLinkClick, - handleUnlinkClick, - children, - disabled, -}: EnableWidgetProps) { - const { t } = useTranslation() - const helpText = helpTextOverride || t('learn_more') - - return ( -
    -
    {logo}
    -
    -
    -

    {title}

    - {!hasFeature && isPremiumFeature && ( - {t('premium_feature')} - )} -
    -

    - {description}{' '} - - {helpText} - -

    - {children} - {hasFeature && statusIndicator} -
    -
    - -
    -
    - ) -} - type ActionButtonProps = { hasFeature?: boolean linked?: boolean @@ -128,5 +60,3 @@ export function ActionButton({ ) } } - -export default EnableWidget diff --git a/services/web/frontend/js/features/source-editor/components/table-generator/toolbar/column-width-modal/column-width.ts b/services/web/frontend/js/features/source-editor/components/table-generator/toolbar/column-width-modal/column-width.ts index f63110916f..45313b3e6e 100644 --- a/services/web/frontend/js/features/source-editor/components/table-generator/toolbar/column-width-modal/column-width.ts +++ b/services/web/frontend/js/features/source-editor/components/table-generator/toolbar/column-width-modal/column-width.ts @@ -27,19 +27,3 @@ type AbsoluteWidth = { } export type WidthSelection = PercentageWidth | CustomWidth | AbsoluteWidth - -export const isPercentageWidth = ( - width: WidthSelection -): width is PercentageWidth => { - return width.unit === '%' -} - -export const isAbsoluteWidth = ( - width: WidthSelection -): width is AbsoluteWidth => { - return (ABSOLUTE_UNITS as readonly string[]).includes(width.unit) -} - -export const isCustomWidth = (width: WidthSelection): width is CustomWidth => { - return width.unit === 'custom' -} diff --git a/services/web/frontend/js/features/source-editor/extensions/review-tooltip.ts b/services/web/frontend/js/features/source-editor/extensions/review-tooltip.ts index 57900b101f..1655a11e4a 100644 --- a/services/web/frontend/js/features/source-editor/extensions/review-tooltip.ts +++ b/services/web/frontend/js/features/source-editor/extensions/review-tooltip.ts @@ -21,10 +21,6 @@ export const addNewCommentRangeEffect = StateEffect.define>() export const removeNewCommentRangeEffect = StateEffect.define() -export const textSelectedEffect = StateEffect.define() - -export const removeReviewPanelTooltipEffect = StateEffect.define() - const mouseDownEffect = StateEffect.define() const mouseUpEffect = StateEffect.define() const mouseDownStateField = StateField.define({ diff --git a/services/web/frontend/js/features/source-editor/extensions/vertical-overflow.ts b/services/web/frontend/js/features/source-editor/extensions/vertical-overflow.ts index 873343c2bc..06525757a2 100644 --- a/services/web/frontend/js/features/source-editor/extensions/vertical-overflow.ts +++ b/services/web/frontend/js/features/source-editor/extensions/vertical-overflow.ts @@ -1,15 +1,8 @@ -import { - Extension, - Facet, - StateEffect, - StateField, - TransactionSpec, -} from '@codemirror/state' +import { Extension, Facet, StateEffect, StateField } from '@codemirror/state' import { Decoration, EditorView, ViewPlugin, - ViewUpdate, WidgetType, } from '@codemirror/view' @@ -222,30 +215,3 @@ const topPaddingDecoration = EditorView.decorations.compute( ]) } ) - -export function setVerticalOverflow(padding: VerticalPadding): TransactionSpec { - return { - effects: [setOverflowPaddingEffect.of(padding)], - } -} - -export function updateSetsVerticalOverflow(update: ViewUpdate): boolean { - return update.transactions.some(tr => { - return tr.effects.some(effect => effect.is(setOverflowPaddingEffect)) - }) -} - -export function updateChangesTopPadding(update: ViewUpdate): boolean { - return ( - update.state.field(overflowPaddingState).top !== - update.startState.field(overflowPaddingState).top - ) -} - -export function editorVerticalTopPadding(view: EditorView): number { - return view.state.field(overflowPaddingState, false)?.top ?? 0 -} - -export function editorOverflowPadding(view: EditorView) { - return view.state.field(overflowPaddingState, false) -} diff --git a/services/web/frontend/js/features/source-editor/extensions/visual/html-elements.ts b/services/web/frontend/js/features/source-editor/extensions/visual/html-elements.ts index c5ef5ed132..61f98577f6 100644 --- a/services/web/frontend/js/features/source-editor/extensions/visual/html-elements.ts +++ b/services/web/frontend/js/features/source-editor/extensions/visual/html-elements.ts @@ -88,11 +88,6 @@ const inlineElements = new Set([ export const isInlineElement = (node: Node): node is HTMLElement => inlineElements.has(node.nodeName) -const codeElements = new Set(['CODE', 'PRE']) - -export const isCodeElement = (node: Node): node is HTMLElement => - codeElements.has(node.nodeName) - const keepEmptyBlockElements = new Set(['TD', 'TH', 'CANVAS', 'DT', 'DD', 'HR']) export const shouldRemoveEmptyBlockElement = ( diff --git a/services/web/frontend/js/features/source-editor/hooks/use-codemirror-content-height.ts b/services/web/frontend/js/features/source-editor/hooks/use-codemirror-content-height.ts deleted file mode 100644 index 04a89f5f23..0000000000 --- a/services/web/frontend/js/features/source-editor/hooks/use-codemirror-content-height.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { EditorView } from '@codemirror/view' -import useCodeMirrorMeasurement from './use-codemirror-measurement' - -// view.contentHeight, which is measured for us by CodeMirror and is what the -// gutters use, is sometimes a pixel or so short of the full height of the -// editor content, which leaves a small gap at the bottom, so use the DOM -// scrollHeight property instead. -const measureContentHeight = (view: EditorView) => view.contentDOM.scrollHeight - -export default function useCodeMirrorContentHeight() { - return useCodeMirrorMeasurement('content-height', measureContentHeight) -} diff --git a/services/web/frontend/js/features/source-editor/hooks/use-codemirror-measurement.ts b/services/web/frontend/js/features/source-editor/hooks/use-codemirror-measurement.ts deleted file mode 100644 index f330a19af4..0000000000 --- a/services/web/frontend/js/features/source-editor/hooks/use-codemirror-measurement.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useCallback, useState } from 'react' -import { useCodeMirrorViewContext } from '../components/codemirror-context' -import { EditorView } from '@codemirror/view' -import useEventListener from '../../../shared/hooks/use-event-listener' - -export default function useCodeMirrorMeasurement( - key: string, - measure: (view: EditorView) => number -) { - const view = useCodeMirrorViewContext() - const [measurement, setMeasurement] = useState(() => measure(view)) - - useEventListener( - 'editor:geometry-change', - useCallback(() => { - view.requestMeasure({ - key, - read: () => measure(view), - write(value) { - // wrap the React state setter in a timeout so it doesn't run inside the CodeMirror update cycle - window.setTimeout(() => { - setMeasurement(value) - }) - }, - }) - }, [view, measure, key]) - ) - - return measurement -} diff --git a/services/web/frontend/js/features/source-editor/utils/file.ts b/services/web/frontend/js/features/source-editor/utils/file.ts index c3cb99cd19..1d04fb8b5a 100644 --- a/services/web/frontend/js/features/source-editor/utils/file.ts +++ b/services/web/frontend/js/features/source-editor/utils/file.ts @@ -44,7 +44,6 @@ function filterByType(type: 'file' | 'doc' | 'folder') { } export const filterFiles = filterByType('file') -export const filterDocs = filterByType('doc') export const filterFolders = filterByType('folder') const IMAGE_FILE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'pdf'] diff --git a/services/web/frontend/js/features/source-editor/utils/sub-view.ts b/services/web/frontend/js/features/source-editor/utils/sub-view.ts deleted file mode 100644 index 86bf2ff4ea..0000000000 --- a/services/web/frontend/js/features/source-editor/utils/sub-view.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { SubView } from '../../../../../types/review-panel/review-panel' - -export const isCurrentFileView = (view: SubView) => view === 'cur_file' -export const isOverviewView = (view: SubView) => view === 'overview' diff --git a/services/web/frontend/js/features/source-editor/utils/tree-operations/ancestors.ts b/services/web/frontend/js/features/source-editor/utils/tree-operations/ancestors.ts index e89fcd9069..c07ef9ad43 100644 --- a/services/web/frontend/js/features/source-editor/utils/tree-operations/ancestors.ts +++ b/services/web/frontend/js/features/source-editor/utils/tree-operations/ancestors.ts @@ -1,68 +1,8 @@ -import { ensureSyntaxTree, syntaxTree } from '@codemirror/language' +import { syntaxTree } from '@codemirror/language' import { EditorSelection, EditorState, SelectionRange } from '@codemirror/state' import { SyntaxNode, Tree } from '@lezer/common' import { ListEnvironment } from '../../lezer-latex/latex.terms.mjs' -const HUNDRED_MS = 100 - -export type AncestorItem = { - node: SyntaxNode - label: string - type?: string - from: number - to: number -} - -/** - * Get the stack of 'ancestor' nodes at the given position. - * The first element is the most distant ancestor, while the last element - * is the node at the position. - */ -export function getAncestorStack( - state: EditorState, - pos: number -): AncestorItem[] | null { - const tree = ensureSyntaxTree(state, pos, HUNDRED_MS) - - if (!tree) { - return null - } - - const stack: AncestorItem[] = [] - const selectedNode = tree.resolve(pos, 0) - - let node: SyntaxNode | null = selectedNode - while (node) { - const name = node.type.name - switch (name) { - case 'Environment': - { - const data: AncestorItem = { - node, - label: name, - from: node.from, - to: node.to, - } - - const child = node.getChild('EnvNameGroup') - if (child) { - data.type = state.doc.sliceString(child.from + 1, child.to - 1) - } - stack.push(data) - } - break - - default: - stack.push({ node, label: name, from: node.from, to: node.to }) - break - } - - node = node.parent - } - - return stack.reverse() -} - export const wrappedNodeOfType = ( state: EditorState, range: SelectionRange, diff --git a/services/web/frontend/js/features/source-editor/utils/tree-operations/common.ts b/services/web/frontend/js/features/source-editor/utils/tree-operations/common.ts index c8d3cbd4c7..0b8f9449a4 100644 --- a/services/web/frontend/js/features/source-editor/utils/tree-operations/common.ts +++ b/services/web/frontend/js/features/source-editor/utils/tree-operations/common.ts @@ -1,29 +1,9 @@ import { ensureSyntaxTree } from '@codemirror/language' import { EditorState } from '@codemirror/state' -import { IterMode, SyntaxNode, SyntaxNodeRef, Tree } from '@lezer/common' +import { SyntaxNode } from '@lezer/common' const HUNDRED_MS = 100 -export function iterateDescendantsOf( - tree: Tree, - ancestors: (string | number)[], - spec: { - enter(node: SyntaxNodeRef): boolean | void - leave?(node: SyntaxNodeRef): void - from?: number | undefined - to?: number | undefined - mode?: IterMode | undefined - } -) { - const filteredEnter = (node: SyntaxNodeRef): boolean | void => { - if (!ancestors.some(x => node.type.is(x))) { - return false - } - return spec.enter(node) - } - tree.iterate({ ...spec, enter: filteredEnter }) -} - export const previousSiblingIs = ( state: EditorState, pos: number, @@ -38,20 +18,6 @@ export const previousSiblingIs = ( return previousNode?.type.name === expectedName } -export const nextSiblingIs = ( - state: EditorState, - pos: number, - expectedName: string -): boolean | null => { - const tree = ensureSyntaxTree(state, pos, HUNDRED_MS) - if (!tree) { - return null - } - const thisNode = tree.resolve(pos) - const previousNode = thisNode?.nextSibling - return previousNode?.type.name === expectedName -} - export const getOptionalArgumentText = ( state: EditorState, optionalArgumentNode: SyntaxNode diff --git a/services/web/frontend/js/features/source-editor/utils/tree-operations/environments.ts b/services/web/frontend/js/features/source-editor/utils/tree-operations/environments.ts index 0fe75ca893..12b31424e8 100644 --- a/services/web/frontend/js/features/source-editor/utils/tree-operations/environments.ts +++ b/services/web/frontend/js/features/source-editor/utils/tree-operations/environments.ts @@ -297,11 +297,3 @@ export function parseFigureData( graphicsCommandArguments, }) } - -export const getBeginEnvSuffix = (state: EditorState, node: SyntaxNode) => { - const argumentNode = node - .getChild('OptionalArgument') - ?.getChild('ShortOptionalArg') - - return argumentNode && state.sliceDoc(argumentNode.from, argumentNode.to) -} diff --git a/services/web/frontend/js/features/source-editor/utils/tree-query.ts b/services/web/frontend/js/features/source-editor/utils/tree-query.ts index ef7a28fbc1..9772775711 100644 --- a/services/web/frontend/js/features/source-editor/utils/tree-query.ts +++ b/services/web/frontend/js/features/source-editor/utils/tree-query.ts @@ -6,11 +6,7 @@ export type { Outline, } from './tree-operations/outline' -export { - iterateDescendantsOf, - previousSiblingIs, - nextSiblingIs, -} from './tree-operations/common' +export { previousSiblingIs } from './tree-operations/common' export { cursorIsAtBeginEnvironment, @@ -19,7 +15,6 @@ export { } from './tree-operations/environments' export { - getAncestorStack, ancestorNodeOfType, ancestorOfNodeWithType, getBibkeyArgumentNode, diff --git a/services/web/frontend/js/features/subscription/components/dashboard/redirect-alerts.tsx b/services/web/frontend/js/features/subscription/components/dashboard/redirect-alerts.tsx index 826d4eecc3..00332230c8 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/redirect-alerts.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/redirect-alerts.tsx @@ -1,7 +1,7 @@ import { useTranslation } from 'react-i18next' import OLNotification from '@/shared/components/ol/ol-notification' -export function RedirectAlerts() { +function RedirectAlerts() { const queryParams = new URLSearchParams(window.location.search) const redirectReason = queryParams.get('redirect-reason') const { t } = useTranslation() diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal.tsx index 742d3ce409..23be08ae86 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal.tsx @@ -112,7 +112,7 @@ function WritefullGrantedAddOn({ ) } -export function WritefullManagedBundleAddOn() { +function WritefullManagedBundleAddOn() { const { setModalIdShown } = useSubscriptionDashboardContext() const handleManageOnWritefull = () => setModalIdShown('manage-on-writefull') return ( diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/contact-support-to-change-group-plan.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/contact-support-to-change-group-plan.tsx deleted file mode 100644 index ebb3cc1757..0000000000 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/contact-support-to-change-group-plan.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Trans } from 'react-i18next' - -export function ContactSupportToChangeGroupPlan() { - return ( -

    - , - ]} - /> -

    - ) -} diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/pending-additional-licenses.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/pending-additional-licenses.tsx deleted file mode 100644 index d01774cc6e..0000000000 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/pending-additional-licenses.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Trans } from 'react-i18next' - -export function PendingAdditionalLicenses({ - additionalLicenses, - totalLicenses, -}: { - additionalLicenses: number - totalLicenses: number -}) { - return ( - , - // eslint-disable-next-line react/jsx-key - , - ]} - /> - ) -} diff --git a/services/web/frontend/js/features/utils/swapModal.js b/services/web/frontend/js/features/utils/swapModal.js deleted file mode 100644 index 49df6386d2..0000000000 --- a/services/web/frontend/js/features/utils/swapModal.js +++ /dev/null @@ -1,12 +0,0 @@ -export function swapModal(selectorBefore, selectorAfter) { - const modalBefore = $(selectorBefore) - const modalAfter = $(selectorAfter) - - // Disable the fade-out + fade-in animation when swapping the forms. - modalBefore.removeClass('fade') - modalAfter.removeClass('fade') - modalAfter.modal() - modalBefore.modal('hide') - modalBefore.addClass('fade') - modalAfter.addClass('fade') -} diff --git a/services/web/frontend/js/shared/components/labs/labs-experiments-widget.tsx b/services/web/frontend/js/shared/components/labs/labs-experiments-widget.tsx index 920022561b..ff4a23ae26 100644 --- a/services/web/frontend/js/shared/components/labs/labs-experiments-widget.tsx +++ b/services/web/frontend/js/shared/components/labs/labs-experiments-widget.tsx @@ -19,6 +19,7 @@ type IntegrationLinkingWidgetProps = { setOptedIn: (optedIn: boolean) => void } +/** @knipignore */ export function LabsExperimentWidget({ logo, title, diff --git a/services/web/frontend/js/shared/hooks/promotions/use-tutorial.tsx b/services/web/frontend/js/shared/hooks/promotions/use-tutorial.tsx index 2d5e814b5c..bb97d65780 100644 --- a/services/web/frontend/js/shared/hooks/promotions/use-tutorial.tsx +++ b/services/web/frontend/js/shared/hooks/promotions/use-tutorial.tsx @@ -4,7 +4,7 @@ import { postJSON } from '@/infrastructure/fetch-json' import { debugConsole } from '@/utils/debugging' import { useEditorContext } from '@/shared/context/editor-context' -export const useTutorial = ( +const useTutorial = ( tutorialKey: string, eventData: Record = {} ) => { diff --git a/services/web/frontend/js/shared/hooks/use-async-with-cancel.ts b/services/web/frontend/js/shared/hooks/use-async-with-cancel.ts index cc480563b6..09ce6659aa 100644 --- a/services/web/frontend/js/shared/hooks/use-async-with-cancel.ts +++ b/services/web/frontend/js/shared/hooks/use-async-with-cancel.ts @@ -121,5 +121,4 @@ function useAsync( } export default useAsync -export type UseAsyncReturnType = ReturnType export { useAsync, abortError } diff --git a/services/web/frontend/js/shared/hooks/use-remind-me-later.ts b/services/web/frontend/js/shared/hooks/use-remind-me-later.ts deleted file mode 100644 index 321037ff6b..0000000000 --- a/services/web/frontend/js/shared/hooks/use-remind-me-later.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { useState, useCallback, useEffect } from 'react' -import customLocalStorage from '@/infrastructure/local-storage' -import usePersistedState from '@/shared/hooks/use-persisted-state' - -/** - * @typedef {Object} RemindMeLater - * @property {boolean} stillDissmissed - whether the user has dismissed the notification, or if the notification is still withing the 1 day reminder period - * @property {function} remindThemLater - saves that the user has dismissed the notification for 1 day in local storage - * @property {function} saveDismissed - saves that the user has dismissed the notification in local storage - */ - -/** - * - * @param {string} key the unique key used to keep track of what popup is currently being shown (usually the component name) - * @param {string} notificationLocation what page the notification originates from (eg, the editor page, project page, etc) - * @returns {RemindMeLater} an object containing whether the notification is still dismissed, and functions to remind the user later or save that they have dismissed the notification - */ -export default function useRemindMeLater( - key: string, - notificationLocation: string = 'editor' -) { - const [dismissedUntil, setDismissedUntil] = usePersistedState< - Date | undefined - >(`${notificationLocation}.has_dismissed_${key}_until`) - - const [stillDissmissed, setStillDismissed] = useState(true) - - useEffect(() => { - const alertDismissed = customLocalStorage.getItem( - `${notificationLocation}.has_dismissed_${key}` - ) - - const isStillDismissed = Boolean( - dismissedUntil && new Date(dismissedUntil) > new Date() - ) - - setStillDismissed(alertDismissed || isStillDismissed) - }, [setStillDismissed, dismissedUntil, key, notificationLocation]) - - const remindThemLater = useCallback(() => { - const until = new Date() - until.setDate(until.getDate() + 1) // 1 day - setDismissedUntil(until) - }, [setDismissedUntil]) - - const saveDismissed = useCallback(() => { - customLocalStorage.setItem( - `${notificationLocation}.has_dismissed_${key}`, - true - ) - }, [key, notificationLocation]) - - return { stillDissmissed, remindThemLater, saveDismissed } -} diff --git a/services/web/frontend/js/shared/svgs/grammarly-logo.tsx b/services/web/frontend/js/shared/svgs/grammarly-logo.tsx deleted file mode 100644 index be48b89cbc..0000000000 --- a/services/web/frontend/js/shared/svgs/grammarly-logo.tsx +++ /dev/null @@ -1,39 +0,0 @@ -function GrammarlyLogo({ width = '40', height = '40', background = 'none' }) { - return ( - - - - - - - - - - ) -} - -export default GrammarlyLogo diff --git a/services/web/frontend/js/shared/svgs/writefull-logo.tsx b/services/web/frontend/js/shared/svgs/writefull-logo.tsx deleted file mode 100644 index 4c8ff02b56..0000000000 --- a/services/web/frontend/js/shared/svgs/writefull-logo.tsx +++ /dev/null @@ -1,39 +0,0 @@ -function WritefullLogo({ width = '40', height = '40', background = 'none' }) { - return ( - - - - - - - - - - ) -} - -export default WritefullLogo diff --git a/services/web/frontend/js/utils/operations.ts b/services/web/frontend/js/utils/operations.ts index 936eb8a508..92ea81cfac 100644 --- a/services/web/frontend/js/utils/operations.ts +++ b/services/web/frontend/js/utils/operations.ts @@ -22,10 +22,6 @@ export const isInsertChange = ( change: Change ): change is Change => isInsertOperation(change.op) -export const isCommentChange = ( - change: Change -): change is Change => isCommentOperation(change.op) - export const isDeleteChange = ( change: Change ): change is Change => isDeleteOperation(change.op) diff --git a/services/web/frontend/js/utils/service-worker-cleanup.js b/services/web/frontend/js/utils/service-worker-cleanup.js deleted file mode 100644 index 4a898f84f3..0000000000 --- a/services/web/frontend/js/utils/service-worker-cleanup.js +++ /dev/null @@ -1,17 +0,0 @@ -export function cleanupServiceWorker() { - try { - navigator.serviceWorker - .getRegistrations() - .catch(() => { - // fail silently if permission not given (e.g. SecurityError) - return [] - }) - .then(registrations => { - registrations.forEach(worker => { - worker.unregister() - }) - }) - } catch (e) { - // fail silently if service worker are not available (on the navigator) - } -} diff --git a/services/web/frontend/js/utils/splitTestUtils.ts b/services/web/frontend/js/utils/splitTestUtils.ts index 22a220f836..78802a0cca 100644 --- a/services/web/frontend/js/utils/splitTestUtils.ts +++ b/services/web/frontend/js/utils/splitTestUtils.ts @@ -8,6 +8,7 @@ export function getSplitTestVariant(name: string, fallback?: string) { return getMeta('ol-splitTestVariants')?.[name] || fallback } +/** @knipignore */ export function parseIntFromSplitTest(name: string, defaultValue: number) { const v = getMeta('ol-splitTestVariants')?.[name] const n = parseInt(v, 10) diff --git a/services/web/knip.ts b/services/web/knip.ts new file mode 100644 index 0000000000..00dfe3443f --- /dev/null +++ b/services/web/knip.ts @@ -0,0 +1,66 @@ +/** + * Configuration for knip, a tool to find unused exports in a project. + * + * To run knip, use the command: bin/npm -w services/web run knip + * + * It currently only runs in the frontend web service, but could be + * adapted to run in the backend as well. + * + * This is an initial version built in a hackathon. It is useful but not yet complete. + * In future we should add it to our CI checks but we need to make it more robust first. + */ + +import type { KnipConfig } from 'knip' +import Path from 'path' +import settings from './config/settings.webpack' + +const moduleEntryPoints = Object.values(settings.overleafModuleImports).flatMap( + paths => + paths.map(path => { + const cleanedPath = path.replace(Path.join(__dirname, '/'), '') + if (cleanedPath.match(/\.(js|jsx|ts|tsx?)$/)) { + return cleanedPath + } + + return `${cleanedPath}.{js,jsx,ts,tsx}` + }) +) + +const knipConfig: KnipConfig = { + $schema: 'https://unpkg.com/knip@5/schema.json', + entry: [ + 'frontend/js/pages/**/*.{js,jsx,ts,tsx}', + 'frontend/stories/**/*.{js,jsx,ts,tsx}', + 'test/frontend/**/*.{js,jsx,ts,tsx}', + 'modules/**/test/**/*.{js,jsx,ts,tsx}', + 'modules/**/*.test.{js,jsx,ts,tsx}', + 'modules/**/stories/**/*.{js,jsx,ts,tsx}', + // TODO: update this when I work out how writefull entry points work + 'modules/writefull/**/*.{js,jsx,ts,tsx}', + 'types/window.ts', + // Workers are loaded dynamically so we explicitly include all workers + // here until we work out a way to follow the dynamic imports + 'modules/**/*.worker*', + 'frontend/js/**/*.worker*', + ...moduleEntryPoints, + ], + project: [ + 'frontend/js/**/*.{js,jsx,ts,tsx}', + 'modules/**/frontend/**/*.{js,jsx,ts,tsx}', + '!frontend/js/.storybook/**/*.{js,jsx,ts,tsx}', + ], + ignore: [ + // TODO: Files that we should keep around even when unused. + // I would like to do this in the file itself but I can't seem + // to work out a way to do that (@knipignore only works for + // individual exports rather than whole files) + 'frontend/js/shared/components/labs/labs-experiments-widget.tsx', + 'frontend/js/features/ide-redesign/components/tooltip-promo.tsx', + ], + ignoreExportsUsedInFile: true, + ignoreBinaries: ['.*'], + ignoreDependencies: ['.*'], + tags: ['-knipignore'], +} + +export default knipConfig diff --git a/services/web/locales/en.json b/services/web/locales/en.json index d17318a07c..b7b2617edb 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -117,7 +117,6 @@ "added_by_on": "Added by __name__ on __date__", "adding": "Adding", "additional_certificate": "Additional certificate", - "additional_licenses": "Your subscription includes <0>__additionalLicenses__ additional license(s) for a total of <1>__totalLicenses__ licenses.", "address": "Address", "address_line_1": "Address", "address_second_line_optional": "Address second line (optional)", @@ -432,7 +431,6 @@ "contact_message_label": "Message", "contact_sales": "Contact sales", "contact_support": "Contact Support", - "contact_support_to_change_group_subscription": "Please <0>contact Support if you wish to change your group subscription.", "contact_us": "Contact us", "contact_us_lowercase": "Contact us", "contacting_the_sales_team": "Contacting the Sales team", diff --git a/services/web/package.json b/services/web/package.json index eb0cc985a7..be6d5c54ca 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -71,7 +71,8 @@ "local:test:acceptance": "npm run local:test:acceptance:app && npm run local:test:acceptance:modules", "local:test:unit": "npm run test:unit:all", "local:test:frontend": "npm run test:frontend", - "local:test": "npm run local:test:unit && npm run local:test:frontend && npm run local:test:acceptance" + "local:test": "npm run local:test:unit && npm run local:test:frontend && npm run local:test:acceptance", + "knip": "knip --config knip.ts" }, "browserslist": [ "defaults and supports woff2", @@ -255,6 +256,7 @@ "@types/express": "^4.17.23", "@types/mocha": "^9.1.0", "@types/mocha-each": "^2.0.4", + "@types/node": "^24.5.2", "@types/react": "^18.3.20", "@types/react-color": "^3.0.13", "@types/react-dom": "^18.3.6", @@ -338,6 +340,7 @@ "jscodeshift": "^17.0.0", "jsdom": "^19.0.0", "jsdom-global": "^3.0.2", + "knip": "^5.64.1", "match-sorter": "^6.2.0", "mathjax": "^3.2.2", "mediatr-ts": "^2.0.1", diff --git a/services/web/test/frontend/features/project-list/fixtures/projects-data.ts b/services/web/test/frontend/features/project-list/fixtures/projects-data.ts index 98590c221f..aff4f1217b 100644 --- a/services/web/test/frontend/features/project-list/fixtures/projects-data.ts +++ b/services/web/test/frontend/features/project-list/fixtures/projects-data.ts @@ -94,9 +94,9 @@ export const trashedAndNotOwnedProject = { owner: users.picard, } as Project -export const sharedProject = archiveableProject +export const sharedProject = { ...archiveableProject } -export const ownedProject = copyableProject +export const ownedProject = { ...copyableProject } export const projectsData: Array = [ copyableProject,