diff --git a/.swcrc b/.swcrc new file mode 100644 index 00000000..742ad40e --- /dev/null +++ b/.swcrc @@ -0,0 +1,13 @@ +{ + "$schema": "https://swc.rs/schema.json", + "sourceMaps": true, + "module": { "type": "commonjs" }, + "jsc": { + "target": "es2023", + "parser": { "syntax": "typescript", "decorators": true, "dynamicImport": true }, + "transform": { "legacyDecorator": true, "decoratorMetadata": true, "useDefineForClassFields": false }, + "keepClassNames": true, + "baseUrl": "./", + "paths": { "src/*": ["./src/*"] } + } +} diff --git a/Dockerfile b/Dockerfile index bfac584b..093de53c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,12 +12,14 @@ COPY package*.json ./ ENV NODE_TLS_REJECT_UNAUTHORIZED='0' RUN npm ci -COPY tsconfig*.json nest-cli.json ./ +COPY tsconfig*.json nest-cli.json .swcrc ./ COPY src ./src -# Dev dependencies are only needed to build; the generated Prisma client -# under node_modules/.prisma survives the prune -RUN npm run build && npm prune --omit=dev +# SWC compiles without checking types, so the image is gated on the type +# check here, as tsc's build used to gate it. Dev dependencies are only needed +# to build; the generated Prisma client under node_modules/.prisma survives +# the prune +RUN npm run typecheck && npm run build && npm prune --omit=dev FROM node:24-bookworm-slim ENV NODE_ENV=production diff --git a/nest-cli.json b/nest-cli.json index f9aa683b..bc46791e 100644 --- a/nest-cli.json +++ b/nest-cli.json @@ -3,6 +3,11 @@ "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { - "deleteOutDir": true + "deleteOutDir": true, + "builder": { + "type": "swc", + "options": { "swcrcPath": ".swcrc" } + }, + "typeCheck": false } } diff --git a/package-lock.json b/package-lock.json index 1a0fbb61..049e75d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.15.1", "compression": "^1.8.2", - "ldapts": "^7.1.0", + "ldapts": "^9.2.0", "lodash": "^4.17.21", "looks-same": "^10.0.1", "odiff-bin": "^4.5.0", @@ -47,7 +47,6 @@ "redis": "^4.7.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2", - "uuid-apikey": "^1.5.3", "zod": "^4.6.7", "zod-to-json-schema": "^3.25.0" }, @@ -55,6 +54,9 @@ "@nestjs/cli": "^12.0.0", "@nestjs/schematics": "^12.0.0", "@nestjs/testing": "^12.0.0", + "@swc/cli": "^0.8.1", + "@swc/core": "^1.16.20", + "@swc/jest": "^0.2.39", "@types/bcryptjs": "^2.4.4", "@types/compression": "^1.8.1", "@types/express": "^5.0.6", @@ -2159,6 +2161,19 @@ "node": ">=8" } }, + "node_modules/@jest/create-cache-key-function": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-30.5.1.tgz", + "integrity": "sha512-5qkif//qhlbt54255O/M2JtUZNlnefOzE12bi7FlApOIYO4pqrZcAwhNCfP4jOGFubez8uoLgY9czWFjYY57ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.5.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/diff-sequences": { "version": "30.5.0", "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.5.0.tgz", @@ -2507,6 +2522,13 @@ "integrity": "sha512-C10pc+0H6j0h8fENOfnGOvkXCmvpSQTDGlfGd0sHphZhPSGTyLjIrHba0FaZZdsKqA/wlmhYicUHb92vfZphaw==", "license": "Apache-2.0" }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" + }, "node_modules/@lukeed/csprng": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", @@ -2522,222 +2544,566 @@ "integrity": "sha512-VHyYyyoH63jFoO3shpw9Zfijk5v/9p/VrClW35z0/q6ooK7sSmxpzOkS4iZPxs3vO4XtBW4H38HRAKkAMIK7DA==", "license": "MIT" }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.5.tgz", - "integrity": "sha512-HshSvXzmBxNzqejd3k/KemgQ3hdREUYFRRrgxOZ+gPljDIsO2SDZrVZMd4Z/HsxudQbE62OVyVFE8z9BCurfjA==", + "node_modules/@napi-rs/nice": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", + "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.3" - }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + "node": ">= 10" }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" }, - "peerDependencies": { - "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", - "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" - } - }, - "node_modules/@nestjs/cli": { - "version": "12.0.10", - "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-12.0.10.tgz", - "integrity": "sha512-sFtiyJaiZe2cPqFCLhgzje5lnU1ANaGuDeaOClWMkECRC20W2m6NkqG6gnUgv+nspejIVyTG9yIyn4mXGJ9Y9g==", + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.1.1", + "@napi-rs/nice-android-arm64": "1.1.1", + "@napi-rs/nice-darwin-arm64": "1.1.1", + "@napi-rs/nice-darwin-x64": "1.1.1", + "@napi-rs/nice-freebsd-x64": "1.1.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.1.1", + "@napi-rs/nice-linux-arm64-gnu": "1.1.1", + "@napi-rs/nice-linux-arm64-musl": "1.1.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.1.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.1.1", + "@napi-rs/nice-linux-s390x-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-musl": "1.1.1", + "@napi-rs/nice-openharmony-arm64": "1.1.1", + "@napi-rs/nice-win32-arm64-msvc": "1.1.1", + "@napi-rs/nice-win32-ia32-msvc": "1.1.1", + "@napi-rs/nice-win32-x64-msvc": "1.1.1" + } + }, + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.1.1.tgz", + "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@angular-devkit/core": "22.2.3", - "@angular-devkit/schematics": "22.2.3", - "@angular-devkit/schematics-cli": "22.2.3", - "@inquirer/prompts": "8.7.3", - "@nestjs/schematics": "^12.0.0", - "ansis": "4.4.0", - "chokidar": "5.0.0", - "cli-table3": "0.6.5", - "commander": "15.0.0", - "jsonc-parser": "3.3.1", - "minimatch": "10.2.6", - "node-emoji": "2.2.0", - "ora": "9.4.1", - "tsconfig-paths": "4.2.0", - "typescript": "~6.0.2" - }, - "bin": { - "nest": "bin/nest.js" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 20.11" - }, - "peerDependencies": { - "@rspack/core": "^1.7.7 || ^2.1.10", - "@swc/cli": "^0.8.0", - "@swc/core": "^1.15.18", - "fork-ts-checker-webpack-plugin": "^9.1.0", - "ts-loader": "^9.5.4", - "tsconfig-paths-webpack-plugin": "^4.2.0", - "webpack": "^5.105.4", - "webpack-node-externals": "^3.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "@swc/cli": { - "optional": true - }, - "@swc/core": { - "optional": true - }, - "fork-ts-checker-webpack-plugin": { - "optional": true - }, - "ts-loader": { - "optional": true - }, - "tsconfig-paths-webpack-plugin": { - "optional": true - }, - "webpack": { - "optional": true - }, - "webpack-node-externals": { - "optional": true - } + "node": ">= 10" } }, - "node_modules/@nestjs/cli/node_modules/@angular-devkit/core": { - "version": "22.2.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-22.2.3.tgz", - "integrity": "sha512-bi1oBjJeg1XCgRrBYSuJikaJV4sO/mcnyFE/0a1H646yHhQpVYoU/M2Vh69Qv8h+YoMfC7RuSxT4LZHaubhhLw==", + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.1.1.tgz", + "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ajv": "8.20.0", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.7", - "rxjs": "7.8.2", - "source-map": "0.8.0" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^22.22.3 || ^24.15.0 || >=26.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^5.0.0" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } + "node": ">= 10" } }, - "node_modules/@nestjs/common": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-12.2.0.tgz", - "integrity": "sha512-xC4tUL31tQ2eoZcHheHIjnCrhnqyAEDRMGGHMUrYsOdh0Kg1DvDlLn7zelisErMcpaNanSIApJH6m9AcnK3fQQ==", + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz", + "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@standard-schema/spec": "1.1.0", - "file-type": "22.1.1", - "iterare": "1.2.1", - "load-esm": "1.0.3", - "tslib": "2.8.1", - "uid": "2.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nest" - }, - "peerDependencies": { - "class-transformer": ">=0.4.1", - "class-validator": ">=0.13.2", - "reflect-metadata": "^0.1.12 || ^0.2.0", - "rxjs": "^7.1.0" - }, - "peerDependenciesMeta": { - "class-transformer": { - "optional": true - }, - "class-validator": { - "optional": true - } + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/@nestjs/config": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-12.0.1.tgz", - "integrity": "sha512-pxqEtULFi4qXda0TqyR2YFq7o4UoRa7h4K5H0lgFuBMrUGr6x6VVldaJJrowFyh4uvp8dNq9Mie3T+l+uEOTfw==", + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz", + "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@standard-schema/spec": "1.1.0", - "dotenv": "18.0.3", - "dotenv-expand": "13.0.0", - "es-toolkit": "1.52.0" - }, - "peerDependencies": { - "@nestjs/common": "^11.0.0 || ^12.0.0", - "rxjs": "^7.1.0" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/@nestjs/config/node_modules/dotenv": { - "version": "18.0.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-18.0.3.tgz", - "integrity": "sha512-/8CJTyL817qPnfbQrJgOEcrU3fDrJkRIoXBE566wHuvIXKOCHUZhPqGPbCPi9SogjdGnreF0TlvnTgK6J/8zpA==", - "license": "BSD-2-Clause", - "bin": { - "dotenv": "dist/index.cjs" - }, + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz", + "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" + "node": ">= 10" } }, - "node_modules/@nestjs/core": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-12.2.0.tgz", - "integrity": "sha512-OTNlRALw/xlSJ/cXeIo7HzY/TqE9RSyv6uFS+OY41dDelDwMqnAjpRFiN5RVGOI/R4wAhaD0TJVjsfOrpZJCjA==", + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz", + "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", + "cpu": [ + "arm" + ], + "dev": true, "license": "MIT", - "dependencies": { - "fast-safe-stringify": "2.1.1", - "iterare": "1.2.1", - "path-to-regexp": "8.4.2", - "tslib": "2.8.1", - "uid": "2.0.2" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 20" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nest" - }, - "peerDependencies": { - "@nestjs/common": "^12.0.0", - "@nestjs/microservices": "^12.0.0", - "@nestjs/platform-express": "^12.0.0", - "@nestjs/websockets": "^12.0.0", - "reflect-metadata": "^0.1.12 || ^0.2.0", - "rxjs": "^7.1.0" - }, - "peerDependenciesMeta": { - "@nestjs/microservices": { - "optional": true - }, - "@nestjs/platform-express": { - "optional": true - }, - "@nestjs/websockets": { - "optional": true - } + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz", + "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz", + "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz", + "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz", + "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz", + "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz", + "integrity": "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.1.1.tgz", + "integrity": "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-openharmony-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz", + "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz", + "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz", + "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz", + "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.5.tgz", + "integrity": "sha512-HshSvXzmBxNzqejd3k/KemgQ3hdREUYFRRrgxOZ+gPljDIsO2SDZrVZMd4Z/HsxudQbE62OVyVFE8z9BCurfjA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + } + }, + "node_modules/@nestjs/cli": { + "version": "12.0.10", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-12.0.10.tgz", + "integrity": "sha512-sFtiyJaiZe2cPqFCLhgzje5lnU1ANaGuDeaOClWMkECRC20W2m6NkqG6gnUgv+nspejIVyTG9yIyn4mXGJ9Y9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "22.2.3", + "@angular-devkit/schematics": "22.2.3", + "@angular-devkit/schematics-cli": "22.2.3", + "@inquirer/prompts": "8.7.3", + "@nestjs/schematics": "^12.0.0", + "ansis": "4.4.0", + "chokidar": "5.0.0", + "cli-table3": "0.6.5", + "commander": "15.0.0", + "jsonc-parser": "3.3.1", + "minimatch": "10.2.6", + "node-emoji": "2.2.0", + "ora": "9.4.1", + "tsconfig-paths": "4.2.0", + "typescript": "~6.0.2" + }, + "bin": { + "nest": "bin/nest.js" + }, + "engines": { + "node": ">= 20.11" + }, + "peerDependencies": { + "@rspack/core": "^1.7.7 || ^2.1.10", + "@swc/cli": "^0.8.0", + "@swc/core": "^1.15.18", + "fork-ts-checker-webpack-plugin": "^9.1.0", + "ts-loader": "^9.5.4", + "tsconfig-paths-webpack-plugin": "^4.2.0", + "webpack": "^5.105.4", + "webpack-node-externals": "^3.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "@swc/cli": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "fork-ts-checker-webpack-plugin": { + "optional": true + }, + "ts-loader": { + "optional": true + }, + "tsconfig-paths-webpack-plugin": { + "optional": true + }, + "webpack": { + "optional": true + }, + "webpack-node-externals": { + "optional": true + } + } + }, + "node_modules/@nestjs/cli/node_modules/@angular-devkit/core": { + "version": "22.2.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-22.2.3.tgz", + "integrity": "sha512-bi1oBjJeg1XCgRrBYSuJikaJV4sO/mcnyFE/0a1H646yHhQpVYoU/M2Vh69Qv8h+YoMfC7RuSxT4LZHaubhhLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.20.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.7", + "rxjs": "7.8.2", + "source-map": "0.8.0" + }, + "engines": { + "node": "^22.22.3 || ^24.15.0 || >=26.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^5.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@nestjs/common": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-12.2.0.tgz", + "integrity": "sha512-xC4tUL31tQ2eoZcHheHIjnCrhnqyAEDRMGGHMUrYsOdh0Kg1DvDlLn7zelisErMcpaNanSIApJH6m9AcnK3fQQ==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "1.1.0", + "file-type": "22.1.1", + "iterare": "1.2.1", + "load-esm": "1.0.3", + "tslib": "2.8.1", + "uid": "2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "class-transformer": ">=0.4.1", + "class-validator": ">=0.13.2", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/config": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-12.0.1.tgz", + "integrity": "sha512-pxqEtULFi4qXda0TqyR2YFq7o4UoRa7h4K5H0lgFuBMrUGr6x6VVldaJJrowFyh4uvp8dNq9Mie3T+l+uEOTfw==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "1.1.0", + "dotenv": "18.0.3", + "dotenv-expand": "13.0.0", + "es-toolkit": "1.52.0" + }, + "peerDependencies": { + "@nestjs/common": "^11.0.0 || ^12.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/config/node_modules/dotenv": { + "version": "18.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-18.0.3.tgz", + "integrity": "sha512-/8CJTyL817qPnfbQrJgOEcrU3fDrJkRIoXBE566wHuvIXKOCHUZhPqGPbCPi9SogjdGnreF0TlvnTgK6J/8zpA==", + "license": "BSD-2-Clause", + "bin": { + "dotenv": "dist/index.cjs" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@nestjs/core": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-12.2.0.tgz", + "integrity": "sha512-OTNlRALw/xlSJ/cXeIo7HzY/TqE9RSyv6uFS+OY41dDelDwMqnAjpRFiN5RVGOI/R4wAhaD0TJVjsfOrpZJCjA==", + "license": "MIT", + "dependencies": { + "fast-safe-stringify": "2.1.1", + "iterare": "1.2.1", + "path-to-regexp": "8.4.2", + "tslib": "2.8.1", + "uid": "2.0.2" + }, + "engines": { + "node": ">= 20" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^12.0.0", + "@nestjs/microservices": "^12.0.0", + "@nestjs/platform-express": "^12.0.0", + "@nestjs/websockets": "^12.0.0", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/microservices": { + "optional": true + }, + "@nestjs/platform-express": { + "optional": true + }, + "@nestjs/websockets": { + "optional": true + } } }, "node_modules/@nestjs/jwt": { @@ -3990,171 +4356,564 @@ "@redis/client": "^1.0.0" } }, - "node_modules/@redis/time-series": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", - "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", - "license": "MIT", - "peerDependencies": { - "@redis/client": "^1.0.0" + "node_modules/@redis/time-series": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "license": "MIT", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz", + "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@smithy/core": { + "version": "3.36.2", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.36.2.tgz", + "integrity": "sha512-oRw0rB9QSUiCZfEs23G5F4Af+b2rQt1lItIICWu5IejFNd+uPM8SWqiKLMpQ6bX1Q7slsLbjuoE2niErFtkVPQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.5.2.tgz", + "integrity": "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.8.0.tgz", + "integrity": "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.1.tgz", + "integrity": "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.7.4.tgz", + "integrity": "sha512-tHy0K0VtqNd5Y7Y41h0a0Lhh0L1GzC08dTWg0F7vRJWFtTENg7IZikf3wQkanYIRdb7ngoIPMTmqgUi401fEeQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.19.0.tgz", + "integrity": "sha512-r7jh49VJxGerfAcTQA6gXcKc+98zOp/tqRwzYjgOE+iSQsP6cEU1hq2QzbuipmP68QtYdY9wKEhiCQZIzHgZ4Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@socket.io/redis-adapter": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@socket.io/redis-adapter/-/redis-adapter-8.3.0.tgz", + "integrity": "sha512-ly0cra+48hDmChxmIpnESKrc94LjRL80TEmZVscuQ/WWkRP81nNj8W8cCGMqbI4L6NCuAaPRSzZF1a9GlAxxnA==", + "license": "MIT", + "dependencies": { + "debug": "~4.3.1", + "notepack.io": "~3.0.1", + "uid2": "1.0.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "socket.io-adapter": "^2.5.4" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@swc/cli": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.8.1.tgz", + "integrity": "sha512-L+ACCGHCiS0VqHVep/INLVnvRvJ2XooQFLZq4L8snhxw1jsqz+XRcY313UsyPVturPPE1shW3jic7rt3qEQTSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@swc/counter": "^0.1.3", + "@xhmikosr/bin-wrapper": "^14.0.0", + "commander": "^8.3.0", + "minimatch": "^9.0.3", + "piscina": "^4.3.1", + "semver": "^7.3.8", + "slash": "3.0.0", + "source-map": "^0.7.3", + "tinyglobby": "^0.2.13" + }, + "bin": { + "spack": "bin/spack.js", + "swc": "bin/swc.js", + "swcx": "bin/swcx.js" + }, + "engines": { + "node": ">= 20.19.0" + }, + "peerDependencies": { + "@swc/core": "^1.2.66", + "chokidar": "^5.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@swc/cli/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@swc/cli/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@swc/cli/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/@swc/cli/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@swc/cli/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/@swc/core": { + "version": "1.16.20", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.16.20.tgz", + "integrity": "sha512-zcdfS7k28OJIMuLhqRRyGja9PxrC2GXhFXkbQ1X15y6uZEMsTrqyej55V1DiJtlXaDOmwSLn5222YPT9ZoFxPA==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.28" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.16.20", + "@swc/core-darwin-x64": "1.16.20", + "@swc/core-linux-arm-gnueabihf": "1.16.20", + "@swc/core-linux-arm64-gnu": "1.16.20", + "@swc/core-linux-arm64-musl": "1.16.20", + "@swc/core-linux-ppc64-gnu": "1.16.20", + "@swc/core-linux-s390x-gnu": "1.16.20", + "@swc/core-linux-x64-gnu": "1.16.20", + "@swc/core-linux-x64-musl": "1.16.20", + "@swc/core-win32-arm64-msvc": "1.16.20", + "@swc/core-win32-ia32-msvc": "1.16.20", + "@swc/core-win32-x64-msvc": "1.16.20" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.16.20", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.16.20.tgz", + "integrity": "sha512-i7NFSDnuE6tdBvBgmoVKKmburgcwZt/FZK2IusJaVGrjFrMUC1hrgE3RI+Kt9RhYM7vHvQj0W9j53zSxKEMbWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" } }, - "node_modules/@scarf/scarf": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", - "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", - "hasInstallScript": true, - "license": "Apache-2.0" + "node_modules/@swc/core-darwin-x64": { + "version": "1.16.20", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.16.20.tgz", + "integrity": "sha512-ho/LkT1Q3beSBeZGNZnm1mVbh5bKZHC0UGHtK3MswqOylbHtZoMXJBL40N8JpDj5SBxwECmdNEyXnU9HJ4F6RA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } }, - "node_modules/@sinclair/typebox": { - "version": "0.34.52", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", - "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.16.20", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.16.20.tgz", + "integrity": "sha512-xm3/s3zp9WyNgB1Sl78tpH76MHgobI3TSenTY15bkSHyrdQa9W2MUSESHRPd7CHGFLfpWl/0jwwNp5+hwG0e9A==", + "cpu": [ + "arm" + ], "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.16.20", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.16.20.tgz", + "integrity": "sha512-e6Um0U+VcTQyurOXhbratRQlrPITSRwugG00fhICbi2MGTi1vRiE4TMqGCjWpR1zPmZKXDGMw+yNFvh7AAgT2Q==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.16.20", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.16.20.tgz", + "integrity": "sha512-ekCO2obfK9NFwXGHfAQjjuU5mLDDC7qMaUJH/V/4iyzD0BqNhilr6McZrXLytOQ/xKtTNxLQ71wYrD3M2LW8Kg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" + "libc": [ + "musl" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "15.4.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz", - "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.16.20", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.16.20.tgz", + "integrity": "sha512-6/x2K3l4GNUWyaSmkywnth6ipZISzhVzOLsI66bj12/hSQYaKAyg9MKTwnuFf2B/iYA6GrjDghk36RYghRameQ==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1" + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" } }, - "node_modules/@smithy/core": { - "version": "3.36.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.36.2.tgz", - "integrity": "sha512-oRw0rB9QSUiCZfEs23G5F4Af+b2rQt1lItIICWu5IejFNd+uPM8SWqiKLMpQ6bX1Q7slsLbjuoE2niErFtkVPQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.19.0", - "tslib": "^2.6.2" - }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.16.20", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.16.20.tgz", + "integrity": "sha512-1GTd8sqS/NpcF+Y3YDSE0bT31V61+hD7ZZWEsVyiyxk/HQt2zpk4zlJJHOASBLc+/CyUuBZ949n6QMszbIXkmQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.0.0" + "node": ">=10" } }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.5.2.tgz", - "integrity": "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.33.2", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.16.20", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.16.20.tgz", + "integrity": "sha512-XtMeYAgQa5UZhAX84TXn0sXPmhf1xHwcv0cl5hHiBXxTVbplY7WiTwIQU9uOtVm3VUqL/quyCm78/umUNZ7FmA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.0.0" + "node": ">=10" } }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.8.0.tgz", - "integrity": "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.18.0", - "tslib": "^2.6.2" - }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.16.20", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.16.20.tgz", + "integrity": "sha512-qeF3UILa+MA4zJ29CdQ8KE7YbGFPMQBxWo3Lh0Ed0ETFEJ8pCKISWo7wQG4/SmfM9HX4KT1Odoj6ixyBn6Ji2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.0.0" + "node": ">=10" } }, - "node_modules/@smithy/node-http-handler": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.1.tgz", - "integrity": "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.18.0", - "tslib": "^2.6.2" - }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.16.20", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.16.20.tgz", + "integrity": "sha512-ciMtHXAmE+jnrWWO14rErr6tfK34QZm8jZRSdRLPodqJlzrYDw7TqN60HC8Yim5Ua9a1r/poyfkpNft6GRXvxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18.0.0" + "node": ">=10" } }, - "node_modules/@smithy/signature-v4": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.7.4.tgz", - "integrity": "sha512-tHy0K0VtqNd5Y7Y41h0a0Lhh0L1GzC08dTWg0F7vRJWFtTENg7IZikf3wQkanYIRdb7ngoIPMTmqgUi401fEeQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.35.0", - "@smithy/types": "^4.19.0", - "tslib": "^2.6.2" - }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.16.20", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.16.20.tgz", + "integrity": "sha512-MTbkgGooWebmjmATx1+QRECVZ+q4ICKNXCutwOrFVD5b5hdxla/yV4e6cRFrfNMowejaNpmMQg31ENWt0Yfzpg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18.0.0" + "node": ">=10" } }, - "node_modules/@smithy/types": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.19.0.tgz", - "integrity": "sha512-r7jh49VJxGerfAcTQA6gXcKc+98zOp/tqRwzYjgOE+iSQsP6cEU1hq2QzbuipmP68QtYdY9wKEhiCQZIzHgZ4Q==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.16.20", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.16.20.tgz", + "integrity": "sha512-Lf5oMaSdJBImtGhDecasxcIa7Z5LeJ4aGmBOY7TScroO+A/MxxB9PslIpgV0rtG/FcxtipN7hveBg9ZTeboaaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18.0.0" + "node": ">=10" } }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "license": "MIT" + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" }, - "node_modules/@socket.io/redis-adapter": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@socket.io/redis-adapter/-/redis-adapter-8.3.0.tgz", - "integrity": "sha512-ly0cra+48hDmChxmIpnESKrc94LjRL80TEmZVscuQ/WWkRP81nNj8W8cCGMqbI4L6NCuAaPRSzZF1a9GlAxxnA==", + "node_modules/@swc/jest": { + "version": "0.2.39", + "resolved": "https://registry.npmjs.org/@swc/jest/-/jest-0.2.39.tgz", + "integrity": "sha512-eyokjOwYd0Q8RnMHri+8/FS1HIrIUKK/sRrFp8c1dThUOfNeCWbLmBP1P5VsKdvmkd25JaH+OKYwEYiAYg9YAA==", + "dev": true, "license": "MIT", "dependencies": { - "debug": "~4.3.1", - "notepack.io": "~3.0.1", - "uid2": "1.0.0" + "@jest/create-cache-key-function": "^30.0.0", + "@swc/counter": "^0.1.3", + "jsonc-parser": "^3.2.0" }, "engines": { - "node": ">=10.0.0" + "npm": ">= 7.0.0" }, "peerDependencies": { - "socket.io-adapter": "^2.5.4" + "@swc/core": "*" } }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT" + "node_modules/@swc/types": { + "version": "0.1.28", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.28.tgz", + "integrity": "sha512-V6Mnml8v09QALx6K0elJ7o9K/MkVDtW3t6L+7Ou/JcWtb3xwId2AH4FeOceySd2JaO87IMw4+6vSZxLm34LPbw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } }, "node_modules/@tokenizer/inflate": { "version": "0.4.1", @@ -4235,15 +4994,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@types/asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@types/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-V91DSJ2l0h0gRhVP4oBfBzRBN9lAbPUkGDMCnwedqPKX2d84aAMc9CulOvxdw1f7DfEYx99afab+Rsm3e52jhA==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -4472,6 +5222,13 @@ "devOptional": true, "license": "MIT" }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/http-errors": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", @@ -5556,62 +6313,410 @@ "devOptional": true, "license": "MIT", "dependencies": { - "@types/lodash": "^4.17.13", - "@types/react": "*", - "@visx/curve": "4.0.1-alpha.0", - "@visx/group": "4.0.1-alpha.0", - "@visx/scale": "4.0.1-alpha.0", - "@visx/vendor": "4.0.0-alpha.0", - "classnames": "^2.3.1", - "lodash": "^4.17.21" + "@types/lodash": "^4.17.13", + "@types/react": "*", + "@visx/curve": "4.0.1-alpha.0", + "@visx/group": "4.0.1-alpha.0", + "@visx/scale": "4.0.1-alpha.0", + "@visx/vendor": "4.0.0-alpha.0", + "classnames": "^2.3.1", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0" + } + }, + "node_modules/@visx/vendor": { + "version": "4.0.0-alpha.0", + "resolved": "https://registry.npmjs.org/@visx/vendor/-/vendor-4.0.0-alpha.0.tgz", + "integrity": "sha512-6I+MuqXBcv9jnlcVowHoHKSdk9gXTWkHLKyqBwRWg7LY6A3Ei8SHfubpqGV5rBUSppxMq2RszPJUS6w+H0YgmQ==", + "devOptional": true, + "license": "MIT and ISC", + "dependencies": { + "@types/d3-array": "3.0.3", + "@types/d3-color": "3.1.0", + "@types/d3-delaunay": "6.0.1", + "@types/d3-format": "3.0.1", + "@types/d3-geo": "3.1.0", + "@types/d3-interpolate": "3.0.1", + "@types/d3-path": "3.1.1", + "@types/d3-scale": "4.0.2", + "@types/d3-shape": "3.1.7", + "@types/d3-time": "3.0.0", + "@types/d3-time-format": "2.1.0", + "d3-array": "3.2.1", + "d3-color": "3.1.0", + "d3-delaunay": "6.0.2", + "d3-format": "3.1.0", + "d3-geo": "3.1.0", + "d3-interpolate": "3.0.1", + "d3-path": "3.1.0", + "d3-scale": "4.0.2", + "d3-shape": "3.2.0", + "d3-time": "3.1.0", + "d3-time-format": "4.1.0", + "internmap": "2.0.3" + } + }, + "node_modules/@visx/vendor/node_modules/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@xhmikosr/archive-type": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/archive-type/-/archive-type-8.1.0.tgz", + "integrity": "sha512-EXOjEbnZFE5c/nFMf4FOrEURVanzHpnkPYmnmr78u02/8hAhE0FMq8p9TK1IM0/bFr5VcyBUY0gfLm8f7dKy+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^21.3.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@xhmikosr/archive-type/node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/@xhmikosr/bin-check": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/bin-check/-/bin-check-8.3.0.tgz", + "integrity": "sha512-ALFOzArQWaPhSb6zr5yQslFr/vyc/ZJENLzFeFzqeTWI2exm3V84zMieU2FEI29KBuotkX1zf5RTKXIc0UaChQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "isexe": "^4.0.0", + "tinyexec": "^1.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@xhmikosr/bin-check/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@xhmikosr/bin-wrapper": { + "version": "14.5.2", + "resolved": "https://registry.npmjs.org/@xhmikosr/bin-wrapper/-/bin-wrapper-14.5.2.tgz", + "integrity": "sha512-I0hz3Y7V8jbHPXLRgAYOuHodl3/4eX+dDePRb8l+nwXY07Gog7Lt3cZ9tIz3IJIbtJZyocTTb8whsatPmr5Zjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xhmikosr/bin-check": "^8.3.0", + "@xhmikosr/downloader": "^16.3.1", + "@xhmikosr/os-filter-obj": "^4.1.0", + "binary-version-check": "^6.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@xhmikosr/decompress": { + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress/-/decompress-11.1.4.tgz", + "integrity": "sha512-ZbYL7SAfY37/TMpopqBR3mQiuQ76kI/RNpN4q82YHSw/UxktZNy8P4wgiKPSrTImMAWRaUG8UK1pgEa56YdLaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xhmikosr/decompress-tar": "^9.0.2", + "@xhmikosr/decompress-tarbz2": "^9.0.2", + "@xhmikosr/decompress-targz": "^9.0.1", + "@xhmikosr/decompress-unzip": "^8.2.1", + "graceful-fs": "^4.2.11", + "strip-dirs": "^3.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@xhmikosr/decompress-tar": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-tar/-/decompress-tar-9.0.2.tgz", + "integrity": "sha512-8nPZ6lZ3ExhsSxi/X/PMB3K+Vtsuxk43HowxYpxw4AsCHTYqFBXwC8B3Y+M/meaUOGOVm+2tFNUAfWGRjBt+Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^21.3.4", + "is-stream": "^4.0.1", + "tar-stream": "3.1.7" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@xhmikosr/decompress-tar/node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/@xhmikosr/decompress-tar/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@xhmikosr/decompress-tarbz2": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-tarbz2/-/decompress-tarbz2-9.0.2.tgz", + "integrity": "sha512-m0DvZhE7remCxtS8xY2iHSjivT4v+iyYDdfNoeuu8Nm+7g8xEXdLKSyDEicu4u1ImJLLGEfjMuTLera/F6UGWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xhmikosr/decompress-tar": "^9.0.1", + "file-type": "^21.3.4", + "is-stream": "^4.0.1", + "seek-bzip": "^2.0.0", + "unbzip2-stream": "^1.4.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@xhmikosr/decompress-tarbz2/node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/@xhmikosr/decompress-tarbz2/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@xhmikosr/decompress-targz": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-targz/-/decompress-targz-9.0.1.tgz", + "integrity": "sha512-1JXu2b6yrpm5EuBoOzMU57B4qrHXJKWQQ7LlMynNEiz85mEjDciO3ayf//GXaTLLCEKiHjWlU3q3THjgf7uODA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xhmikosr/decompress-tar": "^9.0.0", + "file-type": "^21.3.0", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@xhmikosr/decompress-targz/node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/@xhmikosr/decompress-targz/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@xhmikosr/decompress-unzip": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-unzip/-/decompress-unzip-8.2.1.tgz", + "integrity": "sha512-2MS94QnmXQwjkKN8WyFiu1sU7J3rcWJcMze4kRYsX7tN+CXpUGECgkh4YSOhujpkWPuVlFudIziJHO/TxOqkQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^21.3.4", + "get-stream": "^9.0.1", + "yauzl": "^3.4.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@xhmikosr/decompress-unzip/node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/@xhmikosr/decompress-unzip/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@xhmikosr/decompress-unzip/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@xhmikosr/downloader": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@xhmikosr/downloader/-/downloader-16.3.1.tgz", + "integrity": "sha512-M67dvznaFbsvoqhGT4FsHWysaXXQ8386OViGZm0WOyQS3apW9p16WgvHp9nWj2vfKQAR2ZdqIBPNCHSM5rKSCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xhmikosr/archive-type": "^8.1.0", + "@xhmikosr/decompress": "^11.1.4", + "content-disposition": "^2.0.1", + "ext-name": "^5.0.0", + "file-type": "^21.3.4", + "filenamify": "^7.0.2", + "got": "^14.6.6" }, - "peerDependencies": { - "react": "^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0" + "engines": { + "node": ">=20" } }, - "node_modules/@visx/vendor": { - "version": "4.0.0-alpha.0", - "resolved": "https://registry.npmjs.org/@visx/vendor/-/vendor-4.0.0-alpha.0.tgz", - "integrity": "sha512-6I+MuqXBcv9jnlcVowHoHKSdk9gXTWkHLKyqBwRWg7LY6A3Ei8SHfubpqGV5rBUSppxMq2RszPJUS6w+H0YgmQ==", - "devOptional": true, - "license": "MIT and ISC", + "node_modules/@xhmikosr/downloader/node_modules/file-type": { + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", + "dev": true, + "license": "MIT", "dependencies": { - "@types/d3-array": "3.0.3", - "@types/d3-color": "3.1.0", - "@types/d3-delaunay": "6.0.1", - "@types/d3-format": "3.0.1", - "@types/d3-geo": "3.1.0", - "@types/d3-interpolate": "3.0.1", - "@types/d3-path": "3.1.1", - "@types/d3-scale": "4.0.2", - "@types/d3-shape": "3.1.7", - "@types/d3-time": "3.0.0", - "@types/d3-time-format": "2.1.0", - "d3-array": "3.2.1", - "d3-color": "3.1.0", - "d3-delaunay": "6.0.2", - "d3-format": "3.1.0", - "d3-geo": "3.1.0", - "d3-interpolate": "3.0.1", - "d3-path": "3.1.0", - "d3-scale": "4.0.2", - "d3-shape": "3.2.0", - "d3-time": "3.1.0", - "d3-time-format": "4.1.0", - "internmap": "2.0.3" + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" } }, - "node_modules/@visx/vendor/node_modules/d3-array": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.1.tgz", - "integrity": "sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==", - "devOptional": true, - "license": "ISC", + "node_modules/@xhmikosr/os-filter-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/os-filter-obj/-/os-filter-obj-4.1.0.tgz", + "integrity": "sha512-y5ArHvQ7BVule/+L9yE2nYMhceiJhgsqo58lOfnisQ7bg+Kjfmkgr7JBuVFiTkl+ErdShpp829QstZQyLugl8g==", + "dev": true, + "license": "MIT", "dependencies": { - "internmap": "1 - 2" + "system-architecture": "^1.0.0" }, "engines": { - "node": ">=12" + "node": ">=20" } }, "node_modules/accepts": { @@ -5824,15 +6929,6 @@ "dev": true, "license": "MIT" }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "license": "MIT", - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -5863,6 +6959,21 @@ "proxy-from-env": "^2.1.0" } }, + "node_modules/b4a": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.9.0.tgz", + "integrity": "sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/babel-jest": { "version": "30.5.2", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.5.2.tgz", @@ -6066,6 +7177,21 @@ "node": "18 || 20 || >=22" } }, + "node_modules/bare-events": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.2.tgz", + "integrity": "sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -6121,6 +7247,172 @@ "node": "*" } }, + "node_modules/binary-version": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/binary-version/-/binary-version-7.1.0.tgz", + "integrity": "sha512-Iy//vPc3ANPNlIWd242Npqc8MK0a/i4kVcHDlDA6HNMv5zMxz4ulIFhOSYJVKw/8AbHdHy0CnGYEt1QqSXxPsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^8.0.1", + "find-versions": "^6.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/binary-version-check": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/binary-version-check/-/binary-version-check-6.1.0.tgz", + "integrity": "sha512-REKdLKmuViV2WrtWXvNSiPX04KbIjfUV3Cy8batUeOg+FtmowavzJorfFhWq95cVJzINnL/44ixP26TrdJZACA==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-version": "^7.1.0", + "semver": "^7.6.0", + "semver-truncate": "^3.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/binary-version/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/binary-version/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/binary-version/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/binary-version/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/binary-version/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/binary-version/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/binary-version/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/binary-version/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/binary-version/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/body-parser": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.4.0.tgz", @@ -6238,6 +7530,31 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-crc32": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", @@ -6264,6 +7581,19 @@ "node": ">=10.16.0" } }, + "node_modules/byte-counter": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/byte-counter/-/byte-counter-0.1.0.tgz", + "integrity": "sha512-jheRLVMeUKrDBjVw2O5+k4EvR4t9wtxHL+bo/LxfkxsVeuGMy3a5SEGgXdAFA4FSzTrU8rQXQIrsZ3oBq5a0pQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -6293,13 +7623,72 @@ "pkg-types": "^2.3.0", "rc9": "^3.0.1" }, - "peerDependencies": { - "magicast": "*" + "peerDependencies": { + "magicast": "*" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "13.0.19", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-13.0.19.tgz", + "integrity": "sha512-SVXGH037+Mo1aIMO5B2UcleR43FGjFdN+M8JObSyEoQ2Mn4CODRWx28gN5jiTF0n5ItsgtIZfyargMNs8GX4kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "^4.2.0", + "get-stream": "^9.0.1", + "http-cache-semantics": "^4.2.0", + "keyv": "^5.6.0", + "mimic-response": "^4.0.0", + "normalize-url": "^8.1.1", + "responselike": "^4.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" }, - "peerDependenciesMeta": { - "magicast": { - "optional": true - } + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacheable-request/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/call-bind-apply-helpers": { @@ -6579,15 +7968,6 @@ "dev": true, "license": "MIT" }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "license": "MIT", - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -6734,6 +8114,19 @@ "url": "https://opencollective.com/express" } }, + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -6993,6 +8386,22 @@ } } }, + "node_modules/decompress-response": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-10.0.0.tgz", + "integrity": "sha512-oj7KWToJuuxlPr7VV0vabvxEIiqNMo+q0NueIiL3XhtwC6FVOX7Hr1c0C4eD0bmf7Zr+S/dSf2xvkH3Ad6sU3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^4.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dedent": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", @@ -7264,15 +8673,6 @@ "node": ">=14" } }, - "node_modules/encode32": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/encode32/-/encode32-1.1.0.tgz", - "integrity": "sha512-BCmijZ4lWec5+fuGHclf7HSZf+mos2ncQkBjtvomvRWVEGAMI/tw56fuN2x4e+FTgQuTPbZjODPwX80lFy958w==", - "license": "MIT", - "engines": { - "node": ">= 0.4.7" - } - }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -7804,6 +9204,16 @@ "node": ">= 0.6" } }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -7930,6 +9340,33 @@ "devOptional": true, "license": "MIT" }, + "node_modules/ext-list": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", + "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.28.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", + "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ext-list": "^2.0.0", + "sort-keys-length": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -7973,6 +9410,13 @@ "devOptional": true, "license": "MIT" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -8129,6 +9573,35 @@ "url": "https://github.com/sindresorhus/file-type?sponsor=1" } }, + "node_modules/filename-reserved-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-4.1.0.tgz", + "integrity": "sha512-pdgqcVlu789LoYmu0kz4pxSphL0pX/UVFFDJ/kTHk+1ZRc43UiokPZFsRV8OGxW49khh11rj54AhqczGIwLa+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/filenamify": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-7.0.3.tgz", + "integrity": "sha512-Pf0dwHrWs1GcIK15ps304fmxp2AOcfNR6vOIMvCPEy6ZztKYxzRQHlOuPVWo2HRjbCLHaRTY+kD8n3RbQ0A/fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "filename-reserved-regex": "^4.0.1" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/finalhandler": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", @@ -8199,6 +9672,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-versions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-6.0.0.tgz", + "integrity": "sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver-regex": "^4.0.5", + "super-regex": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -8285,6 +9775,16 @@ "node": ">= 6" } }, + "node_modules/form-data-encoder": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.1.0.tgz", + "integrity": "sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, "node_modules/form-data/node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -8365,6 +9865,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function-timeout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", + "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gaxios": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.3.0.tgz", @@ -8626,6 +10139,59 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/got": { + "version": "14.6.6", + "resolved": "https://registry.npmjs.org/got/-/got-14.6.6.tgz", + "integrity": "sha512-QLV1qeYSo5l13mQzWgP/y0LbMr5Plr5fJilgAIwgnwseproEbtNym8xpLsDzeZ6MWXgNE6kdWGBjdh3zT/Qerg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^7.0.1", + "byte-counter": "^0.1.0", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^13.0.12", + "decompress-response": "^10.0.0", + "form-data-encoder": "^4.0.2", + "http2-wrapper": "^2.2.1", + "keyv": "^5.5.3", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^4.0.1", + "responselike": "^4.0.2", + "type-fest": "^4.26.1" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/got/node_modules/@sindresorhus/is": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/got/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -8736,6 +10302,13 @@ "dev": true, "license": "MIT" }, + "node_modules/http-cache-semantics": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.3.0.tgz", + "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -8756,6 +10329,20 @@ "url": "https://opencollective.com/express" } }, + "node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -8879,6 +10466,16 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/inspect-with-kind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/inspect-with-kind/-/inspect-with-kind-1.0.5.tgz", + "integrity": "sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==", + "dev": true, + "license": "ISC", + "dependencies": { + "kind-of": "^6.0.2" + } + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -8961,6 +10558,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", @@ -9894,38 +11501,36 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/ldapts": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/ldapts/-/ldapts-7.4.0.tgz", - "integrity": "sha512-QLgx2pLvxMXY1nCc85Fx+cwVJDvC0sQ3l4CJZSl1FJ/iV8Ypfl6m+5xz4lm1lhoXcUlvhPqxEoyIj/8LR6ut+A==", + "node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, "license": "MIT", "dependencies": { - "@types/asn1": ">=0.2.4", - "asn1": "0.2.6", - "debug": "4.4.0", - "strict-event-emitter-types": "2.0.0", - "uuid": "11.1.0", - "whatwg-url": "14.2.0" - }, + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=0.10.0" } }, - "node_modules/ldapts/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/ldapts": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/ldapts/-/ldapts-9.2.0.tgz", + "integrity": "sha512-5mDg3OT5fDP9DUXga9dCLW8cMW24OTOKT4pv/Qj+f2zCV3qxb+gkZLCzMAqzVTOANoycidKkYpPvkclUQU2aXw==", "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "strict-event-emitter-types": "2.0.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=22" } }, "node_modules/leven": { @@ -10101,6 +11706,19 @@ "node": ">= 18.0.0" } }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -10146,6 +11764,37 @@ "@jridgewell/sourcemap-codec": "^1.6.0" } }, + "node_modules/make-asynchronous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/make-asynchronous/-/make-asynchronous-1.1.0.tgz", + "integrity": "sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-event": "^6.0.0", + "type-fest": "^4.6.0", + "web-worker": "^1.5.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-asynchronous/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -10275,7 +11924,20 @@ "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -10570,6 +12232,19 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-url": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", + "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/notepack.io": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/notepack.io/-/notepack.io-3.0.1.tgz", @@ -10850,6 +12525,32 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/p-cancelable": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-4.0.1.tgz", + "integrity": "sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/p-event": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", + "integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -10895,6 +12596,19 @@ "node": ">=8" } }, + "node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -11083,6 +12797,13 @@ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, "node_modules/perfect-debounce": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", @@ -11209,6 +12930,16 @@ "node": ">= 6" } }, + "node_modules/piscina": { + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.9.4.tgz", + "integrity": "sha512-RyBDr2VheQ8ZfH3N8SzQZHztqVyOtadTwLnUYof6gdj5161/eu2wNhCbGl5GHnNKDpnkicJYpKtL2J+y/gk6XA==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "@napi-rs/nice": "^1.0.1" + } + }, "node_modules/pixelmatch": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", @@ -11562,6 +13293,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -11600,6 +13332,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/range-parser": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", @@ -11731,6 +13476,13 @@ "node": ">=0.10.0" } }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -11764,6 +13516,22 @@ "node": ">=4" } }, + "node_modules/responselike": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-4.0.2.tgz", + "integrity": "sha512-cGk8IbWEAnaCpdAt1BHzJ3Ahz5ewDJa0KseTsE3qIRMJ3C698W8psM7byCeWVpd/Ha7FUYzuRVzXoKoM6nRUbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -11922,6 +13690,30 @@ "license": "MIT", "peer": true }, + "node_modules/seek-bzip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-2.0.0.tgz", + "integrity": "sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^6.0.0" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, + "node_modules/seek-bzip/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/semver": { "version": "7.8.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", @@ -11934,6 +13726,35 @@ "node": ">=10" } }, + "node_modules/semver-regex": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", + "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver-truncate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-3.0.0.tgz", + "integrity": "sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", @@ -12301,6 +14122,32 @@ "node": ">= 0.6" } }, + "node_modules/sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-keys-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", + "integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "sort-keys": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0.tgz", @@ -12403,6 +14250,18 @@ "node": ">=10.0.0" } }, + "node_modules/streamx": { + "version": "2.28.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.1.tgz", + "integrity": "sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, "node_modules/strict-event-emitter-types": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strict-event-emitter-types/-/strict-event-emitter-types-2.0.0.tgz", @@ -12491,6 +14350,17 @@ "node": ">=8" } }, + "node_modules/strip-dirs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-3.0.0.tgz", + "integrity": "sha512-I0sdgcFTfKQlUPZyAqPJmSG3HLO9rWDFnxonnIbskYNM3DwFOeTNB5KzVq3dA1GdRAc/25b5Y7UO2TQfKWw4aQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "inspect-with-kind": "^1.0.5", + "is-plain-obj": "^1.1.0" + } + }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -12530,6 +14400,24 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/super-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.1.0.tgz", + "integrity": "sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-timeout": "^1.0.1", + "make-asynchronous": "^1.0.1", + "time-span": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/superagent": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.4.1.tgz", @@ -12604,6 +14492,74 @@ "url": "https://opencollective.com/synckit" } }, + "node_modules/system-architecture": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/system-architecture/-/system-architecture-1.0.0.tgz", + "integrity": "sha512-0OJWD12D7XX3KUg1DYkMaTTjSTo2k/mhIYI3HlBlceXSMcJhW/1qO735fPKS5prcyjvn57Ub151vvASYXpQrEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "convert-hrtime": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -12648,18 +14604,6 @@ "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/ts-essentials": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-10.2.1.tgz", @@ -12931,6 +14875,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, "node_modules/undici-types": { "version": "7.24.6", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", @@ -13044,53 +14999,6 @@ "node": ">= 0.4.0" } }, - "node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, - "node_modules/uuid-apikey": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/uuid-apikey/-/uuid-apikey-1.5.3.tgz", - "integrity": "sha512-v28vGJ1hRDzqLm6ufZ7b098Kmk159PInIHYWXfB47r3xOACZ5nRIAWe9VxFjvSW0MwckQYAnS1ucWUAXGKo95w==", - "license": "MIT", - "dependencies": { - "colors": "^1.4.0", - "commander": "^8.0.0", - "encode32": "^1.1.0", - "uuid": "^8.3.1" - }, - "bin": { - "apiKeyTool": "bin/apiKeyTool.js" - } - }, - "node_modules/uuid-apikey/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/uuid-apikey/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -13155,14 +15063,12 @@ "node": ">= 8" } }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } + "node_modules/web-worker": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", + "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/whatwg-fetch": { "version": "3.6.20", @@ -13170,19 +15076,6 @@ "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", "license": "MIT" }, - "node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", - "license": "MIT", - "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -13358,6 +15251,19 @@ "node": ">=12" } }, + "node_modules/yauzl": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.4.0.tgz", + "integrity": "sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 739d4d58..4b3dcc20 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.15.1", "compression": "^1.8.2", - "ldapts": "^7.1.0", + "ldapts": "^9.2.0", "lodash": "^4.17.21", "looks-same": "^10.0.1", "odiff-bin": "^4.5.0", @@ -66,7 +66,6 @@ "redis": "^4.7.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2", - "uuid-apikey": "^1.5.3", "zod": "^4.6.7", "zod-to-json-schema": "^3.25.0" }, @@ -74,6 +73,9 @@ "@nestjs/cli": "^12.0.0", "@nestjs/schematics": "^12.0.0", "@nestjs/testing": "^12.0.0", + "@swc/cli": "^0.8.1", + "@swc/core": "^1.16.20", + "@swc/jest": "^0.2.39", "@types/bcryptjs": "^2.4.4", "@types/compression": "^1.8.1", "@types/express": "^5.0.6", diff --git a/src/auth/auth.service.spec.ts b/src/auth/auth.service.spec.ts index ffebb106..8a5c729c 100644 --- a/src/auth/auth.service.spec.ts +++ b/src/auth/auth.service.spec.ts @@ -2,7 +2,6 @@ import { Test, TestingModule } from '@nestjs/testing'; import { JwtService } from '@nestjs/jwt'; import { User } from '@prisma/client'; import { compare } from 'bcryptjs'; -import uuidAPIKey from 'uuid-apikey'; import { AuthService } from './auth.service'; import { UsersService } from '../users/users.service'; @@ -53,11 +52,11 @@ describe('AuthService', () => { }); describe('generateApiKey', () => { - it('returns a valid uuid-apikey without dashes', () => { + it('returns a key in the format keys were always issued in', () => { const apiKey = service.generateApiKey(); - expect(apiKey).toMatch(/^[0-9A-Z]{28}$/); - expect(uuidAPIKey.isAPIKey(apiKey)).toBe(true); + // 28 Crockford base32 digits, upper case, no dashes + expect(apiKey).toMatch(/^[0-9A-HJKMNP-TV-Z]{28}$/); }); it('returns a new key on every call', () => { diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 446b7b05..352d3e75 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; -import uuidAPIKey from 'uuid-apikey'; +import { generateRandomKey } from '../utils/random-key'; import { compare, genSalt, hash } from 'bcryptjs'; import { JwtPayload } from './jwt-payload.model'; import { User } from '@prisma/client'; @@ -15,7 +15,7 @@ export class AuthService { } generateApiKey(): string { - return uuidAPIKey.create({ noDashes: true }).apiKey; + return generateRandomKey(); } async compare(password: string, hashedPassword: string): Promise { diff --git a/src/auth/guards/role.guard.spec.ts b/src/auth/guards/role.guard.spec.ts index 586ebaa5..c00fb5e8 100644 --- a/src/auth/guards/role.guard.spec.ts +++ b/src/auth/guards/role.guard.spec.ts @@ -2,6 +2,7 @@ import { ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Role, User } from '@prisma/client'; import { PrismaService } from '../../prisma/prisma.service'; +import { Roles } from '../../shared/roles.decorator'; import { RoleGuard } from './role.guard'; const userWithRole = (role: Role): User => ({ id: '1', role }) as User; @@ -80,4 +81,38 @@ describe('RoleGuard', () => { await expect(guard.canActivate(context(undefined, 'unknown'))).resolves.toBe(false); }); + describe('with the @Roles metadata', () => { + @Roles(Role.admin) + class AdminController { + inherited(): void {} + + @Roles(Role.editor) + overridden(): void {} + } + + const decoratedContext = (handler: () => void, user: User): ExecutionContext => + ({ + switchToHttp: () => ({ getRequest: () => ({ user, header: jest.fn() }) }), + getHandler: () => handler, + getClass: () => AdminController, + }) as unknown as ExecutionContext; + + beforeEach(() => { + guard = new RoleGuard(prismaService as unknown as PrismaService, new Reflector()); + }); + + it('applies the roles declared on the controller class', async () => { + const handler = AdminController.prototype.inherited; + + await expect(guard.canActivate(decoratedContext(handler, userWithRole(Role.editor)))).resolves.toBe(false); + await expect(guard.canActivate(decoratedContext(handler, userWithRole(Role.admin)))).resolves.toBe(true); + }); + + it('lets the roles on the handler override the class', async () => { + const handler = AdminController.prototype.overridden; + + await expect(guard.canActivate(decoratedContext(handler, userWithRole(Role.editor)))).resolves.toBe(true); + await expect(guard.canActivate(decoratedContext(handler, userWithRole(Role.admin)))).resolves.toBe(false); + }); + }); }); diff --git a/src/builds/builds.controller.ts b/src/builds/builds.controller.ts index c7f5d00b..f455b953 100644 --- a/src/builds/builds.controller.ts +++ b/src/builds/builds.controller.ts @@ -30,6 +30,7 @@ import { ProjectsService } from '../projects/projects.service'; import { RoleGuard } from '../auth/guards/role.guard'; import { Roles } from '../shared/roles.decorator'; import { CurrentUser } from '../shared/current-user.decorator'; +import type { WrapperType } from '../shared/wrapper-type'; @Controller('builds') @ApiTags('builds') @@ -37,7 +38,7 @@ export class BuildsController { constructor( private buildsService: BuildsService, @Inject(forwardRef(() => ProjectsService)) - private projectService: ProjectsService + private projectService: WrapperType ) {} @Get() diff --git a/src/builds/builds.service.ts b/src/builds/builds.service.ts index 30bd1d1d..b8273e70 100644 --- a/src/builds/builds.service.ts +++ b/src/builds/builds.service.ts @@ -7,6 +7,7 @@ import { BuildDto } from './dto/build.dto'; import { getBuildsStats } from './build-stats'; import { PaginatedBuildDto } from './dto/build-paginated.dto'; import { ModifyBuildDto } from './dto/build-modify.dto'; +import type { WrapperType } from '../shared/wrapper-type'; // Newest first, with the id to break a tie: without a total order two builds // made in the same instant have no fixed place, so an offset page could show @@ -23,7 +24,7 @@ export class BuildsService { private prismaService: PrismaService, private eventsGateway: EventsGateway, @Inject(forwardRef(() => TestRunsService)) - private testRunsService: TestRunsService + private testRunsService: WrapperType ) {} async findOne(id: string): Promise { diff --git a/src/compare/compare.service.spec.ts b/src/compare/compare.service.spec.ts index e0e981af..aab21bb6 100644 --- a/src/compare/compare.service.spec.ts +++ b/src/compare/compare.service.spec.ts @@ -13,10 +13,17 @@ import { StaticModule } from '../static/static.module'; import { StaticService } from '../static/static.service'; import { Dinov2Service } from './libs/dinov2/dinov2.service'; import { ImageComparison, TestStatus } from '@prisma/client'; -import * as utils from '../static/utils'; +import { isHddStaticServiceConfigured } from '../static/utils'; import { ImageCompareInput } from './libs/ImageCompareInput'; import { BlurredDifference, DEFAULT_BLUR_SIGMA, MAX_SOLID_SHARE } from './libs/pixelmatch/blur-amplitude.core'; +// a mock function in place of the export rather than a spy on it: SWC's +// CommonJS exports are getters, which jest.spyOn cannot replace +jest.mock('../static/utils', () => { + const actual = jest.requireActual('../static/utils'); + return { ...actual, isHddStaticServiceConfigured: jest.fn(actual.isHddStaticServiceConfigured) }; +}); + jest.mock('zod/v3', () => { const actualZod = jest.requireActual('zod'); return actualZod; @@ -75,13 +82,13 @@ describe('CompareService', () => { }); it('should return odiffService', () => { - jest.spyOn(utils, 'isHddStaticServiceConfigured').mockReturnValue(true); + jest.mocked(isHddStaticServiceConfigured).mockReturnValue(true); expect(service.getComparator(ImageComparison.odiff)).toBe(odiffService); }); it('should throw if not HDD for Odiff', () => { - jest.spyOn(utils, 'isHddStaticServiceConfigured').mockReturnValue(false); + jest.mocked(isHddStaticServiceConfigured).mockReturnValue(false); expect(() => service.getComparator(ImageComparison.odiff)).toThrow( 'Odiff can only be used with HDD static service. Please use another image comparison lib in project settings or switch STATIC_SERVICE envitonmental variable to HDD.' @@ -269,7 +276,7 @@ describe('CompareService with mocked collaborators', () => { }); it('passes an odiff failure that is only rendering noise', async () => { - jest.spyOn(utils, 'isHddStaticServiceConfigured').mockReturnValue(true); + jest.mocked(isHddStaticServiceConfigured).mockReturnValue(true); const { service } = await setup({ imageComparison: ImageComparison.odiff }); const result = await service.getDiff({ projectId: 'projectId', data }); diff --git a/src/compare/diff-worker-pool.workers.spec.ts b/src/compare/diff-worker-pool.workers.spec.ts index ffc6ca54..f2526c39 100644 --- a/src/compare/diff-worker-pool.workers.spec.ts +++ b/src/compare/diff-worker-pool.workers.spec.ts @@ -381,6 +381,38 @@ describe('DiffWorkerPool with worker threads', () => { expect(mockWorkers).toHaveLength(3); }); + it('does not run a job again when the worker that already answered it crashes later', async () => { + createPool(); + + const result = pool.run(job(1)); + mockWorkers[0].emit('message', output(1)); + await expect(result).resolves.toEqual(output(1)); + + mockWorkers[0].emit('error', new Error('boom')); + + expect(mockWorkers).toHaveLength(2); + expect(mockWorkers[1].postMessage).not.toHaveBeenCalled(); + }); + + it('takes only the crashed worker out of the idle ones', async () => { + createPool({ workers: '3' }); + + settle(pool.run(job(1))); + const [crashed, idle, busy] = mockWorkers; + expect(busy.postMessage).toHaveBeenCalledWith(job(1)); + + crashed.emit('error', new Error('boom')); + const replacement = mockWorkers[3]; + settle(pool.run(job(2))); + settle(pool.run(job(3))); + settle(pool.run(job(4))); + + expect(replacement.postMessage).toHaveBeenCalledWith(job(2)); + expect(idle.postMessage).toHaveBeenCalledWith(job(3)); + // job 4 waits: the crashed worker is never handed anything + expect(crashed.postMessage).not.toHaveBeenCalled(); + }); + it('ignores a late answer from a worker that was already dropped', async () => { createPool(); diff --git a/src/compare/libs/looks-same/looks-same.service.boundaries.spec.ts b/src/compare/libs/looks-same/looks-same.service.boundaries.spec.ts new file mode 100644 index 00000000..e7889ac1 --- /dev/null +++ b/src/compare/libs/looks-same/looks-same.service.boundaries.spec.ts @@ -0,0 +1,58 @@ +import { Test } from '@nestjs/testing'; +import { TestStatus } from '@prisma/client'; +import { PNG } from 'pngjs'; +import { StaticService } from '../../../static/static.service'; +import { DIFF_DIMENSION_RESULT } from '../consts'; +import { DEFAULT_CONFIG, LookSameService } from './looks-same.service'; + +const filled = (width: number, height: number, value: number): PNG => { + const image = new PNG({ width, height }); + image.data.fill(value); + for (let i = 3; i < image.data.length; i += 4) { + image.data[i] = 255; + } + return image; +}; + +const initService = async (baseline: PNG, image: PNG) => { + const saveImage = jest.fn().mockResolvedValue('diff.png'); + const module = await Test.createTestingModule({ + providers: [ + LookSameService, + { + provide: StaticService, + useValue: { getImage: jest.fn().mockResolvedValueOnce(baseline).mockResolvedValueOnce(image), saveImage }, + }, + ], + }).compile(); + return { service: module.get(LookSameService), saveImage }; +}; + +const input = { baseline: 'baseline', image: 'image', diffTollerancePercent: 0, ignoreAreas: [], saveDiffAsFile: true }; + +describe('LookSameService.getDiff dimensions', () => { + it('reports a different height as a dimension change', async () => { + const { service } = await initService(filled(10, 10, 0), filled(10, 5, 0)); + + await expect(service.getDiff(input, DEFAULT_CONFIG)).resolves.toBe(DIFF_DIMENSION_RESULT); + }); + + it('reports a different width as a dimension change', async () => { + const { service } = await initService(filled(10, 10, 0), filled(5, 10, 0)); + + await expect(service.getDiff(input, DEFAULT_CONFIG)).resolves.toBe(DIFF_DIMENSION_RESULT); + }); +}); + +describe('LookSameService.getDiff saving the diff', () => { + it('stores the diff as a "diff" image', async () => { + const { service, saveImage } = await initService(filled(10, 10, 0), filled(10, 10, 255)); + + const result = await service.getDiff(input, DEFAULT_CONFIG); + + expect(result.status).toBe(TestStatus.unresolved); + expect(result.diffName).toBe('diff.png'); + expect(saveImage).toHaveBeenCalledWith('diff', expect.any(Buffer)); + expect(PNG.sync.read(saveImage.mock.calls[0][1]).width).toBe(10); + }); +}); diff --git a/src/compare/libs/pixelmatch/pixelmatch.core.boundaries.spec.ts b/src/compare/libs/pixelmatch/pixelmatch.core.boundaries.spec.ts new file mode 100644 index 00000000..2abae522 --- /dev/null +++ b/src/compare/libs/pixelmatch/pixelmatch.core.boundaries.spec.ts @@ -0,0 +1,108 @@ +import { PNG } from 'pngjs'; +import { computePixelmatchDiff, PixelmatchJobInput } from './pixelmatch.core'; + +// white image with the first `redPixels` pixels (row by row) painted red +const png = (width: number, height: number, redPixels = 0): Buffer => { + const image = new PNG({ width, height }); + for (let i = 0; i < width * height; i++) { + image.data[i * 4] = 255; + image.data[i * 4 + 1] = i < redPixels ? 0 : 255; + image.data[i * 4 + 2] = i < redPixels ? 0 : 255; + image.data[i * 4 + 3] = 255; + } + return PNG.sync.write(image); +}; + +const job = (baseline: Buffer, image: Buffer, overrides: Partial = {}) => + computePixelmatchDiff({ + kind: 'diff', + baseline, + image, + ignoreAreas: [], + threshold: 0.1, + // count every differing pixel, so the percentages below are exact + includeAA: true, + allowDiffDimensions: false, + diffTolerancePercent: 0, + saveDiff: true, + ...overrides, + }); + +describe('computePixelmatchDiff tolerance boundary', () => { + // 36 of 3600 pixels differ: exactly 1% + const baseline = png(60, 60); + const image = png(60, 60, 36); + + it('reports the mismatch count and percent', () => { + const result = job(baseline, image, { diffTolerancePercent: 1 }); + + expect(result.equal).toBe(false); + expect(result.isSameDimension).toBe(true); + expect(result.pixelMisMatchCount).toBe(36); + expect(result.diffPercent).toBe(1); + }); + + it('does not keep the diff when the percent equals the tolerance', () => { + const result = job(baseline, image, { diffTolerancePercent: 1, withThumbnails: true }); + + expect(result.diffBuffer).toBeUndefined(); + expect(result.imageThumbnail).toBeUndefined(); + expect(result.diffThumbnail).toBeUndefined(); + }); + + it('keeps the diff and thumbnails when the percent is just over the tolerance', () => { + const result = job(baseline, image, { diffTolerancePercent: 0.99, withThumbnails: true }); + + expect(PNG.sync.read(Buffer.from(result.diffBuffer!)).width).toBe(60); + expect(result.imageThumbnail).toBeDefined(); + expect(result.diffThumbnail).toBeDefined(); + }); + + it('does not keep the diff over the tolerance when saving is off', () => { + const result = job(baseline, image, { diffTolerancePercent: 0, saveDiff: false, withThumbnails: true }); + + expect(result.diffBuffer).toBeUndefined(); + expect(result.imageThumbnail).toBeUndefined(); + }); +}); + +describe('computePixelmatchDiff dimensions', () => { + it('stops at a different height when different dimensions are not allowed', () => { + expect(job(png(60, 60), png(60, 30, 1))).toStrictEqual({ equal: false, isSameDimension: false }); + }); + + it('stops at a different width when different dimensions are not allowed', () => { + expect(job(png(60, 60), png(30, 60, 1))).toStrictEqual({ equal: false, isSameDimension: false }); + }); + + // 60x30 and 30x60 with the first 60 pixels red hold the very same bytes: + // one red row against two half-width red rows + const wide = png(60, 30, 60); + const tall = png(30, 60, 60); + + it('stops at another shape with the same pixel bytes when different dimensions are not allowed', () => { + expect(job(wide, tall)).toStrictEqual({ equal: false, isSameDimension: false }); + }); + + it('compares another shape with the same pixel bytes instead of calling it equal when dimensions may differ', () => { + const result = job(wide, tall, { allowDiffDimensions: true }); + + expect(result.equal).toBe(false); + expect(result.isSameDimension).toBe(false); + expect(result.pixelMisMatchCount).toBeGreaterThan(0); + }); + + it('compares on the larger canvas when different dimensions are allowed', () => { + // the checkpoint is padded with transparent pixels, which pixelmatch blends to white: + // only the baseline's red first row differs + const result = job(png(10, 10, 10), png(10, 5), { allowDiffDimensions: true, withSignature: true }); + + expect(result.equal).toBe(false); + expect(result.isSameDimension).toBe(false); + expect(result.pixelMisMatchCount).toBe(10); + expect(result.diffPercent).toBe(10); + expect(PNG.sync.read(Buffer.from(result.diffBuffer!)).height).toBe(10); + // no signature for screenshots of different sizes + expect(result.signature).toBeUndefined(); + }); +}); diff --git a/src/compare/libs/pixelmatch/pixelmatch.core.ts b/src/compare/libs/pixelmatch/pixelmatch.core.ts index 5241b001..5cf732e2 100644 --- a/src/compare/libs/pixelmatch/pixelmatch.core.ts +++ b/src/compare/libs/pixelmatch/pixelmatch.core.ts @@ -78,15 +78,20 @@ export function computePixelmatchDiff(input: PixelmatchJobInput): PixelmatchJobO const baseline = PNG.sync.read(toBuffer(input.baseline)); const image = PNG.sync.read(toBuffer(input.image)); - if (baseline.data.equals(new Uint8Array(image.data.buffer, image.data.byteOffset, image.data.byteLength))) { - return { equal: true }; - } - const isSameDimension = baseline.width === image.width && baseline.height === image.height; if (!isSameDimension && !input.allowDiffDimensions) { return { equal: false, isSameDimension }; } + // Identical bytes only mean identical screenshots at the same size: a 60x30 + // and a 30x60 image of the same pixels decode to the very same buffer. + if ( + isSameDimension && + baseline.data.equals(new Uint8Array(image.data.buffer, image.data.byteOffset, image.data.byteLength)) + ) { + return { equal: true }; + } + // scale image to max size const maxWidth = Math.max(baseline.width, image.width); const maxHeight = Math.max(baseline.height, image.height); diff --git a/src/compare/libs/pixelmatch/pixelmatch.service.boundaries.spec.ts b/src/compare/libs/pixelmatch/pixelmatch.service.boundaries.spec.ts new file mode 100644 index 00000000..f2cc0a2b --- /dev/null +++ b/src/compare/libs/pixelmatch/pixelmatch.service.boundaries.spec.ts @@ -0,0 +1,76 @@ +import { Test } from '@nestjs/testing'; +import { TestStatus } from '@prisma/client'; +import { StaticService } from '../../../static/static.service'; +import { DiffWorkerPool } from '../../diff-worker-pool'; +import { Dinov2Service } from '../dinov2/dinov2.service'; +import { DEFAULT_CONFIG, PixelmatchService } from './pixelmatch.service'; + +const diffBuffer = Buffer.from('diff'); +const imageThumbnail = Buffer.from('image-thumb'); +const diffThumbnail = Buffer.from('diff-thumb'); + +const initService = async (output: Record) => { + const saveImage = jest + .fn() + .mockResolvedValueOnce('diff.png') + .mockResolvedValueOnce('image-thumb.png') + .mockResolvedValueOnce('diff-thumb.png'); + const module = await Test.createTestingModule({ + providers: [ + PixelmatchService, + { provide: DiffWorkerPool, useValue: { run: jest.fn().mockResolvedValue(output) } }, + { provide: Dinov2Service, useValue: { confirmsNoise: jest.fn() } }, + { + provide: StaticService, + useValue: { getImageBuffer: jest.fn().mockResolvedValue(Buffer.from('png')), saveImage }, + }, + ], + }).compile(); + return { service: module.get(PixelmatchService), saveImage }; +}; + +const input = (diffTollerancePercent: number) => ({ + baseline: 'baseline', + image: 'image', + diffTollerancePercent, + ignoreAreas: [], + saveDiffAsFile: true, +}); + +describe('PixelmatchService.getDiff tolerance boundary', () => { + const output = { + equal: false, + isSameDimension: true, + pixelMisMatchCount: 10, + diffPercent: 1, + diffBuffer, + imageThumbnail, + diffThumbnail, + }; + + it('is ok and saves nothing when the percent equals the tolerance', async () => { + const { service, saveImage } = await initService(output); + + const result = await service.getDiff(input(1), DEFAULT_CONFIG); + + expect(result.status).toBe(TestStatus.ok); + expect(result.diffName).toBeNull(); + expect(saveImage).not.toHaveBeenCalled(); + }); + + it('is unresolved and saves the diff and both thumbnails just over the tolerance', async () => { + const { service, saveImage } = await initService(output); + + const result = await service.getDiff(input(0.99), DEFAULT_CONFIG); + + expect(result.status).toBe(TestStatus.unresolved); + expect(result.diffName).toBe('diff.png'); + expect(result.imageThumbnailName).toBe('image-thumb.png'); + expect(result.diffThumbnailName).toBe('diff-thumb.png'); + expect(saveImage.mock.calls).toEqual([ + ['diff', diffBuffer], + ['screenshot', imageThumbnail], + ['diff', diffThumbnail], + ]); + }); +}); diff --git a/src/compare/libs/pixelmatch/signature.core.spec.ts b/src/compare/libs/pixelmatch/signature.core.spec.ts index 73f7abc1..3c48e99c 100644 --- a/src/compare/libs/pixelmatch/signature.core.spec.ts +++ b/src/compare/libs/pixelmatch/signature.core.spec.ts @@ -55,6 +55,20 @@ describe('computeChangeSignature', () => { expect(signatureOf(blank, PNG.sync.write(taller))).toBeNull(); }); + it('has no signature for a checkpoint with the same pixel count but another shape', () => { + const wide = new PNG({ width: WIDTH * 2, height: HEIGHT / 2 }); + wide.data.fill(0); + + expect(signatureOf(blank, PNG.sync.write(wide))).toBeNull(); + }); + + it('has no signature for a checkpoint of another width only', () => { + const wider = new PNG({ width: WIDTH * 2, height: HEIGHT }); + wider.data.fill(0); + + expect(signatureOf(blank, PNG.sync.write(wider))).toBeNull(); + }); + it('has no signature when nothing changed', () => { expect(signatureOf(blank, blank)).toBeNull(); }); diff --git a/src/compare/libs/vlm/providers/gemini/gemini.service.ts b/src/compare/libs/vlm/providers/gemini/gemini.service.ts index c949561f..e3c0ff55 100644 --- a/src/compare/libs/vlm/providers/gemini/gemini.service.ts +++ b/src/compare/libs/vlm/providers/gemini/gemini.service.ts @@ -3,7 +3,7 @@ import { GoogleGenAI } from '@google/genai'; import { zodToJsonSchema } from 'zod-to-json-schema'; import { VlmProvider, VlmProviderResponse } from '../../vlm-provider.interface'; import { VlmConfig, GeminiVlmConfig } from '../../vlm.types'; -import { VlmComparisonResultSchema } from '../../vlm.service'; +import { VlmComparisonResultSchema } from '../../vlm-result.schema'; @Injectable() export class GeminiService implements VlmProvider { diff --git a/src/compare/libs/vlm/providers/ollama/ollama.service.ts b/src/compare/libs/vlm/providers/ollama/ollama.service.ts index c3e9cd58..e4d4ae78 100644 --- a/src/compare/libs/vlm/providers/ollama/ollama.service.ts +++ b/src/compare/libs/vlm/providers/ollama/ollama.service.ts @@ -4,7 +4,7 @@ import { Ollama, ChatRequest, ChatResponse, ListResponse, ModelResponse } from ' import { zodToJsonSchema } from 'zod-to-json-schema'; import { VlmProvider, VlmProviderResponse } from '../../vlm-provider.interface'; import { VlmConfig, OllamaVlmConfig } from '../../vlm.types'; -import { VlmComparisonResultSchema } from '../../vlm.service'; +import { VlmComparisonResultSchema } from '../../vlm-result.schema'; @Injectable() export class OllamaService implements VlmProvider { diff --git a/src/compare/libs/vlm/vlm-result.schema.ts b/src/compare/libs/vlm/vlm-result.schema.ts new file mode 100644 index 00000000..a6ec8555 --- /dev/null +++ b/src/compare/libs/vlm/vlm-result.schema.ts @@ -0,0 +1,9 @@ +import z from 'zod/v3'; + +// The answer a VLM provider must give. Its own module because the providers +// validate with it and VlmService uses the providers: kept in vlm.service it +// made an import cycle, which only resolves when vlm.service loads first. +export const VlmComparisonResultSchema = z.object({ + identical: z.boolean(), + description: z.string(), +}); diff --git a/src/compare/libs/vlm/vlm.service.spec.ts b/src/compare/libs/vlm/vlm.service.spec.ts index f3c0eb8f..5629797c 100644 --- a/src/compare/libs/vlm/vlm.service.spec.ts +++ b/src/compare/libs/vlm/vlm.service.spec.ts @@ -421,6 +421,20 @@ describe('VlmService', () => { expect(result.vlmDescription).toBe('From thinking'); }); + it('marks the run unresolved when the model sees a difference, whatever status pixelmatch gave', async () => { + const { getImageMock } = createImageMocks(); + const pixelmatchGetDiffMock = jest.fn().mockResolvedValue(createPixelmatchResult({ status: TestStatus.failed })); + const ollamaGenerateMock = jest.fn().mockResolvedValue({ + content: '{"identical": false, "description": "Button moved"}', + }); + const service = await initService({ getImageMock, ollamaGenerateMock, pixelmatchGetDiffMock }); + + const result = await service.getDiff(input, DEFAULT_CONFIG); + + expect(result.status).toBe(TestStatus.unresolved); + expect(result.vlmDescription).toBe('Button moved'); + }); + it('falls back to the content field when thinking is preferred but empty', async () => { const { getImageMock } = createImageMocks(); const pixelmatchGetDiffMock = jest.fn().mockResolvedValue(createPixelmatchResult({})); diff --git a/src/compare/libs/vlm/vlm.service.ts b/src/compare/libs/vlm/vlm.service.ts index d1f6a326..7ecadd50 100644 --- a/src/compare/libs/vlm/vlm.service.ts +++ b/src/compare/libs/vlm/vlm.service.ts @@ -7,11 +7,11 @@ import { ImageComparator } from '../image-comparator.interface'; import { ImageCompareInput } from '../ImageCompareInput'; import { VlmConfig, OllamaVlmConfig } from './vlm.types'; import { VlmProvider } from './vlm-provider.interface'; +import { VlmComparisonResultSchema } from './vlm-result.schema'; import { OllamaService } from './providers/ollama/ollama.service'; import { GeminiService } from './providers/gemini/gemini.service'; import { PixelmatchService, DEFAULT_CONFIG as PIXELMATCH_DEFAULT_CONFIG } from '../pixelmatch/pixelmatch.service'; import { PNG } from 'pngjs'; -import z from 'zod/v3'; export const DEFAULT_PROMPT = `You are provided with three images: 1. First image: baseline screenshot @@ -22,10 +22,7 @@ Spot any difference in text, color, shape and position of elements - treat as di Ignore minor rendering artifacts that are imperceptible to users like antialiasing. Describe the difference in about 100 words.`; -export const VlmComparisonResultSchema = z.object({ - identical: z.boolean(), - description: z.string(), -}); +export { VlmComparisonResultSchema } from './vlm-result.schema'; export const DEFAULT_CONFIG: OllamaVlmConfig = { provider: 'ollama', diff --git a/src/compare/utils/index.spec.ts b/src/compare/utils/index.spec.ts index 6a45147e..b514f537 100644 --- a/src/compare/utils/index.spec.ts +++ b/src/compare/utils/index.spec.ts @@ -2,7 +2,7 @@ import { PNG } from 'pngjs'; import { IgnoreAreaDto } from '../../test-runs/dto/ignore-area.dto'; import { generatePng } from '../../_data_'; import { Logger } from '@nestjs/common'; -import { applyIgnoreAreas, parseConfig } from '.'; +import { applyIgnoreAreas, downscale, parseConfig, scaleImageToSize } from '.'; describe('utils', () => { it.each<[PNG, IgnoreAreaDto[]]>([ @@ -36,3 +36,117 @@ describe('parseConfig', () => { expect(parseConfig(json, defaults, logger)).toStrictEqual(expected); }); }); + +// pixel i of a w x h image gets channels [i, 100 + i, 200 - i, 50 + i] so every channel is distinguishable +const distinctPixels = (width: number, height: number): Buffer => { + const data = Buffer.alloc(width * height * 4); + for (let i = 0; i < width * height; i++) { + data[i * 4] = i; + data[i * 4 + 1] = 100 + i; + data[i * 4 + 2] = 200 - i; + data[i * 4 + 3] = 50 + i; + } + return data; +}; + +const pixelOf = (data: Buffer, index: number): number[] => [...data.subarray(index * 4, index * 4 + 4)]; + +describe('applyIgnoreAreas edges', () => { + it('clears all four channels inside the area and leaves the first row and column after it untouched', () => { + const image = generatePng(3, 3); + + applyIgnoreAreas(image, [{ x: 0, y: 0, width: 2, height: 1 }]); + + expect(pixelOf(image.data, 0)).toEqual([0, 0, 0, 0]); + expect(pixelOf(image.data, 1)).toEqual([0, 0, 0, 0]); + // column right after the area + expect(pixelOf(image.data, 2)).toEqual([255, 255, 255, 255]); + // row right after the area + expect(pixelOf(image.data, 3)).toEqual([255, 255, 255, 255]); + expect(pixelOf(image.data, 4)).toEqual([255, 255, 255, 255]); + }); + + it('clears an area in the middle of the image without touching its surroundings', () => { + const image = generatePng(4, 4); + + applyIgnoreAreas(image, [{ x: 1, y: 1, width: 2, height: 2 }]); + + const cleared = [5, 6, 9, 10]; + for (let i = 0; i < 16; i++) { + expect(pixelOf(image.data, i)).toEqual(cleared.includes(i) ? [0, 0, 0, 0] : [255, 255, 255, 255]); + } + }); +}); + +describe('scaleImageToSize', () => { + it('returns the same image when the requested size equals the image size', () => { + const image = generatePng(3, 2); + + expect(scaleImageToSize(image, 3, 2)).toBe(image); + }); + + it('returns the same image when only the height is requested smaller', () => { + const image = generatePng(3, 3); + + expect(scaleImageToSize(image, 3, 2)).toBe(image); + }); + + it('pads a wider canvas with transparent black, keeping the original pixels top-left', () => { + const image = new PNG({ width: 2, height: 1 }); + distinctPixels(2, 1).copy(image.data); + + const result = scaleImageToSize(image, 3, 1); + + expect(result).not.toBe(image); + expect(result.width).toBe(3); + expect(result.height).toBe(1); + expect([...result.data]).toEqual([0, 100, 200, 50, 1, 101, 199, 51, 0, 0, 0, 0]); + }); + + it('pads a taller canvas with transparent black, keeping the original pixels top-left', () => { + const image = new PNG({ width: 1, height: 1 }); + distinctPixels(1, 1).copy(image.data); + + const result = scaleImageToSize(image, 1, 2); + + expect(result.width).toBe(1); + expect(result.height).toBe(2); + expect([...result.data]).toEqual([0, 100, 200, 50, 0, 0, 0, 0]); + }); +}); + +describe('downscale', () => { + it('returns the source itself when it already fits', () => { + const source = { data: distinctPixels(2, 2), width: 2, height: 2 }; + + expect(downscale(source, 2)).toBe(source); + }); + + it('picks the nearest-neighbour source pixel for every target pixel', () => { + const source = { data: distinctPixels(4, 4), width: 4, height: 4 }; + + const result = downscale(source, 2); + + expect(result.width).toBe(2); + expect(result.height).toBe(2); + expect(result.data.length).toBe(2 * 2 * 4); + // target (x, y) <- source (2x, 2y): source indexes 0, 2, 8, 10 + expect([...result.data]).toEqual([ + ...pixelOf(source.data, 0), + ...pixelOf(source.data, 2), + ...pixelOf(source.data, 8), + ...pixelOf(source.data, 10), + ]); + }); + + it('scales by the longest side and keeps the aspect ratio', () => { + const source = { data: distinctPixels(6, 3), width: 6, height: 3 }; + + const result = downscale(source, 2); + + expect(result.width).toBe(2); + expect(result.height).toBe(1); + // target x 0 <- source x 0, target x 1 <- source x 3 (scale 1/3) + expect([...result.data]).toEqual([...pixelOf(source.data, 0), ...pixelOf(source.data, 3)]); + }); +}); diff --git a/src/jest.config.ts b/src/jest.config.ts index 495170c4..a544aa21 100644 --- a/src/jest.config.ts +++ b/src/jest.config.ts @@ -5,11 +5,29 @@ module.exports = async () => { roots: ['./'], moduleFileExtensions: ['js', 'json', 'ts'], transform: { - // transpile-only: type errors are caught once by `npm run typecheck` - '^.+\\.(t|j)s$': ['ts-jest', { tsconfig: '/../tsconfig.jest.json' }], + // SWC only transpiles; type errors are caught once by `npm run typecheck` + '^.+\\.(t|j)s$': '@swc/jest', }, // count every source file, not just the ones some test happens to import - collectCoverageFrom: ['**/*.ts', '!**/*.spec.ts', '!jest.config.ts', '!main.ts'], + // measured on the TypeScript source through source maps: istanbul would + // instrument SWC's output and count its decorator helpers as branches + coverageProvider: 'v8', + collectCoverageFrom: [ + '**/*.ts', + '!**/*.spec.ts', + '!jest.config.ts', + // startup only + '!main.ts', + '!swagger.ts', + // types only: no code runs, so v8 would report them as never executed + '!**/*.interface.ts', + '!**/*.types.ts', + '!**/*.model.ts', + '!compare/libs/ImageCompareInput.ts', + '!test-runs/diffResult.ts', + '!shared/wrapper-type.ts', + '!test-variations/dto/test-variation-update.dto.ts', + ], coverageThreshold: { global: { statements: 98, branches: 85, functions: 98, lines: 98 }, }, diff --git a/src/projects/projects.service.ts b/src/projects/projects.service.ts index fe4bff36..251b08d9 100644 --- a/src/projects/projects.service.ts +++ b/src/projects/projects.service.ts @@ -4,8 +4,9 @@ import { BuildsService } from '../builds/builds.service'; import { TestVariationsService } from '../test-variations/test-variations.service'; import { PrismaService } from '../prisma/prisma.service'; import { Project } from '@prisma/client'; -import uuidAPIKey from 'uuid-apikey'; +import { isUuidShaped } from '../utils/random-key'; import { UpdateProjectDto } from './dto/update-project.dto'; +import type { WrapperType } from '../shared/wrapper-type'; @Injectable() export class ProjectsService { @@ -14,13 +15,13 @@ export class ProjectsService { constructor( private prismaService: PrismaService, @Inject(forwardRef(() => BuildsService)) - private buildsService: BuildsService, + private buildsService: WrapperType, @Inject(forwardRef(() => TestVariationsService)) - private testVariationsService: TestVariationsService + private testVariationsService: WrapperType ) {} async findOne(idOrName: string): Promise { - const isUUID = uuidAPIKey.isUUID(idOrName); + const isUUID = isUuidShaped(idOrName); const project: Project | null = await this.prismaService.project.findUnique({ where: { id: isUUID ? idOrName : undefined, diff --git a/src/shared/events/events.gateway.spec.ts b/src/shared/events/events.gateway.spec.ts index 7139b9ce..32ddae0b 100644 --- a/src/shared/events/events.gateway.spec.ts +++ b/src/shared/events/events.gateway.spec.ts @@ -154,6 +154,19 @@ describe('EventsGateway', () => { expect(emitsOf(emitMock, 'testRun_deleted')).toEqual([[testRunB]]); }); + it('keeps the other queued updates when one updated test run is deleted', async () => { + const { gateway, emitMock } = await initGateway({}); + + gateway.testRunUpdated(testRunA); + gateway.testRunUpdated(testRunB); + gateway.testRunUpdated(testRunC); + gateway.testRunDeleted(testRunB); + + jest.advanceTimersByTime(1500); + + expect(emitsOf(emitMock, 'testRun_updated')).toEqual([[testRunA], [testRunC]]); + }); + it('still emits an empty batch once every queued entry was deleted', async () => { const { gateway, emitMock } = await initGateway({}); diff --git a/src/shared/tasks/tasks.service.ts b/src/shared/tasks/tasks.service.ts index 4aebadf7..a6b67176 100644 --- a/src/shared/tasks/tasks.service.ts +++ b/src/shared/tasks/tasks.service.ts @@ -2,6 +2,7 @@ import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common'; import { Cron, CronExpression } from '@nestjs/schedule'; import { PrismaService } from '../../prisma/prisma.service'; import { TestVariationsService } from '../../test-variations/test-variations.service'; +import type { WrapperType } from '../wrapper-type'; // Advisory lock key of the cleanup, shared by every instance against the same // database: "VRT" and 1, a value nothing else in the database locks on @@ -14,7 +15,7 @@ export class TasksService { constructor( private prismaService: PrismaService, @Inject(forwardRef(() => TestVariationsService)) - private testVariationService: TestVariationsService + private testVariationService: WrapperType ) {} /** diff --git a/src/shared/wrapper-type.ts b/src/shared/wrapper-type.ts new file mode 100644 index 00000000..50b5520a --- /dev/null +++ b/src/shared/wrapper-type.ts @@ -0,0 +1,8 @@ +/** + * The type of a dependency injected with forwardRef. A plain class type would + * be emitted into the constructor's design:paramtypes metadata, and with + * circular imports that class is not initialised yet when the decorator runs: + * tsc's output reads it as undefined, SWC's live bindings throw. Behind an + * alias the metadata says Object, and forwardRef hands Nest the real class. + */ +export type WrapperType = T; diff --git a/src/static/utils.spec.ts b/src/static/utils.spec.ts index a2b4d788..cbdeae63 100644 --- a/src/static/utils.spec.ts +++ b/src/static/utils.spec.ts @@ -1,4 +1,3 @@ -import uuidAPIKey from 'uuid-apikey'; import { generateNewImageName, isHddStaticServiceConfigured, isPngBuffer, isS3ServiceConfigured } from './utils'; describe('static utils', () => { @@ -40,8 +39,7 @@ describe('static utils', () => { const name = generateNewImageName(type); const [key, suffix, extension] = name.split('.'); - expect(uuidAPIKey.isAPIKey(key)).toBe(true); - expect(key).toMatch(/^[0-9A-Z]{28}$/); + expect(key).toMatch(/^[0-9A-HJKMNP-TV-Z]{28}$/); expect(suffix).toBe(type); expect(extension).toBe('png'); }); diff --git a/src/static/utils.ts b/src/static/utils.ts index 20e65b3e..259f7954 100644 --- a/src/static/utils.ts +++ b/src/static/utils.ts @@ -1,4 +1,4 @@ -import uuidAPIKey from 'uuid-apikey'; +import { generateRandomKey } from '../utils/random-key'; export function isHddStaticServiceConfigured() { return !process.env.STATIC_SERVICE || process.env.STATIC_SERVICE === 'hdd'; @@ -9,7 +9,7 @@ export function isS3ServiceConfigured() { } export function generateNewImageName(type: 'screenshot' | 'diff' | 'baseline'): string { - return `${uuidAPIKey.create({ noDashes: true }).apiKey}.${type}.png`; + return `${generateRandomKey()}.${type}.png`; } const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); diff --git a/src/test-runs/test-runs.service.spec.ts b/src/test-runs/test-runs.service.spec.ts index 420d4eb1..0a869fd7 100644 --- a/src/test-runs/test-runs.service.spec.ts +++ b/src/test-runs/test-runs.service.spec.ts @@ -1579,6 +1579,7 @@ describe('TestRunsService', () => { new Prisma.PrismaClientKnownRequestError('fk', { code: 'P2003', clientVersion: 'test' }), ], ['any other error', new Error('connection lost')], + ['a non-prisma error carrying the P2025 code', Object.assign(new Error('gone'), { code: 'P2025' })], ])('swallows %s and reports the run as deleted', async (_case, error) => { const { built, testRun, eventTestRunDeletedMock } = await initDeleteService(error); @@ -2016,6 +2017,58 @@ describe('TestRunsService', () => { expect(compareGetChangeSignatureMock).toHaveBeenCalledTimes(1); }); + // the default project config is threshold 0.1 with antialiasing ignored + const stamped = (signature: unknown[], stamp = { threshold: 0.1, includeAA: true }) => + JSON.stringify({ ...stamp, signature }); + + it('reuses a stored signature stamped with the current config', async () => { + const testRun = sibling('target', { changeSignature: stamped(SAME_PALETTE) }); + const { built, compareGetChangeSignatureMock } = await initEdgeService({ testRun }); + + await built.getMatchingVariations(testRun.id); + + expect(compareGetChangeSignatureMock).not.toHaveBeenCalled(); + }); + + it.each([ + ['only the threshold changed', { threshold: 0.2, includeAA: true }], + ['only the antialiasing setting changed', { threshold: 0.1, includeAA: false }], + ])('recomputes a stored signature when %s', async (_case, stamp) => { + const testRun = sibling('target', { changeSignature: stamped(SAME_PALETTE, stamp) }); + const { built, compareGetChangeSignatureMock } = await initEdgeService({ testRun }); + + await built.getMatchingVariations(testRun.id); + + expect(compareGetChangeSignatureMock).toHaveBeenCalledTimes(1); + }); + + it.each([ + ['a single value that is not a number', stamped([1, ...new Array(SIGNATURE_LENGTH - 1).fill('nope')])], + ['a value that is not finite', stamped(SAME_PALETTE).replace('[1,', '[1e999,')], + ])('recomputes a stored signature with %s', async (_case, changeSignature) => { + const testRun = sibling('target', { changeSignature }); + const { built, compareGetChangeSignatureMock } = await initEdgeService({ testRun }); + + await built.getMatchingVariations(testRun.id); + + expect(compareGetChangeSignatureMock).toHaveBeenCalledTimes(1); + }); + + it.each([ + ['no stored signature', null], + ['an empty stored signature', ''], + ['a stored json null', 'null'], + ])('computes quietly for %s', async (_case, changeSignature) => { + const testRun = sibling('target', { changeSignature }); + const { built, compareGetChangeSignatureMock } = await initEdgeService({ testRun }); + const warn = jest.spyOn(built['logger'], 'warn').mockImplementation(() => undefined); + + await built.getMatchingVariations(testRun.id); + + expect(warn).not.toHaveBeenCalled(); + expect(compareGetChangeSignatureMock).toHaveBeenCalledTimes(1); + }); + it('does not remember a failed signature', async () => { const testRun = sibling('target'); let calls = 0; @@ -2054,6 +2107,19 @@ describe('TestRunsService', () => { expect(cache.get('key 2000')).toBeDefined(); }); + it('does not take up room for a key that was looked up but never set', () => { + const cache = service['signatureCache']; + for (let index = 0; index < 2000; index++) { + cache.set(`key ${index}`, Promise.resolve([index])); + } + + expect(cache.get('absent')).toBeUndefined(); + // replacing an existing key keeps the size, so nothing is evicted + cache.set('key 0', Promise.resolve([0])); + + expect(cache.get('key 1')).toBeDefined(); + }); + it('keeps an entry that was read recently', () => { const cache = service['signatureCache']; for (let index = 0; index < 2000; index++) { diff --git a/src/test-runs/test-runs.service.ts b/src/test-runs/test-runs.service.ts index 9ed59037..03e042f8 100644 --- a/src/test-runs/test-runs.service.ts +++ b/src/test-runs/test-runs.service.ts @@ -16,6 +16,7 @@ import { parseConfig } from '../compare/utils'; import { DEFAULT_CONFIG } from '../compare/libs/pixelmatch/pixelmatch.service'; import { PixelmatchConfig } from '../compare/libs/pixelmatch/pixelmatch.types'; import { signaturesMatch, SIGNATURE_LENGTH } from '../compare/libs/pixelmatch/signature.core'; +import type { WrapperType } from '../shared/wrapper-type'; @Injectable() export class TestRunsService { @@ -24,7 +25,7 @@ export class TestRunsService { constructor( @Inject(forwardRef(() => TestVariationsService)) - private testVariationService: TestVariationsService, + private testVariationService: WrapperType, private prismaService: PrismaService, private staticService: StaticService, private compareService: CompareService, diff --git a/src/test-variations/test-variations.service.ts b/src/test-variations/test-variations.service.ts index d6ed71cd..69224317 100644 --- a/src/test-variations/test-variations.service.ts +++ b/src/test-variations/test-variations.service.ts @@ -9,6 +9,7 @@ import { BuildDto } from '../builds/dto/build.dto'; import { getTestVariationUniqueData } from '../utils'; import { TestVariationUpdateDto } from './dto/test-variation-update.dto'; import { BaselineDataDto } from '../shared/dto/baseline-data.dto'; +import type { WrapperType } from '../shared/wrapper-type'; @Injectable() export class TestVariationsService { @@ -18,9 +19,9 @@ export class TestVariationsService { private prismaService: PrismaService, private staticService: StaticService, @Inject(forwardRef(() => TestRunsService)) - private testRunsService: TestRunsService, + private testRunsService: WrapperType, @Inject(forwardRef(() => BuildsService)) - private buildsService: BuildsService + private buildsService: WrapperType ) {} async getDetails(id: string): Promise< diff --git a/src/users/oidc/oidcusers.service.spec.ts b/src/users/oidc/oidcusers.service.spec.ts index f5873d6e..649fa609 100644 --- a/src/users/oidc/oidcusers.service.spec.ts +++ b/src/users/oidc/oidcusers.service.spec.ts @@ -412,6 +412,163 @@ describe('OidcUsersService', () => { }); }); + describe('configuration', () => { + const configWith = (overrides: Record): ConfigService => + ({ + get: jest.fn((key: string, defaultValue?: string) => + key in overrides ? overrides[key] : configService.get(key, defaultValue) + ), + getOrThrow: configService.getOrThrow, + }) as unknown as ConfigService; + const checks = { state: 'mock-state', codeVerifier: 'mock-verifier' }; + + it.each([ + ['a single admin value', { groups: 'admin' }, Role.admin], + ['values separated by spaces', { groups: 'viewer vrt-admin' }, Role.admin], + ['values separated by commas', { groups: 'viewer,admin' }, Role.admin], + ['admin values in another case', { groups: ['VRT-Admin'] }, Role.admin], + ['values that are only separators', { groups: ' , ' }, Role.editor], + ['object values, which are ignored', { groups: [{ name: 'admin' }] }, Role.editor], + ])('derives the role from %s', async (_case, roleClaims, expected) => { + mockCallback.mockResolvedValue({ + claims: () => ({ email: 'new@example.com', email_verified: true, ...roleClaims }), + }); + findUnique.mockResolvedValue(null); + create.mockImplementation(({ data }) => ({ ...user, ...data })); + + await service.handleCallback({ code: 'code' }, checks); + + expect(create).toHaveBeenCalledWith( + expect.objectContaining({ data: expect.objectContaining({ role: expected }) }) + ); + }); + + it('trims and lower-cases the configured admin values', async () => { + const custom = new OidcUsersService( + configWith({ OIDC_ADMIN_VALUES: ' Ops-Team , ,42' }), + prismaService, + authService + ); + mockCallback.mockResolvedValue({ + claims: () => ({ email: 'new@example.com', email_verified: true, groups: ['ops-team'] }), + }); + findUnique.mockResolvedValue(null); + create.mockImplementation(({ data }) => ({ ...user, ...data })); + + await custom.handleCallback({ code: 'code' }, checks); + + expect(create).toHaveBeenCalledWith( + expect.objectContaining({ data: expect.objectContaining({ role: Role.admin }) }) + ); + }); + + it.each([ + ['a number', 42], + ['a boolean', true], + ])('compares a role claim holding %s as a string', async (_case, value) => { + const custom = new OidcUsersService(configWith({ OIDC_ADMIN_VALUES: String(value) }), prismaService, authService); + mockCallback.mockResolvedValue({ + claims: () => ({ email: 'new@example.com', email_verified: true, groups: [value] }), + }); + findUnique.mockResolvedValue(null); + create.mockImplementation(({ data }) => ({ ...user, ...data })); + + await custom.handleCallback({ code: 'code' }, checks); + + expect(create).toHaveBeenCalledWith( + expect.objectContaining({ data: expect.objectContaining({ role: Role.admin }) }) + ); + }); + + it('gives new users without an admin claim the configured default role', async () => { + const custom = new OidcUsersService(configWith({ OIDC_DEFAULT_ROLE: 'Admin' }), prismaService, authService); + mockCallback.mockResolvedValue({ claims: () => ({ email: 'new@example.com', email_verified: true }) }); + findUnique.mockResolvedValue(null); + create.mockImplementation(({ data }) => ({ ...user, ...data })); + + await custom.handleCallback({ code: 'code' }, checks); + + expect(create).toHaveBeenCalledWith( + expect.objectContaining({ data: expect.objectContaining({ role: Role.admin }) }) + ); + }); + }); + + describe('handleCallback claim sources', () => { + const checks = { state: 'mock-state', codeVerifier: 'mock-verifier' }; + const relaxed = () => + new OidcUsersService( + { + get: jest.fn((key: string, defaultValue?: string) => + key === 'OIDC_REQUIRE_VERIFIED_EMAIL' ? 'false' : configService.get(key, defaultValue) + ), + getOrThrow: configService.getOrThrow, + } as unknown as ConfigService, + prismaService, + authService + ); + + it('rejects a missing email even when unverified emails are allowed', async () => { + mockCallback.mockResolvedValue({ claims: () => ({ sub: 'user-1' }) }); + + await expect(relaxed().handleCallback({ code: 'code' }, checks)).rejects.toThrow('OIDC login failed.'); + + expect(findUnique).not.toHaveBeenCalled(); + }); + + it('warns and keeps the role of an existing user when the role claim is missing', async () => { + mockCallback.mockResolvedValue({ claims: () => ({ email: 'test@example.com', email_verified: true }) }); + findUnique.mockResolvedValue({ ...user, role: Role.admin }); + const warn = jest.spyOn(service['logger'], 'warn').mockImplementation(() => undefined); + + await service.handleCallback({ code: 'code' }, checks); + + expect(warn).toHaveBeenCalledWith("Claim 'groups' not found for 'test@example.com'. Keeping role 'admin'."); + expect(update).not.toHaveBeenCalled(); + }); + + it.each([ + ['a missing parent claim', {}], + ['a null parent claim', { realm_access: null }], + ['a parent claim that is not an object', { realm_access: 'admin' }], + ])('treats a dotted role claim with %s as missing', async (_case, parent) => { + const nested = new OidcUsersService( + { + get: jest.fn((key: string, defaultValue?: string) => + key === 'OIDC_ROLE_CLAIM' ? 'realm_access.length' : configService.get(key, defaultValue) + ), + getOrThrow: configService.getOrThrow, + } as unknown as ConfigService, + prismaService, + authService + ); + mockCallback.mockResolvedValue({ + claims: () => ({ email: 'test@example.com', email_verified: true, ...parent }), + }); + findUnique.mockResolvedValue({ ...user, role: Role.admin }); + + await nested.handleCallback({ code: 'code' }, checks); + + expect(update).not.toHaveBeenCalled(); + }); + + it.each([ + ['another prisma error', new PrismaClientKnownRequestError('fk', { code: 'P2003', clientVersion: '5' })], + ['a non-prisma error', new Error('db down')], + ])('rethrows %s from creating the user without looking it up again', async (_case, error) => { + mockCallback.mockResolvedValue({ claims: () => ({ email: 'race@example.com', email_verified: true }) }); + // a lookup after the failed insert would find the user + findUnique + .mockReset() + .mockResolvedValueOnce(null) + .mockResolvedValue({ ...user, email: 'race@example.com' }); + create.mockRejectedValue(error); + + await expect(service.handleCallback({ code: 'code' }, checks)).rejects.toBe(error); + expect(findUnique).toHaveBeenCalledTimes(1); + }); + }); + describe('non-SSO operations', () => { it('updates the name but never the email of an OIDC user', async () => { update.mockResolvedValue(user); diff --git a/src/users/users.service.spec.ts b/src/users/users.service.spec.ts index 71c5a81a..3ebc4228 100644 --- a/src/users/users.service.spec.ts +++ b/src/users/users.service.spec.ts @@ -162,7 +162,7 @@ describe('UsersService with DbUserService implementation', () => { await service.delete(user.id); // Assert - expect(prismaUserDeleteMock).toHaveBeenCalled(); + expect(prismaUserDeleteMock).toHaveBeenCalledWith({ where: { id: user.id } }); }); describe('deleteMany', () => { @@ -196,6 +196,14 @@ describe('UsersService with DbUserService implementation', () => { // Assert expect(result).toBeInstanceOf(UserDto); + expect(prismaService.user.findUnique).toHaveBeenCalledWith({ where: { id: user.id } }); + }); + + it('looks a user up by id', async () => { + const found = await service.findOne(user.id); + + expect(found).toBe(user); + expect(prismaService.user.findUnique).toHaveBeenCalledWith({ where: { id: user.id } }); }); it('should assign role to user', async () => { diff --git a/src/utils/random-key.spec.ts b/src/utils/random-key.spec.ts new file mode 100644 index 00000000..4abfd561 --- /dev/null +++ b/src/utils/random-key.spec.ts @@ -0,0 +1,39 @@ +import { randomUUID } from 'crypto'; +import { generateRandomKey, isUuidShaped } from './random-key'; + +describe('generateRandomKey', () => { + it('has the format keys were always issued in: 28 Crockford base32 digits, upper case', () => { + expect(generateRandomKey()).toMatch(/^[0-9A-HJKMNP-TV-Z]{28}$/); + }); + + it('never repeats', () => { + const keys = new Set(Array.from({ length: 1000 }, generateRandomKey)); + + expect(keys.size).toBe(1000); + }); + + it('uses every digit', () => { + const digits = new Set(Array.from({ length: 200 }, generateRandomKey).join('')); + + expect(digits.size).toBe(32); + }); +}); + +describe('isUuidShaped', () => { + it.each([randomUUID(), randomUUID().toUpperCase(), '00000000-0000-0000-0000-000000000000'])( + 'takes %s for an id', + (id) => { + expect(isUuidShaped(id)).toBe(true); + } + ); + + it.each([ + ['a project name', 'Default project'], + ['an empty string', ''], + ['a key without dashes', generateRandomKey()], + ['dashes in other places', '0000-00000000-0000-0000-000000000000'], + ['a UUID one character short', '00000000-0000-0000-0000-00000000000'], + ])('takes %s for a name', (_, value) => { + expect(isUuidShaped(value)).toBe(false); + }); +}); diff --git a/src/utils/random-key.ts b/src/utils/random-key.ts new file mode 100644 index 00000000..d4f9c91d --- /dev/null +++ b/src/utils/random-key.ts @@ -0,0 +1,38 @@ +import { randomBytes } from 'crypto'; + +// Crockford's base32 digits: no I, L, O or U, so a key read aloud or retyped +// is not mistaken. The alphabet of the keys issued before (uuid-apikey). +const CROCKFORD_DIGITS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; +const KEY_LENGTH = 28; + +/** + * A random 28-character key in the format API keys and image names have + * always had — Crockford base32, upper case, no dashes — so keys issued + * before keep working and clients see no change. Each character carries 5 + * random bits (140 in all, more than the 122 a v4 UUID gave). + */ +export function generateRandomKey(): string { + const bytes = randomBytes(KEY_LENGTH); + let key = ''; + for (const byte of bytes) { + // 256 is a multiple of 32, so taking the low 5 bits keeps every digit equally likely + key += CROCKFORD_DIGITS[byte & 31]; + } + return key; +} + +/** + * Whether a project reference is an id rather than a name: dashes where a + * UUID has its first three, and 32 characters without them. The check + * uuid-apikey made, so references resolve as they did; an empty string is + * simply not a UUID instead of an exception. + */ +export function isUuidShaped(value: string): boolean { + return ( + !!value && + value.charAt(8) === '-' && + value.charAt(13) === '-' && + value.charAt(18) === '-' && + value.replace(/-/g, '').length === 32 + ); +} diff --git a/test/builds-api.e2e-spec.ts b/test/builds-api.e2e-spec.ts index 27904d8c..9c66e54a 100644 --- a/test/builds-api.e2e-spec.ts +++ b/test/builds-api.e2e-spec.ts @@ -1,6 +1,6 @@ import { INestApplication } from '@nestjs/common'; import { Build, Project, TestRun, TestStatus } from '@prisma/client'; -import uuidAPIKey from 'uuid-apikey'; +import { randomUUID } from 'crypto'; import { TEST_PROJECT } from '../src/_data_'; import { BuildDto } from '../src/builds/dto/build.dto'; import { BuildsService } from '../src/builds/builds.service'; @@ -40,7 +40,7 @@ describe('Builds API (e2e)', () => { ...TEST_PROJECT, autoApproveFeature: false, maxBuildAllowed: 100, - name: `Builds API E2E ${uuidAPIKey.create().uuid}`, + name: `Builds API E2E ${randomUUID()}`, }); }); @@ -106,11 +106,11 @@ describe('Builds API (e2e)', () => { }); it('aroundBuildId that holds no page falls back to skip', async () => { - const otherProject = await projectsService.create({ ...TEST_PROJECT, name: `Other ${uuidAPIKey.create().uuid}` }); + const otherProject = await projectsService.create({ ...TEST_PROJECT, name: `Other ${randomUUID()}` }); try { const foreign = await newBuild(undefined, otherProject.id); - for (const aroundBuildId of [uuidAPIKey.create().uuid, foreign.id]) { + for (const aroundBuildId of [randomUUID(), foreign.id]) { const res = await list(`take=2&skip=2&aroundBuildId=${aroundBuildId}`).expect(200); expect(res.body.skip).toBe(2); expect(res.body.data.map((build: BuildDto) => build.id)).toEqual(ordered.slice(2, 4)); @@ -189,7 +189,7 @@ describe('Builds API (e2e)', () => { }); it('404 for an unknown id', async () => { - const id = uuidAPIKey.create().uuid; + const id = randomUUID(); const res = await requestWithAuth(app, 'get', `/builds/${id}`, user.token).expect(404); @@ -234,7 +234,7 @@ describe('Builds API (e2e)', () => { }); it('404 for an unknown id', async () => { - await requestWithAuth(app, 'delete', `/builds/${uuidAPIKey.create().uuid}`, user.token).expect(404); + await requestWithAuth(app, 'delete', `/builds/${randomUUID()}`, user.token).expect(404); }); it('403 for a guest', async () => { @@ -282,9 +282,7 @@ describe('Builds API (e2e)', () => { }); it('404 for an unknown id', async () => { - await requestWithAuth(app, 'patch', `/builds/${uuidAPIKey.create().uuid}/approve?merge=false`, user.token).expect( - 404 - ); + await requestWithAuth(app, 'patch', `/builds/${randomUUID()}/approve?merge=false`, user.token).expect(404); }); it('403 for a guest', async () => { @@ -342,7 +340,7 @@ describe('Builds API (e2e)', () => { }); it('404 for an unknown id', async () => { - await requestWithApiKey(app, 'patch', `/builds/${uuidAPIKey.create().uuid}`, user.apiKey) + await requestWithApiKey(app, 'patch', `/builds/${randomUUID()}`, user.apiKey) .send({ isRunning: false }) .expect(404); }); diff --git a/test/builds.e2e-spec.ts b/test/builds.e2e-spec.ts index f9455613..d4883764 100644 --- a/test/builds.e2e-spec.ts +++ b/test/builds.e2e-spec.ts @@ -10,7 +10,7 @@ import { ProjectsService } from '../src/projects/projects.service'; import { TestRunsService } from '../src/test-runs/test-runs.service'; import { BuildsController } from '../src/builds/builds.controller'; import { TEST_PROJECT } from '../src/_data_'; -import uuidAPIKey from 'uuid-apikey'; +import { randomUUID } from 'crypto'; describe('Builds (e2e)', () => { let app: INestApplication; @@ -42,7 +42,7 @@ describe('Builds (e2e)', () => { user = await haveUserLogged(usersService); project = await projecstService.create({ ...TEST_PROJECT, - name: `Builds E2E test${uuidAPIKey.create().uuid}`, + name: `Builds E2E test${randomUUID()}`, }); }); diff --git a/test/comparators.e2e-spec.ts b/test/comparators.e2e-spec.ts index f7445e9e..05df6494 100644 --- a/test/comparators.e2e-spec.ts +++ b/test/comparators.e2e-spec.ts @@ -4,7 +4,7 @@ import { ImageComparison, Project, TestStatus } from '@prisma/client'; import { existsSync, readFileSync, promises as fs } from 'fs'; import { createServer } from 'net'; import { PNG } from 'pngjs'; -import uuidAPIKey from 'uuid-apikey'; +import { randomUUID } from 'crypto'; import { AppModule } from '../src/app.module'; import { UsersService } from '../src/users/users.service'; import { ProjectsService } from '../src/projects/projects.service'; @@ -105,7 +105,7 @@ describe('Comparators (e2e)', () => { }); const projectBody = (imageComparison: ImageComparison, imageComparisonConfig: string) => ({ - name: `Comparators E2E ${imageComparison} ${uuidAPIKey.create().uuid}`, + name: `Comparators E2E ${imageComparison} ${randomUUID()}`, mainBranchName: 'master', autoApproveFeature: false, imageComparison, diff --git a/test/preconditions.ts b/test/preconditions.ts index c3886f23..26babde6 100644 --- a/test/preconditions.ts +++ b/test/preconditions.ts @@ -1,6 +1,6 @@ import { INestApplication } from '@nestjs/common'; import { UsersService } from 'src/users/users.service'; -import uuidAPIKey from 'uuid-apikey'; +import { generateRandomKey } from '../src/utils/random-key'; import request, { Test } from 'supertest'; import { BuildsService } from 'src/builds/builds.service'; import { TestRunsService } from 'src/test-runs/test-runs.service'; @@ -14,7 +14,7 @@ import { UserLoginResponseDto } from 'src/users/dto/user-login-response.dto'; export type LoggedUser = UserLoginResponseDto & { token: string }; export const generateUser = (password: string): CreateUserDto => ({ - email: `email+${uuidAPIKey.create().apiKey.toLocaleLowerCase()}@example.com`, + email: `email+${generateRandomKey().toLocaleLowerCase()}@example.com`, password, firstName: 'fName', lastName: 'lName', diff --git a/test/project-favorites.e2e-spec.ts b/test/project-favorites.e2e-spec.ts index 53eb8919..2e748249 100644 --- a/test/project-favorites.e2e-spec.ts +++ b/test/project-favorites.e2e-spec.ts @@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication, ValidationPipe } from '@nestjs/common'; import { Project, Role } from '@prisma/client'; import request from 'supertest'; -import uuidAPIKey from 'uuid-apikey'; +import { randomUUID } from 'crypto'; import { AppModule } from '../src/app.module'; import { UsersService } from '../src/users/users.service'; import { PrismaService } from '../src/prisma/prisma.service'; @@ -25,7 +25,7 @@ describe('Project favourites (e2e)', () => { }; const haveProject = async (): Promise => { - const project = await projectsService.create({ ...TEST_PROJECT, name: `Favourite ${uuidAPIKey.create().uuid}` }); + const project = await projectsService.create({ ...TEST_PROJECT, name: `Favourite ${randomUUID()}` }); projectIds.push(project.id); return project; }; @@ -58,8 +58,8 @@ describe('Project favourites (e2e)', () => { it.each([ ['get', '/projects/favorites'], - ['put', `/projects/${uuidAPIKey.create().uuid}/favorite`], - ['delete', `/projects/${uuidAPIKey.create().uuid}/favorite`], + ['put', `/projects/${randomUUID()}/favorite`], + ['delete', `/projects/${randomUUID()}/favorite`], ] as const)('401 on %s %s without a token', (method, url) => request(app.getHttpServer())[method](url).expect(401)); it('starts with no favourites', async () => { @@ -101,7 +101,7 @@ describe('Project favourites (e2e)', () => { it('answers 404 for adding an unknown project', async () => { const user = await haveUser(); - await requestWithAuth(app, 'put', `/projects/${uuidAPIKey.create().uuid}/favorite`, user.token).expect(404); + await requestWithAuth(app, 'put', `/projects/${randomUUID()}/favorite`, user.token).expect(404); await expect(favoritesOf(user)).resolves.toEqual([]); }); @@ -109,7 +109,7 @@ describe('Project favourites (e2e)', () => { it('answers 204 for removing an unknown project', async () => { const user = await haveUser(); - await requestWithAuth(app, 'delete', `/projects/${uuidAPIKey.create().uuid}/favorite`, user.token).expect(204); + await requestWithAuth(app, 'delete', `/projects/${randomUUID()}/favorite`, user.token).expect(204); }); it('answers 400 for an id that is not a UUID', async () => { diff --git a/test/projects.e2e-spec.ts b/test/projects.e2e-spec.ts index 7e288246..24d9762e 100644 --- a/test/projects.e2e-spec.ts +++ b/test/projects.e2e-spec.ts @@ -4,11 +4,11 @@ import { AppModule } from '../src/app.module'; import { UsersService } from '../src/users/users.service'; import { requestWithAuth, haveUserLogged, LoggedUser } from './preconditions'; import { ProjectsService } from '../src/projects/projects.service'; -import uuidAPIKey from 'uuid-apikey'; +import { randomUUID } from 'crypto'; import { ProjectDto } from 'src/projects/dto/project.dto'; const project: ProjectDto = { - id: uuidAPIKey.create().uuid, + id: randomUUID(), name: 'Test project', buildsCounter: 0, mainBranchName: 'master', diff --git a/test/static.e2e-spec.ts b/test/static.e2e-spec.ts index c5bb6bbc..28b064ef 100644 --- a/test/static.e2e-spec.ts +++ b/test/static.e2e-spec.ts @@ -3,7 +3,7 @@ import { Project, TestStatus } from '@prisma/client'; import { readFileSync } from 'fs'; import { PNG } from 'pngjs'; import request from 'supertest'; -import uuidAPIKey from 'uuid-apikey'; +import { randomUUID } from 'crypto'; import { TEST_PROJECT } from '../src/_data_'; import { BuildsService } from '../src/builds/builds.service'; import { PrismaService } from '../src/prisma/prisma.service'; @@ -47,7 +47,7 @@ describe('Images (e2e)', () => { project = await projectsService.create({ ...TEST_PROJECT, autoApproveFeature: false, - name: `Images E2E ${uuidAPIKey.create().uuid}`, + name: `Images E2E ${randomUUID()}`, }); }); diff --git a/test/tasks.e2e-spec.ts b/test/tasks.e2e-spec.ts index 238633dc..7d2778f8 100644 --- a/test/tasks.e2e-spec.ts +++ b/test/tasks.e2e-spec.ts @@ -2,7 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import { Project, TestStatus } from '@prisma/client'; import { existsSync } from 'fs'; -import uuidAPIKey from 'uuid-apikey'; +import { randomUUID } from 'crypto'; import { AppModule } from '../src/app.module'; import { CLEANUP_LOCK_KEY, TasksService } from '../src/shared/tasks/tasks.service'; import { PrismaService } from '../src/prisma/prisma.service'; @@ -64,7 +64,7 @@ describe('Tasks: cleanOldTestVariations (e2e)', () => { const haveProject = async (maxBranchLifetime: number): Promise => { const project = await projectsService.create({ ...TEST_PROJECT, - name: `Tasks E2E ${uuidAPIKey.create().uuid}`, + name: `Tasks E2E ${randomUUID()}`, maxBuildAllowed: 100, maxBranchLifetime, autoApproveFeature: false, diff --git a/test/test-runs-review.e2e-spec.ts b/test/test-runs-review.e2e-spec.ts index dce578d0..81cbc18b 100644 --- a/test/test-runs-review.e2e-spec.ts +++ b/test/test-runs-review.e2e-spec.ts @@ -1,6 +1,6 @@ import { INestApplication } from '@nestjs/common'; import { Build, Project, TestStatus } from '@prisma/client'; -import uuidAPIKey from 'uuid-apikey'; +import { randomUUID } from 'crypto'; import { TEST_PROJECT } from '../src/_data_'; import { BuildsService } from '../src/builds/builds.service'; import { PrismaService } from '../src/prisma/prisma.service'; @@ -37,7 +37,7 @@ describe('TestRuns review API (e2e)', () => { ...TEST_PROJECT, autoApproveFeature: false, maxBuildAllowed: 100, - name: `TestRuns review E2E ${uuidAPIKey.create().uuid}`, + name: `TestRuns review E2E ${randomUUID()}`, }); }); @@ -170,7 +170,7 @@ describe('TestRuns review API (e2e)', () => { }); it('404 for an unknown id', async () => { - const id = uuidAPIKey.create().uuid; + const id = randomUUID(); const res = await requestWithAuth(app, 'get', `/test-runs/${id}`, user.token).expect(404); @@ -297,9 +297,7 @@ describe('TestRuns review API (e2e)', () => { }); it('404 for an unknown id', async () => { - await requestWithAuth(app, 'post', `/test-runs/approve?merge=false`, user.token) - .send([uuidAPIKey.create().uuid]) - .expect(404); + await requestWithAuth(app, 'post', `/test-runs/approve?merge=false`, user.token).send([randomUUID()]).expect(404); }); }); @@ -346,7 +344,7 @@ describe('TestRuns review API (e2e)', () => { }); it('ignores ids that do not exist', async () => { - await requestWithAuth(app, 'post', `/test-runs/delete`, user.token).send([uuidAPIKey.create().uuid]).expect(201); + await requestWithAuth(app, 'post', `/test-runs/delete`, user.token).send([randomUUID()]).expect(201); }); it('403 for a guest', async () => { @@ -439,7 +437,7 @@ describe('TestRuns review API (e2e)', () => { it('404 for an unknown id', async () => { await requestWithAuth(app, 'post', `/test-runs/ignoreAreas/update`, user.token) - .send({ ids: [uuidAPIKey.create().uuid], ignoreAreas: [area] }) + .send({ ids: [randomUUID()], ignoreAreas: [area] }) .expect(404); }); }); @@ -655,7 +653,7 @@ describe('TestRuns review API (e2e)', () => { }, 60000); it('404 for an unknown id', async () => { - await matching(uuidAPIKey.create().uuid).expect(404); + await matching(randomUUID()).expect(404); }); it('400 for an id that is not a uuid', async () => { @@ -663,7 +661,7 @@ describe('TestRuns review API (e2e)', () => { }); it('401 without a token', async () => { - await requestWithAuth(app, 'get', `/test-runs/matchingSiblings/${uuidAPIKey.create().uuid}`, '').expect(401); + await requestWithAuth(app, 'get', `/test-runs/matchingSiblings/${randomUUID()}`, '').expect(401); }); }); }); diff --git a/test/test-runs.e2e-spec.ts b/test/test-runs.e2e-spec.ts index 62313fe8..f2125f8e 100644 --- a/test/test-runs.e2e-spec.ts +++ b/test/test-runs.e2e-spec.ts @@ -9,7 +9,7 @@ import { Project, TestStatus } from '@prisma/client'; import { BuildsService } from '../src/builds/builds.service'; import { TestVariationsService } from '../src/test-variations/test-variations.service'; import { TEST_PROJECT } from '../src/_data_'; -import uuidAPIKey from 'uuid-apikey'; +import { randomUUID } from 'crypto'; import { UpdateIgnoreAreasDto } from 'src/test-runs/dto/update-ignore-area.dto'; describe('TestRuns (e2e)', () => { @@ -46,7 +46,7 @@ describe('TestRuns (e2e)', () => { user = await haveUserLogged(usersService); project = await projecstService.create({ ...TEST_PROJECT, - name: `TestRun E2E test${uuidAPIKey.create().uuid}`, + name: `TestRun E2E test${randomUUID()}`, }); }); @@ -237,7 +237,7 @@ describe('TestRuns (e2e)', () => { it('404 for a project that does not exist, storing nothing', async () => { const build = await buildsService.findOrCreate({ projectId: project.id, branchName: project.mainBranchName }); - const projectId = uuidAPIKey.create().uuid; + const projectId = randomUUID(); const res = await requestWithApiKey(app, 'post', url, user.apiKey) .set('Content-type', 'multipart/form-data') diff --git a/test/test-variations-api.e2e-spec.ts b/test/test-variations-api.e2e-spec.ts index 11976750..a680b312 100644 --- a/test/test-variations-api.e2e-spec.ts +++ b/test/test-variations-api.e2e-spec.ts @@ -1,6 +1,6 @@ import { INestApplication } from '@nestjs/common'; import { Baseline, Build, Project, TestStatus } from '@prisma/client'; -import uuidAPIKey from 'uuid-apikey'; +import { randomUUID } from 'crypto'; import { TEST_PROJECT } from '../src/_data_'; import { BuildsService } from '../src/builds/builds.service'; import { PrismaService } from '../src/prisma/prisma.service'; @@ -40,7 +40,7 @@ describe('TestVariations API (e2e)', () => { ...TEST_PROJECT, autoApproveFeature: false, maxBuildAllowed: 100, - name: `TestVariations API E2E ${uuidAPIKey.create().uuid}`, + name: `TestVariations API E2E ${randomUUID()}`, }); }); @@ -75,7 +75,7 @@ describe('TestVariations API (e2e)', () => { it('lists the variations of every branch of the project', async () => { const master = (await haveBaseline(project.mainBranchName, WHITE))!; const feature = (await haveBaseline('feature', CHANGED))!; - const other = await projectsService.create({ ...TEST_PROJECT, name: `Other ${uuidAPIKey.create().uuid}` }); + const other = await projectsService.create({ ...TEST_PROJECT, name: `Other ${randomUUID()}` }); try { await testRunsService.postTestRun({ createTestRequestDto: { @@ -142,7 +142,7 @@ describe('TestVariations API (e2e)', () => { }); it('404 for an unknown id', async () => { - const id = uuidAPIKey.create().uuid; + const id = randomUUID(); const res = await requestWithAuth(app, 'get', `/test-variations/details/${id}`, user.token).expect(404); @@ -278,7 +278,7 @@ describe('TestVariations API (e2e)', () => { }); it('404 for an unknown id', async () => { - await requestWithAuth(app, 'delete', `/test-variations/${uuidAPIKey.create().uuid}`, user.token).expect(404); + await requestWithAuth(app, 'delete', `/test-variations/${randomUUID()}`, user.token).expect(404); }); it('403 for a guest', async () => { diff --git a/test/test-variations.e2e-spec.ts b/test/test-variations.e2e-spec.ts index 957bf57b..9f147aa9 100644 --- a/test/test-variations.e2e-spec.ts +++ b/test/test-variations.e2e-spec.ts @@ -9,7 +9,7 @@ import { Project } from '@prisma/client'; import { BuildsService } from '../src/builds/builds.service'; import { TestVariationsService } from '../src/test-variations/test-variations.service'; import { TEST_PROJECT } from '../src/_data_'; -import uuidAPIKey from 'uuid-apikey'; +import { randomUUID } from 'crypto'; describe('TestVariations (e2e)', () => { let app: INestApplication; @@ -41,7 +41,7 @@ describe('TestVariations (e2e)', () => { user = await haveUserLogged(usersService); project = await projecstService.create({ ...TEST_PROJECT, - name: `TestVariations E2E test${uuidAPIKey.create().uuid}`, + name: `TestVariations E2E test${randomUUID()}`, }); }); diff --git a/test/users-admin.e2e-spec.ts b/test/users-admin.e2e-spec.ts index 96b00322..51d314bc 100644 --- a/test/users-admin.e2e-spec.ts +++ b/test/users-admin.e2e-spec.ts @@ -4,7 +4,7 @@ import { Role, TestStatus } from '@prisma/client'; import request from 'supertest'; import { createHash, generateKeyPairSync, KeyObject, randomUUID, sign } from 'crypto'; import { createServer, IncomingMessage, Server } from 'http'; -import uuidAPIKey from 'uuid-apikey'; +import { generateRandomKey } from '../src/utils/random-key'; import { AppModule } from '../src/app.module'; import { UsersService } from '../src/users/users.service'; import { PrismaService } from '../src/prisma/prisma.service'; @@ -239,7 +239,7 @@ describe('Users admin and RBAC (e2e)', () => { const projectsService = app.get(ProjectsService); const project = await projectsService.create({ ...TEST_PROJECT, - name: `Users admin E2E ${uuidAPIKey.create().uuid}`, + name: `Users admin E2E ${randomUUID()}`, }); try { const { testRun } = await haveTestRunCreated( @@ -422,7 +422,7 @@ describe('Users OIDC (e2e)', () => { }); const newEmail = () => { - const email = `oidc+${uuidAPIKey.create().apiKey.toLowerCase()}@example.com`; + const email = `oidc+${generateRandomKey().toLowerCase()}@example.com`; emails.push(email); return email; }; diff --git a/test_acceptance/acceptance.spec.ts b/test_acceptance/acceptance.spec.ts index ccda8ce4..43b2ab0e 100644 --- a/test_acceptance/acceptance.spec.ts +++ b/test_acceptance/acceptance.spec.ts @@ -1,30 +1,29 @@ import { User } from '@prisma/client'; -import axios from 'axios' +import axios from 'axios'; import { UserLoginRequestDto } from 'src/users/dto/user-login-request.dto'; import { UserLoginResponseDto } from 'src/users/dto/user-login-response.dto'; -import uuidAPIKey from 'uuid-apikey'; +import { randomUUID } from 'crypto'; axios.defaults.baseURL = 'http://localhost:4200'; let user: Partial = { - email: `${uuidAPIKey.create().uuid}@example.com`, - password: '123456', - firstName: 'fName', - lastName: 'lName', + email: `${randomUUID()}@example.com`, + password: '123456', + firstName: 'fName', + lastName: 'lName', }; const loginData: UserLoginRequestDto = { - email: user.email!, - password: user.password!, -} + email: user.email!, + password: user.password!, +}; describe('Acceptance', () => { + test('Register and login', async () => { + await axios.post('/users/register', user); - test('Register and login', async () => { - await axios.post('/users/register', user); - - const response = await axios.post('/users/login', loginData) + const response = await axios.post('/users/login', loginData); - expect(response.status).toBe(201) - expect(response.data.token).toBeDefined() - }) -}) \ No newline at end of file + expect(response.status).toBe(201); + expect(response.data.token).toBeDefined(); + }); +});