From 51d26a4c1b37c68dc0f72a54ae7ef1dc28a83b38 Mon Sep 17 00:00:00 2001 From: Dokril Date: Sat, 14 Mar 2026 17:04:53 +0300 Subject: [PATCH 1/3] feat: add network module and service for TCP latency measurement and proxy performance --- .github/copilot-instructions.md | 104 + docker/Dockerfile.singbox | 7 +- docker/entrypoint.sh | 2 +- web/api/.gitignore | 2 + web/api/nest-cli.json | 8 + web/api/package-lock.json | 6294 ++++++++++++++++++++++++ web/api/package.json | 25 + web/api/src/app.module.ts | 7 + web/api/src/config/config.ts | 33 + web/api/src/main.ts | 23 + web/api/src/network/network.module.ts | 8 + web/api/src/network/network.service.ts | 294 ++ web/api/src/proxy/proxy.controller.ts | 315 ++ web/api/src/proxy/proxy.module.ts | 13 + web/api/src/proxy/proxy.service.ts | 507 ++ web/api/src/storage/storage.module.ts | 8 + web/api/src/storage/storage.service.ts | 151 + web/api/src/vless/vless.module.ts | 8 + web/api/src/vless/vless.service.ts | 158 + web/api/tsconfig.build.json | 4 + web/api/tsconfig.json | 22 + web/app/__init__.py | 0 web/app/api.py | 742 --- web/app/config.py | 31 - web/app/network.py | 108 - web/app/storage.py | 80 - web/app/utils.py | 26 - web/app/vless.py | 176 - web/server.py | 23 - web/static/js/app.js | 1 + 30 files changed, 7992 insertions(+), 1188 deletions(-) create mode 100644 .github/copilot-instructions.md create mode 100644 web/api/.gitignore create mode 100644 web/api/nest-cli.json create mode 100644 web/api/package-lock.json create mode 100644 web/api/package.json create mode 100644 web/api/src/app.module.ts create mode 100644 web/api/src/config/config.ts create mode 100644 web/api/src/main.ts create mode 100644 web/api/src/network/network.module.ts create mode 100644 web/api/src/network/network.service.ts create mode 100644 web/api/src/proxy/proxy.controller.ts create mode 100644 web/api/src/proxy/proxy.module.ts create mode 100644 web/api/src/proxy/proxy.service.ts create mode 100644 web/api/src/storage/storage.module.ts create mode 100644 web/api/src/storage/storage.service.ts create mode 100644 web/api/src/vless/vless.module.ts create mode 100644 web/api/src/vless/vless.service.ts create mode 100644 web/api/tsconfig.build.json create mode 100644 web/api/tsconfig.json delete mode 100644 web/app/__init__.py delete mode 100644 web/app/api.py delete mode 100644 web/app/config.py delete mode 100644 web/app/network.py delete mode 100644 web/app/storage.py delete mode 100644 web/app/utils.py delete mode 100644 web/app/vless.py delete mode 100644 web/server.py diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..6877a2f --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,104 @@ +# Project Guidelines + +## Overview + +VPN-Proxy is a self-hosted VPN/proxy management system using **sing-box** as the core proxy engine (VLESS + REALITY TLS). It consists of a NestJS (TypeScript) backend, a vanilla HTML/JS frontend, PowerShell scripts for Windows management, and Docker for deployment. Documentation and UI are in **Russian**. + +## Architecture + +``` +Browser → NestJS web server (PORT, default 3456) + ├─ Serves index.html with SSI-like includes () + └─ API endpoints in web/api/src/proxy/proxy.controller.ts + ↓ writes config + data/client.json → sing-box binary (PROXY_PORT, default 8080) + ↓ reload via HTTP to RELOAD_PORT (9090, internal) + ↓ + VPN traffic out +``` + +### Key layers + +| Layer | Location | Notes | +|-------|----------|-------| +| Frontend | `web/index.html`, `web/components/`, `web/static/` | Tailwind via CDN, no build step | +| Backend | `web/api/` | NestJS + TypeScript, minimal deps | +| Proxy core | `docker/entrypoint.sh` + sing-box binary | Config in `data/client.json` | +| Windows client | `manage.ps1`, `scripts/` | PowerShell 7+ required, runs as Admin | +| Docker | `docker-compose.yml` (dev), `docker-compose.server.yml` (prod, host network) | + +### State files (`data/`) + +All JSON. Do not change their structure without updating both backend and JS consumers: +- `client.json` — active sing-box config +- `subscription.json` — subscription URL + selected server +- `fallback.json` — fallback proxy settings +- `proxy_enabled.json` — on/off toggle +- `start_time.json` — uptime timestamp +- `hwid` — immutable device ID (16-char hex), generated once + +## Build and Run + +```powershell +# Docker (dev, bridged network) +docker compose up -d # starts on localhost:3456 + 8080 +docker compose up -d --build # rebuild after changes + +# Docker (Linux VPS, host network for UDP) +docker compose -f docker-compose.server.yml up -d + +# Logs +docker logs -f sing-proxy + +# Windows native (PowerShell 7, Admin) +.\manage.ps1 + +# Backend dev (local) +cd web/api +npm install +npm run start:dev +``` + +Environment variables: `PORT` (3456), `PROXY_PORT` (8080), `RELOAD_PORT` (9090), `PROXY_BIND_IP` (0.0.0.0). + +## Conventions + +### Code style +- **TypeScript**: NestJS conventions — modules, controllers, services. `camelCase` for methods, `PascalCase` for classes +- **PowerShell**: `PascalCase` functions (e.g., `Write-Success`, `Manage-ScheduledTask`) +- **JSON keys**: `camelCase` (e.g., `serverPort`, `selectedServer`) +- **HTML element IDs**: `camelCase` (e.g., `subUrlInput`, `fallbackToggle`) + +### Adding features +- New API endpoint → controller in `web/api/src/proxy/proxy.controller.ts` + JS call in `web/static/js/app.js` +- Business logic → `web/api/src/proxy/proxy.service.ts` +- VLESS config changes → `web/api/src/vless/vless.service.ts` +- Persistent state → `web/api/src/storage/storage.service.ts` (JSON file I/O) +- Network utilities → `web/api/src/network/network.service.ts` +- Windows scripts → `scripts/setup-*.ps1`, shared helpers in `scripts/lib/` + +### Backend module structure +``` +web/api/src/ + main.ts — Bootstrap & static assets + app.module.ts — Root module + config/config.ts — Environment configuration + storage/ — JSON file persistence + HWID + vless/ — VLESS URL parsing + sing-box config generation + network/ — TCP latency + proxy performance measurement + proxy/ — API controller + business logic service +``` + +### VLESS handling +- Parsing is strict: requires `vless://uuid@host:port?pbk=...&sid=...` format (REALITY params mandatory) +- Subscription URLs must be `http://` or `https://` only + +## Pitfalls + +- **Windows Docker cannot use `network_mode: host`** — UDP (Discord voice, games) won't work in Docker on Windows. Use native sing-box via `manage.ps1` instead. +- **Port 9090 is internal only** — used for reload triggers via netcat, never expose externally. +- **`hwid` is immutable** — after first generation, changing it requires manual file deletion. +- **DOS line endings** — the Dockerfile runs `dos2unix` on shell scripts. Keep this in place. +- **sing-box needs a config before starting** — apply config via the web UI first; it won't bootstrap empty. +- **No test suite exists** — validate changes manually via Docker. +- **NestJS build required** — the Dockerfile runs `npm ci && npm run build` during image build. For local dev use `npm run start:dev`. diff --git a/docker/Dockerfile.singbox b/docker/Dockerfile.singbox index de257dd..2d1fd12 100644 --- a/docker/Dockerfile.singbox +++ b/docker/Dockerfile.singbox @@ -2,7 +2,7 @@ FROM alpine:3.20 ARG SINGBOX_VER=1.12.13 # Устанавливаем зависимости, включая dos2unix для исправления скриптов -RUN apk add --no-cache curl ca-certificates tar jq bash coreutils netcat-openbsd python3 dos2unix && update-ca-certificates +RUN apk add --no-cache curl ca-certificates tar jq bash coreutils netcat-openbsd nodejs npm dos2unix && update-ca-certificates # Автоматическое определение архитектуры и установка sing-box RUN ARCH=$(uname -m) && \ @@ -18,6 +18,11 @@ RUN ARCH=$(uname -m) && \ COPY --chown=suser:suser docker/entrypoint.sh /app/ COPY --chown=suser:suser web/ /app/web/ +# Собираем NestJS бэкенд +WORKDIR /app/web/api +RUN npm ci && npm run build && npm prune --omit=dev +WORKDIR /app + # Исправляем окончания строк (важно для Windows пользователей) и даем права на запуск RUN dos2unix /app/*.sh && chmod +x /app/entrypoint.sh diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 8a1e019..024dcd1 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -41,7 +41,7 @@ start_singbox # Start Web UI Server with configurable port echo "$(date): Starting Web UI on port $PORT..." -PORT=$PORT PROXY_PORT=$PROXY_PORT python3 /app/web/server.py & +PORT=$PORT PROXY_PORT=$PROXY_PORT node /app/web/api/dist/main.js & WEBUI_PID=$! # HTTP Control Server (Simple Netcat loop) diff --git a/web/api/.gitignore b/web/api/.gitignore new file mode 100644 index 0000000..b947077 --- /dev/null +++ b/web/api/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/web/api/nest-cli.json b/web/api/nest-cli.json new file mode 100644 index 0000000..f9aa683 --- /dev/null +++ b/web/api/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/web/api/package-lock.json b/web/api/package-lock.json new file mode 100644 index 0000000..348393e --- /dev/null +++ b/web/api/package-lock.json @@ -0,0 +1,6294 @@ +{ + "name": "vpn-proxy-api", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vpn-proxy-api", + "version": "1.0.0", + "dependencies": { + "@nestjs/common": "^10.4.0", + "@nestjs/core": "^10.4.0", + "@nestjs/platform-express": "^10.4.0", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@nestjs/cli": "^10.4.0", + "@types/express": "^4.17.21", + "@types/node": "^22.0.0", + "typescript": "^5.7.0" + } + }, + "node_modules/@ljharb/through": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.14.tgz", + "integrity": "sha512-ajBvlKpWucBB17FuQYUShqpqy8GRgYEpJW0vWJbUu1CV9lWyrDCapy0lScU8T8Z6qn49sSwJB3+M+evYIdGg+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@nestjs/cli": { + "version": "10.4.9", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.9.tgz", + "integrity": "sha512-s8qYd97bggqeK7Op3iD49X2MpFtW4LVNLAwXFkfbRxKME6IYT7X0muNTJ2+QfI8hpbNx9isWkrLWIp+g5FOhiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "17.3.11", + "@angular-devkit/schematics": "17.3.11", + "@angular-devkit/schematics-cli": "17.3.11", + "@nestjs/schematics": "^10.0.1", + "chalk": "4.1.2", + "chokidar": "3.6.0", + "cli-table3": "0.6.5", + "commander": "4.1.1", + "fork-ts-checker-webpack-plugin": "9.0.2", + "glob": "10.4.5", + "inquirer": "8.2.6", + "node-emoji": "1.11.0", + "ora": "5.4.1", + "tree-kill": "1.2.2", + "tsconfig-paths": "4.2.0", + "tsconfig-paths-webpack-plugin": "4.2.0", + "typescript": "5.7.2", + "webpack": "5.97.1", + "webpack-node-externals": "3.0.0" + }, + "bin": { + "nest": "bin/nest.js" + }, + "engines": { + "node": ">= 16.14" + }, + "peerDependencies": { + "@swc/cli": "^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0", + "@swc/core": "^1.3.62" + }, + "peerDependenciesMeta": { + "@swc/cli": { + "optional": true + }, + "@swc/core": { + "optional": true + } + } + }, + "node_modules/@nestjs/cli/node_modules/@angular-devkit/core": { + "version": "17.3.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.11.tgz", + "integrity": "sha512-vTNDYNsLIWpYk2I969LMQFH29GTsLzxNk/0cLw5q56ARF0v5sIWfHYwGTS88jdDqIpuuettcSczbxeA7EuAmqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.1", + "picomatch": "4.0.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@nestjs/cli/node_modules/@angular-devkit/schematics": { + "version": "17.3.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.11.tgz", + "integrity": "sha512-I5wviiIqiFwar9Pdk30Lujk8FczEEc18i22A5c6Z9lbmhPQdTroDnEQdsfXjy404wPe8H62s0I15o4pmMGfTYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "17.3.11", + "jsonc-parser": "3.2.1", + "magic-string": "0.30.8", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@nestjs/cli/node_modules/@angular-devkit/schematics-cli": { + "version": "17.3.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-17.3.11.tgz", + "integrity": "sha512-kcOMqp+PHAKkqRad7Zd7PbpqJ0LqLaNZdY1+k66lLWmkEBozgq8v4ASn/puPWf9Bo0HpCiK+EzLf0VHE8Z/y6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "17.3.11", + "@angular-devkit/schematics": "17.3.11", + "ansi-colors": "4.1.3", + "inquirer": "9.2.15", + "symbol-observable": "4.0.0", + "yargs-parser": "21.1.1" + }, + "bin": { + "schematics": "bin/schematics.js" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@nestjs/cli/node_modules/@angular-devkit/schematics-cli/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/@angular-devkit/schematics-cli/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@nestjs/cli/node_modules/@angular-devkit/schematics-cli/node_modules/inquirer": { + "version": "9.2.15", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.15.tgz", + "integrity": "sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ljharb/through": "^2.3.12", + "ansi-escapes": "^4.3.2", + "chalk": "^5.3.0", + "cli-cursor": "^3.1.0", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "figures": "^3.2.0", + "lodash": "^4.17.21", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@nestjs/cli/node_modules/@angular-devkit/schematics-cli/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/@angular-devkit/schematics-cli/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@nestjs/cli/node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@nestjs/cli/node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@nestjs/cli/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@nestjs/cli/node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/cli/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@nestjs/cli/node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@nestjs/cli/node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@nestjs/cli/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nestjs/cli/node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nestjs/cli/node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@nestjs/cli/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@nestjs/cli/node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@nestjs/cli/node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@nestjs/cli/node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@nestjs/cli/node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@nestjs/cli/node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@nestjs/cli/node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@nestjs/cli/node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@nestjs/cli/node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@nestjs/cli/node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@nestjs/cli/node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@nestjs/cli/node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@nestjs/cli/node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/@nestjs/cli/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@nestjs/cli/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@nestjs/cli/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/@nestjs/cli/node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@nestjs/cli/node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/cli/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@nestjs/cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@nestjs/cli/node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nestjs/cli/node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@nestjs/cli/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@nestjs/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/@nestjs/cli/node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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" + }, + "node_modules/@nestjs/cli/node_modules/baseline-browser-mapping": { + "version": "2.10.7", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.7.tgz", + "integrity": "sha512-1ghYO3HnxGec0TCGBXiDLVns4eCSx4zJpxnHrlqFQajmhfKMQBzUGDdkMK7fUW7PTHTeLf+j87aTuKuuwWzMGw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@nestjs/cli/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/@nestjs/cli/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@nestjs/cli/node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/@nestjs/cli/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/@nestjs/cli/node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@nestjs/cli/node_modules/caniuse-lite": { + "version": "1.0.30001778", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001778.tgz", + "integrity": "sha512-PN7uxFL+ExFJO61aVmP1aIEG4i9whQd4eoSCebav62UwDyp5OHh06zN4jqKSMePVgxHifCw1QJxdRkA1Pisekg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/@nestjs/cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@nestjs/cli/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/@nestjs/cli/node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/@nestjs/cli/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/cli/node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/@nestjs/cli/node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/@nestjs/cli/node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/@nestjs/cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@nestjs/cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@nestjs/cli/node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@nestjs/cli/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nestjs/cli/node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@nestjs/cli/node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/cli/node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/electron-to-chromium": { + "version": "1.5.313", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", + "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==", + "dev": true, + "license": "ISC" + }, + "node_modules/@nestjs/cli/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/enhanced-resolve": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@nestjs/cli/node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/@nestjs/cli/node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@nestjs/cli/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@nestjs/cli/node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@nestjs/cli/node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@nestjs/cli/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@nestjs/cli/node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/@nestjs/cli/node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@nestjs/cli/node_modules/fork-ts-checker-webpack-plugin": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.0.2.tgz", + "integrity": "sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cosmiconfig": "^8.2.0", + "deepmerge": "^4.2.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", + "minimatch": "^3.0.4", + "node-abort-controller": "^3.0.1", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">=12.13.0", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "typescript": ">3.6.0", + "webpack": "^5.11.0" + } + }, + "node_modules/@nestjs/cli/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@nestjs/cli/node_modules/fs-monkey": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", + "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/@nestjs/cli/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@nestjs/cli/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@nestjs/cli/node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/@nestjs/cli/node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@nestjs/cli/node_modules/glob/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/@nestjs/cli/node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@nestjs/cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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": "BSD-3-Clause" + }, + "node_modules/@nestjs/cli/node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/cli/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@nestjs/cli/node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@nestjs/cli/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@nestjs/cli/node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/@nestjs/cli/node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/cli/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/@nestjs/cli/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/@nestjs/cli/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/@nestjs/cli/node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@nestjs/cli/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@nestjs/cli/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@nestjs/cli/node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@nestjs/cli/node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/cli/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@nestjs/cli/node_modules/magic-string": { + "version": "0.30.8", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", + "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@nestjs/cli/node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@nestjs/cli/node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/cli/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/cli/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@nestjs/cli/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@nestjs/cli/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@nestjs/cli/node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@nestjs/cli/node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/node-emoji": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/@nestjs/cli/node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@nestjs/cli/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/cli/node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/cli/node_modules/ora/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/@nestjs/cli/node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@nestjs/cli/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/cli/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@nestjs/cli/node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/@nestjs/cli/node_modules/picomatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", + "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@nestjs/cli/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@nestjs/cli/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@nestjs/cli/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/@nestjs/cli/node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@nestjs/cli/node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@nestjs/cli/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@nestjs/cli/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@nestjs/cli/node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/@nestjs/cli/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@nestjs/cli/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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" + }, + "node_modules/@nestjs/cli/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@nestjs/cli/node_modules/schema-utils/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@nestjs/cli/node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/@nestjs/cli/node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nestjs/cli/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@nestjs/cli/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nestjs/cli/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/@nestjs/cli/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@nestjs/cli/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/@nestjs/cli/node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@nestjs/cli/node_modules/terser": { + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nestjs/cli/node_modules/terser-webpack-plugin": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/@nestjs/cli/node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@nestjs/cli/node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/@nestjs/cli/node_modules/tsconfig-paths-webpack-plugin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz", + "integrity": "sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tapable": "^2.2.1", + "tsconfig-paths": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@nestjs/cli/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@nestjs/cli/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/cli/node_modules/typescript": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@nestjs/cli/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@nestjs/cli/node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/@nestjs/cli/node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/@nestjs/cli/node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@nestjs/cli/node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/@nestjs/cli/node_modules/webpack": { + "version": "5.97.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", + "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/@nestjs/cli/node_modules/webpack-node-externals": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", + "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@nestjs/cli/node_modules/webpack-sources": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@nestjs/cli/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nestjs/cli/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@nestjs/cli/node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@nestjs/cli/node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/cli/node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@nestjs/cli/node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/cli/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/@nestjs/common": { + "version": "10.4.22", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.22.tgz", + "integrity": "sha512-fxJ4v85nDHaqT1PmfNCQ37b/jcv2OojtXTaK1P2uAXhzLf9qq6WNUOFvxBrV4fhQek1EQoT1o9oj5xAZmv3NRw==", + "license": "MIT", + "dependencies": { + "file-type": "20.4.1", + "iterare": "1.2.1", + "tslib": "2.8.1", + "uid": "2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "class-transformer": "*", + "class-validator": "*", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/common/node_modules/@borewit/text-codec": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz", + "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@nestjs/common/node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/common/node_modules/@tokenizer/inflate": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", + "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "fflate": "^0.8.2", + "token-types": "^6.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@nestjs/common/node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, + "node_modules/@nestjs/common/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@nestjs/common/node_modules/file-type": { + "version": "20.4.1", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.4.1.tgz", + "integrity": "sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ==", + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.2.6", + "strtok3": "^10.2.0", + "token-types": "^6.0.0", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/@nestjs/common/node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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": "BSD-3-Clause" + }, + "node_modules/@nestjs/common/node_modules/iterare": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", + "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", + "license": "ISC", + "engines": { + "node": ">=6" + } + }, + "node_modules/@nestjs/common/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/@nestjs/common/node_modules/strtok3": { + "version": "10.3.4", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", + "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@nestjs/common/node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@nestjs/common/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@nestjs/common/node_modules/uid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", + "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", + "license": "MIT", + "dependencies": { + "@lukeed/csprng": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/common/node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/core": { + "version": "10.4.22", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.22.tgz", + "integrity": "sha512-6IX9+VwjiKtCjx+mXVPncpkQ5ZjKfmssOZPFexmT+6T9H9wZ3svpYACAo7+9e7Nr9DZSoRZw3pffkJP7Z0UjaA==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@nuxtjs/opencollective": "0.3.2", + "fast-safe-stringify": "2.1.1", + "iterare": "1.2.1", + "path-to-regexp": "3.3.0", + "tslib": "2.8.1", + "uid": "2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/microservices": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@nestjs/websockets": "^10.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/core/node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/core/node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, + "node_modules/@nestjs/core/node_modules/iterare": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", + "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", + "license": "ISC", + "engines": { + "node": ">=6" + } + }, + "node_modules/@nestjs/core/node_modules/path-to-regexp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "license": "MIT" + }, + "node_modules/@nestjs/core/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@nestjs/core/node_modules/uid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", + "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", + "license": "MIT", + "dependencies": { + "@lukeed/csprng": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/platform-express": { + "version": "10.4.22", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.22.tgz", + "integrity": "sha512-ySSq7Py/DFozzZdNDH67m/vHoeVdphDniWBnl6q5QVoXldDdrZIHLXLRMPayTDh5A95nt7jjJzmD4qpTbNQ6tA==", + "license": "MIT", + "peer": true, + "dependencies": { + "body-parser": "1.20.4", + "cors": "2.8.5", + "express": "4.22.1", + "multer": "2.0.2", + "tslib": "2.8.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0" + } + }, + "node_modules/@nestjs/platform-express/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "node_modules/@nestjs/platform-express/node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/@nestjs/platform-express/node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/@nestjs/platform-express/node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/@nestjs/platform-express/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nestjs/platform-express/node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@nestjs/platform-express/node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@nestjs/platform-express/node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/@nestjs/platform-express/node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/@nestjs/platform-express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@nestjs/platform-express/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nestjs/platform-express/node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@nestjs/platform-express/node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/@nestjs/platform-express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nestjs/platform-express/node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@nestjs/platform-express/node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@nestjs/platform-express/node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@nestjs/platform-express/node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/@nestjs/platform-express/node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@nestjs/platform-express/node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nestjs/platform-express/node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@nestjs/platform-express/node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@nestjs/platform-express/node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@nestjs/platform-express/node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@nestjs/platform-express/node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@nestjs/platform-express/node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@nestjs/platform-express/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@nestjs/platform-express/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@nestjs/platform-express/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/@nestjs/platform-express/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/@nestjs/platform-express/node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@nestjs/platform-express/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/platform-express/node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@nestjs/platform-express/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@nestjs/platform-express/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/@nestjs/platform-express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/@nestjs/platform-express/node_modules/multer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "mkdirp": "^0.5.6", + "object-assign": "^4.1.1", + "type-is": "^1.6.18", + "xtend": "^4.0.2" + }, + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/@nestjs/platform-express/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@nestjs/platform-express/node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@nestjs/platform-express/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nestjs/platform-express/node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nestjs/platform-express/node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/@nestjs/platform-express/node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/@nestjs/platform-express/node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@nestjs/platform-express/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nestjs/platform-express/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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" + }, + "node_modules/@nestjs/platform-express/node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/@nestjs/platform-express/node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@nestjs/platform-express/node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/@nestjs/platform-express/node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@nestjs/platform-express/node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/@nestjs/platform-express/node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@nestjs/platform-express/node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@nestjs/platform-express/node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@nestjs/platform-express/node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@nestjs/platform-express/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nestjs/platform-express/node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@nestjs/platform-express/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/@nestjs/platform-express/node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@nestjs/platform-express/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/@nestjs/platform-express/node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nestjs/platform-express/node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/@nestjs/platform-express/node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nestjs/platform-express/node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/@nestjs/schematics": { + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.2.3.tgz", + "integrity": "sha512-4e8gxaCk7DhBxVUly2PjYL4xC2ifDFexCqq1/u4TtivLGXotVk0wHdYuPYe1tHTHuR1lsOkRbfOCpkdTnigLVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "17.3.11", + "@angular-devkit/schematics": "17.3.11", + "comment-json": "4.2.5", + "jsonc-parser": "3.3.1", + "pluralize": "8.0.0" + }, + "peerDependencies": { + "typescript": ">=4.8.2" + } + }, + "node_modules/@nestjs/schematics/node_modules/@angular-devkit/core": { + "version": "17.3.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.11.tgz", + "integrity": "sha512-vTNDYNsLIWpYk2I969LMQFH29GTsLzxNk/0cLw5q56ARF0v5sIWfHYwGTS88jdDqIpuuettcSczbxeA7EuAmqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.1", + "picomatch": "4.0.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@nestjs/schematics/node_modules/@angular-devkit/core/node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/schematics/node_modules/@angular-devkit/schematics": { + "version": "17.3.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.11.tgz", + "integrity": "sha512-I5wviiIqiFwar9Pdk30Lujk8FczEEc18i22A5c6Z9lbmhPQdTroDnEQdsfXjy404wPe8H62s0I15o4pmMGfTYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "17.3.11", + "jsonc-parser": "3.2.1", + "magic-string": "0.30.8", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@nestjs/schematics/node_modules/@angular-devkit/schematics/node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/schematics/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/schematics/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@nestjs/schematics/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@nestjs/schematics/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/schematics/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@nestjs/schematics/node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/schematics/node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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" + }, + "node_modules/@nestjs/schematics/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/@nestjs/schematics/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/@nestjs/schematics/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@nestjs/schematics/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/schematics/node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/schematics/node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/@nestjs/schematics/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@nestjs/schematics/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/schematics/node_modules/comment-json": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.5.tgz", + "integrity": "sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1", + "has-own-prop": "^2.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@nestjs/schematics/node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/schematics/node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/schematics/node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@nestjs/schematics/node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/schematics/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/schematics/node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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": "BSD-3-Clause" + }, + "node_modules/@nestjs/schematics/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@nestjs/schematics/node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/schematics/node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/schematics/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/schematics/node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/schematics/node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/schematics/node_modules/magic-string": { + "version": "0.30.8", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", + "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@nestjs/schematics/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@nestjs/schematics/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/schematics/node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nestjs/schematics/node_modules/picomatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", + "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@nestjs/schematics/node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@nestjs/schematics/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@nestjs/schematics/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@nestjs/schematics/node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@nestjs/schematics/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/schematics/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@nestjs/schematics/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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" + }, + "node_modules/@nestjs/schematics/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@nestjs/schematics/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nestjs/schematics/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/@nestjs/schematics/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/schematics/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/schematics/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@nestjs/schematics/node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/@nestjs/schematics/node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/schematics/node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/@nuxtjs/opencollective": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz", + "integrity": "sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "consola": "^2.15.0", + "node-fetch": "^2.6.1" + }, + "bin": { + "opencollective": "bin/opencollective.js" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/@nuxtjs/opencollective/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@nuxtjs/opencollective/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@nuxtjs/opencollective/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@nuxtjs/opencollective/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/@nuxtjs/opencollective/node_modules/consola": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", + "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", + "license": "MIT" + }, + "node_modules/@nuxtjs/opencollective/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nuxtjs/opencollective/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@types/body-parser": { + "dev": true + }, + "node_modules/@types/eslint": { + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", + "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind/node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bind/node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bind/node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bind/node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bind/node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bind/node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind/node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind/node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bind/node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind/node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind/node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bind/node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-data-property/node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-data-property/node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-data-property/node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/external-editor/node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true, + "license": "MIT" + }, + "node_modules/external-editor/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/external-editor/node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/has-own-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", + "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors/node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/inquirer": { + "version": "8.2.6", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", + "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^6.0.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/inquirer/node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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" + }, + "node_modules/inquirer/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/inquirer/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/inquirer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10" + } + }, + "node_modules/inquirer/node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/inquirer/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/inquirer/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/inquirer/node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/inquirer/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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": "BSD-3-Clause" + }, + "node_modules/inquirer/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/inquirer/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/inquirer/node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/inquirer/node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inquirer/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/inquirer/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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" + }, + "node_modules/inquirer/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/inquirer/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/inquirer/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/inquirer/node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/inquirer/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/rxjs/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-length/node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-length/node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-length/node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-length/node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-length/node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-length/node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/set-function-length/node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/set-function-length/node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-length/node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/set-function-length/node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/set-function-length/node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-length/node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tsconfig-paths/node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/web/api/package.json b/web/api/package.json new file mode 100644 index 0000000..c84e128 --- /dev/null +++ b/web/api/package.json @@ -0,0 +1,25 @@ +{ + "name": "vpn-proxy-api", + "version": "1.0.0", + "description": "VPN-Proxy backend API", + "private": true, + "scripts": { + "build": "nest build", + "start": "nest start", + "start:dev": "nest start --watch", + "start:prod": "node dist/main" + }, + "dependencies": { + "@nestjs/common": "^10.4.0", + "@nestjs/core": "^10.4.0", + "@nestjs/platform-express": "^10.4.0", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@nestjs/cli": "^10.4.0", + "@types/express": "^4.17.21", + "@types/node": "^22.0.0", + "typescript": "^5.7.0" + } +} diff --git a/web/api/src/app.module.ts b/web/api/src/app.module.ts new file mode 100644 index 0000000..11260d1 --- /dev/null +++ b/web/api/src/app.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { ProxyModule } from './proxy/proxy.module'; + +@Module({ + imports: [ProxyModule], +}) +export class AppModule {} diff --git a/web/api/src/config/config.ts b/web/api/src/config/config.ts new file mode 100644 index 0000000..f313a01 --- /dev/null +++ b/web/api/src/config/config.ts @@ -0,0 +1,33 @@ +import * as path from 'path'; + +// After compilation: dist/config/config.js +// __dirname = /dist/config +// Go up: dist/config -> dist -> api -> web -> project root +const API_DIR = path.resolve(__dirname, '..', '..'); +const WEB_DIR = path.resolve(API_DIR, '..'); +const BASE_DIR = path.resolve(WEB_DIR, '..'); +const DATA_DIR = path.join(BASE_DIR, 'data'); + +export const config = { + port: parseInt(process.env.PORT || '3456', 10), + proxyPort: parseInt(process.env.PROXY_PORT || '8080', 10), + reloadPort: parseInt(process.env.RELOAD_PORT || '9090', 10), + proxyBindIp: process.env.PROXY_BIND_IP || '0.0.0.0', + appName: 'VPN-Proxy-Control by Dokril', + + webDir: WEB_DIR, + baseDir: BASE_DIR, + dataDir: DATA_DIR, + configFile: path.join(DATA_DIR, 'client.json'), + hwidFile: path.join(DATA_DIR, 'hwid'), + subscriptionFile: path.join(DATA_DIR, 'subscription.json'), + fallbackFile: path.join(DATA_DIR, 'fallback.json'), + proxyEnabledFile: path.join(DATA_DIR, 'proxy_enabled.json'), + startTimeFile: path.join(DATA_DIR, 'start_time.json'), + + defaultFallback: { + enabled: false, + host: '192.168.50.111', + port: 8080, + }, +}; diff --git a/web/api/src/main.ts b/web/api/src/main.ts new file mode 100644 index 0000000..028f32e --- /dev/null +++ b/web/api/src/main.ts @@ -0,0 +1,23 @@ +import { NestFactory } from '@nestjs/core'; +import { NestExpressApplication } from '@nestjs/platform-express'; +import * as path from 'path'; +import { AppModule } from './app.module'; +import { config } from './config/config'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + + // Serve static files from web/static/ + app.useStaticAssets(path.join(config.webDir, 'static'), { + prefix: '/static/', + }); + + app.enableCors(); + + await app.listen(config.port); + console.log(`[WebUI] Server started on port ${config.port}`); + console.log( + `[WebUI] Open http://localhost:${config.port} in your browser`, + ); +} +bootstrap(); diff --git a/web/api/src/network/network.module.ts b/web/api/src/network/network.module.ts new file mode 100644 index 0000000..7ec9b81 --- /dev/null +++ b/web/api/src/network/network.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { NetworkService } from './network.service'; + +@Module({ + providers: [NetworkService], + exports: [NetworkService], +}) +export class NetworkModule {} diff --git a/web/api/src/network/network.service.ts b/web/api/src/network/network.service.ts new file mode 100644 index 0000000..a2dc3e8 --- /dev/null +++ b/web/api/src/network/network.service.ts @@ -0,0 +1,294 @@ +import { Injectable } from '@nestjs/common'; +import * as net from 'net'; +import * as http from 'http'; +import * as tls from 'tls'; +import { config } from '../config/config'; + +@Injectable() +export class NetworkService { + measureTcpLatency( + host: string, + port: number, + timeout = 2000, + ): Promise { + return new Promise((resolve) => { + const start = Date.now(); + const socket = new net.Socket(); + socket.setTimeout(timeout); + + socket.on('connect', () => { + const latency = Date.now() - start; + socket.destroy(); + resolve(latency); + }); + + socket.on('error', () => { + socket.destroy(); + resolve(-1); + }); + + socket.on('timeout', () => { + socket.destroy(); + resolve(-1); + }); + + socket.connect(port, host); + }); + } + + async measureProxyPerformance( + enableSpeedTest = false, + ): Promise> { + const result: Record = {}; + + // 1. Measure Latency + try { + const start = Date.now(); + await this.httpViaProxy('http://www.gstatic.com/generate_204', { + headers: { 'User-Agent': 'singbox-test' }, + timeout: 5000, + }); + result.latency = `${Date.now() - start}ms`; + } catch { + result.latency = 'Error'; + } + + // 2. Get Public IP (IPv4) + try { + const ipRes = await this.httpViaProxy('http://v4.ident.me', { + headers: { 'User-Agent': 'curl/7.68.0' }, + timeout: 5000, + }); + result.ip = ipRes.body.trim(); + } catch { + try { + const ipRes = await this.httpViaProxy('http://api.ipify.org', { + headers: { 'User-Agent': 'curl/7.68.0' }, + timeout: 5000, + }); + result.ip = ipRes.body.trim(); + } catch { + result.ip = 'Unknown'; + } + } + + // 3. Speed test + if (enableSpeedTest) { + const testFiles = [ + { url: 'https://speedtest.selectel.ru/100MB', sizeMb: 100 }, + { url: 'https://speedtest.selectel.ru/1GB', sizeMb: 1000 }, + ]; + + let speedMbps = 0; + + for (const testFile of testFiles) { + try { + console.log(`[WebUI] Testing speed with: ${testFile.url}`); + const start = Date.now(); + const maxBytes = 25 * 1024 * 1024; + const minDurationMs = 2000; + + const downloaded = await this.downloadViaProxy( + testFile.url, + maxBytes, + minDurationMs, + ); + + const duration = (Date.now() - start) / 1000; + if (duration > 0.1 && downloaded > 0) { + speedMbps = + Math.round( + ((downloaded * 8) / (1000 * 1000) / duration) * 10, + ) / 10; + console.log( + `[WebUI] Speed test: downloaded ${(downloaded / (1024 * 1024)).toFixed(1)}MB in ${duration.toFixed(1)}s = ${speedMbps} Mbps`, + ); + break; + } + } catch (e) { + console.log( + `[WebUI] Speed test failed for ${testFile.url}: ${e}`, + ); + continue; + } + } + + result.speed = `${speedMbps} Mbps`; + } + + return result; + } + + /** Send HTTP request through local HTTP proxy (for http:// URLs) */ + private httpViaProxy( + targetUrl: string, + options: { + headers?: Record; + timeout?: number; + } = {}, + ): Promise<{ statusCode: number; body: string }> { + return new Promise((resolve, reject) => { + const parsed = new URL(targetUrl); + const req = http.request( + { + hostname: '127.0.0.1', + port: config.proxyPort, + path: targetUrl, + method: 'GET', + headers: { + Host: parsed.host, + ...(options.headers || {}), + }, + timeout: options.timeout || 5000, + }, + (res) => { + let body = ''; + res.on('data', (chunk: Buffer) => (body += chunk.toString())); + res.on('end', () => + resolve({ statusCode: res.statusCode, body }), + ); + }, + ); + + req.on('error', reject); + req.on('timeout', () => { + req.destroy(); + reject(new Error('Timeout')); + }); + req.end(); + }); + } + + /** Download through proxy via CONNECT tunnel (for https:// URLs) */ + private downloadViaProxy( + targetUrl: string, + maxBytes: number, + minDurationMs: number, + ): Promise { + return new Promise((resolve, reject) => { + const parsed = new URL(targetUrl); + const isHttps = parsed.protocol === 'https:'; + + if (!isHttps) { + // HTTP download through proxy + const req = http.request( + { + hostname: '127.0.0.1', + port: config.proxyPort, + path: targetUrl, + method: 'GET', + headers: { Host: parsed.host }, + timeout: 30000, + }, + (res) => { + let downloaded = 0; + const start = Date.now(); + + res.on('data', (chunk: Buffer) => { + downloaded += chunk.length; + const elapsed = Date.now() - start; + if ( + downloaded >= maxBytes || + (elapsed >= minDurationMs && downloaded >= 2 * 1024 * 1024) + ) { + res.destroy(); + resolve(downloaded); + } + }); + + res.on('end', () => resolve(downloaded)); + res.on('error', reject); + }, + ); + + req.on('error', reject); + req.end(); + return; + } + + // HTTPS: use CONNECT tunnel through proxy + const proxyReq = http.request({ + hostname: '127.0.0.1', + port: config.proxyPort, + method: 'CONNECT', + path: `${parsed.hostname}:${parsed.port || 443}`, + }); + + let resolved = false; + const finish = (bytes: number) => { + if (!resolved) { + resolved = true; + resolve(bytes); + } + }; + const fail = (err: Error) => { + if (!resolved) { + resolved = true; + reject(err); + } + }; + + proxyReq.on('connect', (proxyRes, socket) => { + if (proxyRes.statusCode !== 200) { + socket.destroy(); + fail(new Error(`CONNECT failed: ${proxyRes.statusCode}`)); + return; + } + + const tlsSocket = tls.connect( + { socket, servername: parsed.hostname }, + () => { + // Send raw HTTP request over TLS tunnel + tlsSocket.write( + `GET ${parsed.pathname}${parsed.search} HTTP/1.1\r\n` + + `Host: ${parsed.hostname}\r\n` + + `User-Agent: singbox-speedtest\r\n` + + `Connection: close\r\n\r\n`, + ); + + let downloaded = 0; + let headersParsed = false; + let headerBuffer = ''; + const start = Date.now(); + + tlsSocket.on('data', (chunk: Buffer) => { + if (!headersParsed) { + headerBuffer += chunk.toString('binary'); + const headerEnd = headerBuffer.indexOf('\r\n\r\n'); + if (headerEnd !== -1) { + headersParsed = true; + const bodyStart = headerEnd + 4; + downloaded += + Buffer.byteLength(headerBuffer.slice(bodyStart), 'binary'); + } + } else { + downloaded += chunk.length; + } + + const elapsed = Date.now() - start; + if ( + downloaded >= maxBytes || + (elapsed >= minDurationMs && downloaded >= 2 * 1024 * 1024) + ) { + tlsSocket.destroy(); + finish(downloaded); + } + }); + + tlsSocket.on('end', () => finish(downloaded)); + tlsSocket.on('error', fail); + }, + ); + + tlsSocket.on('error', fail); + }); + + proxyReq.on('error', fail); + proxyReq.setTimeout(30000, () => { + proxyReq.destroy(); + fail(new Error('CONNECT timeout')); + }); + proxyReq.end(); + }); + } +} diff --git a/web/api/src/proxy/proxy.controller.ts b/web/api/src/proxy/proxy.controller.ts new file mode 100644 index 0000000..8bb6358 --- /dev/null +++ b/web/api/src/proxy/proxy.controller.ts @@ -0,0 +1,315 @@ +import { + Controller, + Get, + Post, + Body, + Query, + Res, + HttpException, + HttpStatus, +} from '@nestjs/common'; +import { Response } from 'express'; +import * as fs from 'fs'; +import * as path from 'path'; +import { config } from '../config/config'; +import { StorageService } from '../storage/storage.service'; +import { VlessService } from '../vless/vless.service'; +import { NetworkService } from '../network/network.service'; +import { ProxyService } from './proxy.service'; + +@Controller() +export class ProxyController { + constructor( + private readonly storage: StorageService, + private readonly vless: VlessService, + private readonly network: NetworkService, + private readonly proxyService: ProxyService, + ) {} + + // --- Static / Index --- + + @Get('/') + serveIndex(@Res() res: Response) { + const indexPath = path.join(config.webDir, 'index.html'); + if (!fs.existsSync(indexPath)) { + return res.status(404).send('index.html not found'); + } + + try { + let content = fs.readFileSync(indexPath, 'utf-8'); + const normalizedWebDir = path.resolve(config.webDir); + + content = content.replace( + //g, + (_, includePath: string) => { + const fullPath = path.resolve(normalizedWebDir, includePath); + // Path traversal protection + if ( + !fullPath.startsWith(normalizedWebDir + path.sep) && + fullPath !== normalizedWebDir + ) { + return ``; + } + if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile()) { + return ``; + } + return fs.readFileSync(fullPath, 'utf-8'); + }, + ); + + res.type('html').send(content); + } catch (e) { + res.status(500).send(`Error serving index: ${e}`); + } + } + + // --- Status --- + + @Get('/status') + getStatus() { + const configExists = this.storage.configFileExists(); + let currentTag: string | null = null; + let currentServer: string | null = null; + const proxyEnabled = this.storage.loadProxyEnabled(); + + if (configExists) { + try { + const cfg = this.storage.readConfigFile(); + for (const outbound of cfg.outbounds || []) { + if (outbound.type === 'vless') { + currentTag = outbound.tag || 'unknown'; + currentServer = outbound.server || 'unknown'; + break; + } + } + } catch {} + } + + return { + active: configExists && proxyEnabled, + tag: currentTag, + server: currentServer, + proxyPort: config.proxyPort, + proxyEnabled, + startTime: + configExists && proxyEnabled ? this.storage.loadStartTime() : 0, + }; + } + + // --- Subscription --- + + @Get('/subscription') + getSubscription() { + const sub = this.storage.loadSubscription(); + if (sub) { + return { + saved: true, + url: sub.url, + selectedServer: sub.selectedServer, + userInfo: sub.userInfo, + }; + } + return { saved: false }; + } + + // --- Connection Test --- + + @Get('/test-connection') + async testConnection(@Query('speed') speed?: string) { + const enableSpeed = speed?.toLowerCase() === 'true'; + return this.network.measureProxyPerformance(enableSpeed); + } + + // --- Fallback Config --- + + @Get('/fallback-config') + getFallbackConfig() { + const fallback = this.storage.loadFallbackConfig(); + return { + enabled: fallback.enabled ?? false, + host: fallback.host ?? '192.168.50.111', + port: fallback.port ?? 8080, + }; + } + + @Post('/fallback-config') + saveFallbackConfig(@Body() body: any) { + const enabled = body.enabled ?? false; + const host = (body.host || '').trim(); + const port = parseInt(body.port, 10) || 8080; + + if (enabled && !host) { + throw new HttpException( + { success: false, error: 'Host is required' }, + HttpStatus.BAD_REQUEST, + ); + } + + this.storage.saveFallbackConfig(enabled, host, port); + const regenerated = this.proxyService.regenerateCurrentConfig(); + + return { + success: true, + message: 'Fallback config saved', + regenerated, + }; + } + + // --- Active Proxy --- + + @Get('/active-proxy') + async getActiveProxy() { + return this.proxyService.getActiveProxy(); + } + + // --- Proxy Enabled --- + + @Get('/proxy-enabled') + getProxyEnabled() { + return { enabled: this.storage.loadProxyEnabled() }; + } + + @Post('/proxy-enabled') + setProxyEnabled(@Body() body: any) { + const enabled = body.enabled ?? true; + this.storage.saveProxyEnabled(enabled); + + let regenerated = false; + + if (enabled) { + regenerated = this.proxyService.regenerateCurrentConfig(); + if (regenerated) { + this.storage.saveStartTime(Date.now() / 1000); + } + } else { + regenerated = this.proxyService.applyDirectConfig(); + this.storage.saveStartTime(0); + } + + return { success: true, enabled, regenerated }; + } + + // --- Apply VLESS URL --- + + @Post('/apply') + applyConfig(@Body() body: any) { + const url = (body.url || '').trim(); + + if (!url) { + throw new HttpException( + { success: false, error: 'URL не указан' }, + HttpStatus.BAD_REQUEST, + ); + } + + if (!url.startsWith('vless://')) { + throw new HttpException( + { + success: false, + error: 'Неверный формат. Поддерживаются только vless:// ссылки', + }, + HttpStatus.BAD_REQUEST, + ); + } + + let vlessParams; + try { + vlessParams = this.vless.parseVlessUrl(url); + } catch (e: any) { + throw new HttpException( + { + success: false, + error: `Ошибка парсинга URL: ${e.message}`, + }, + HttpStatus.BAD_REQUEST, + ); + } + + const cfg = this.vless.generateVlessConfig(vlessParams); + this.storage.writeConfigFile(cfg); + this.proxyService.triggerReload(); + this.storage.saveStartTime(Date.now() / 1000); + + return { + success: true, + message: `Конфигурация '${vlessParams.tag}' успешно применена!`, + }; + } + + // --- Fetch Subscription --- + + @Post('/fetch-subscription') + async fetchSubscription(@Body() body: any) { + const url = (body.url || '').trim(); + + if (!url) { + throw new HttpException( + { success: false, error: 'URL подписки не указан' }, + HttpStatus.BAD_REQUEST, + ); + } + + const result = await this.proxyService.fetchSubscriptionFromUrl(url); + + if (!result.success) { + throw new HttpException(result, HttpStatus.BAD_REQUEST); + } + + return result; + } + + // --- Apply Subscription --- + + @Post('/apply-subscription') + applySubscription(@Body() body: any) { + const subConfig = body.config; + const selectedTag = body.selectedServer; + const subUrl = body.subUrl; + const userInfo = body.userInfo; + + if (!subConfig) { + throw new HttpException( + { success: false, error: 'Конфигурация не указана' }, + HttpStatus.BAD_REQUEST, + ); + } + + if (!selectedTag) { + throw new HttpException( + { success: false, error: 'Сервер не выбран' }, + HttpStatus.BAD_REQUEST, + ); + } + + const result = this.proxyService.applySubscriptionConfig( + subConfig, + selectedTag, + subUrl, + userInfo, + ); + + if (!result.success) { + throw new HttpException(result, HttpStatus.BAD_REQUEST); + } + + return result; + } + + // --- Ping Target --- + + @Post('/ping-target') + async pingTarget(@Body() body: any) { + const server = body.server; + const port = parseInt(body.port, 10) || 443; + + if (!server) { + throw new HttpException( + { error: 'No server specified' }, + HttpStatus.BAD_REQUEST, + ); + } + + const latency = await this.network.measureTcpLatency(server, port); + return { latency }; + } +} diff --git a/web/api/src/proxy/proxy.module.ts b/web/api/src/proxy/proxy.module.ts new file mode 100644 index 0000000..28c12c2 --- /dev/null +++ b/web/api/src/proxy/proxy.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { StorageModule } from '../storage/storage.module'; +import { VlessModule } from '../vless/vless.module'; +import { NetworkModule } from '../network/network.module'; +import { ProxyController } from './proxy.controller'; +import { ProxyService } from './proxy.service'; + +@Module({ + imports: [StorageModule, VlessModule, NetworkModule], + controllers: [ProxyController], + providers: [ProxyService], +}) +export class ProxyModule {} diff --git a/web/api/src/proxy/proxy.service.ts b/web/api/src/proxy/proxy.service.ts new file mode 100644 index 0000000..9e870f5 --- /dev/null +++ b/web/api/src/proxy/proxy.service.ts @@ -0,0 +1,507 @@ +import { Injectable } from '@nestjs/common'; +import * as http from 'http'; +import * as https from 'https'; +import { config } from '../config/config'; +import { StorageService } from '../storage/storage.service'; +import { VlessService } from '../vless/vless.service'; +import { NetworkService } from '../network/network.service'; + +@Injectable() +export class ProxyService { + constructor( + private readonly storage: StorageService, + private readonly vless: VlessService, + private readonly network: NetworkService, + ) {} + + triggerReload(): void { + try { + const req = http.get( + `http://127.0.0.1:${config.reloadPort}/reload`, + { timeout: 3000 }, + () => {}, + ); + req.on('error', () => {}); + } catch {} + } + + /** Regenerate current config with updated fallback settings */ + regenerateCurrentConfig(): boolean { + if (!this.storage.configFileExists()) return false; + + try { + const cfg = this.storage.readConfigFile(); + const outbounds: any[] = cfg.outbounds || []; + + let vpnOutbound: any = null; + const utilityOutbounds: any[] = []; + + for (const outbound of outbounds) { + if ( + ['vless', 'vmess', 'trojan', 'shadowsocks', 'hysteria2'].includes( + outbound.type, + ) + ) { + vpnOutbound = outbound; + } else if (['direct', 'block', 'dns'].includes(outbound.type)) { + utilityOutbounds.push(outbound); + } + } + + if (!vpnOutbound) return false; + + const selectedTag = vpnOutbound.tag; + const fallback = this.storage.loadFallbackConfig(); + const fallbackEnabled = fallback.enabled; + const fallbackHost = fallback.host; + const fallbackPort = fallback.port; + + const finalOutbounds: any[] = []; + let finalTag = selectedTag; + + if (fallbackEnabled && fallbackHost) { + finalOutbounds.push({ + type: 'urltest', + tag: 'auto-select', + outbounds: ['fallback-proxy', selectedTag], + url: 'http://www.gstatic.com/generate_204', + interval: '30s', + tolerance: 9999, + }); + + finalOutbounds.push({ + type: 'http', + tag: 'fallback-proxy', + server: fallbackHost, + server_port: fallbackPort, + }); + + finalTag = 'auto-select'; + } + + finalOutbounds.push(vpnOutbound); + finalOutbounds.push(...utilityOutbounds); + + cfg.outbounds = finalOutbounds; + cfg.route.final = finalTag; + + this.storage.writeConfigFile(cfg); + this.triggerReload(); + + return true; + } catch (e) { + console.error(`[WebUI] Failed to regenerate config: ${e}`); + return false; + } + } + + /** Generate and apply direct config (bypass proxy) */ + applyDirectConfig(): boolean { + try { + const directCfg = this.vless.generateDirectConfig(); + this.storage.writeConfigFile(directCfg); + this.triggerReload(); + return true; + } catch (e) { + console.error(`[WebUI] Failed to generate direct config: ${e}`); + return false; + } + } + + /** Get active proxy chain info */ + async getActiveProxy(): Promise> { + const result: Record = { + configured: false, + fallbackEnabled: false, + fallbackHost: null, + vpnTag: null, + vpnServer: null, + activeOutbound: null, + }; + + if (!this.storage.configFileExists()) return result; + + try { + const cfg = this.storage.readConfigFile(); + const outbounds: any[] = cfg.outbounds || []; + const routeFinal = cfg.route?.final; + + result.configured = true; + + for (const outbound of outbounds) { + const outType = outbound.type; + + if (outType === 'urltest') { + result.fallbackEnabled = true; + } else if (outType === 'http' && outbound.tag === 'fallback-proxy') { + result.fallbackHost = `${outbound.server}:${outbound.server_port}`; + } else if ( + ['vless', 'vmess', 'trojan', 'shadowsocks', 'hysteria2'].includes( + outType, + ) + ) { + result.vpnTag = outbound.tag; + result.vpnServer = outbound.server; + } + } + + result.activeOutbound = routeFinal; + + // Check fallback proxy reachability + if (result.fallbackEnabled && result.fallbackHost) { + try { + const [host, portStr] = result.fallbackHost.split(':'); + const latency = await this.network.measureTcpLatency( + host, + parseInt(portStr, 10), + 1000, + ); + result.fallbackReachable = latency > 0; + result.fallbackLatency = latency > 0 ? latency : null; + } catch { + result.fallbackReachable = false; + result.fallbackLatency = null; + } + } + } catch (e) { + result.error = String(e); + } + + return result; + } + + /** Fetch subscription from URL */ + async fetchSubscriptionFromUrl(url: string): Promise> { + // Validate URL scheme (SSRF protection) + let parsed: URL; + try { + parsed = new URL(url); + if (!['http:', 'https:'].includes(parsed.protocol)) { + return { + success: false, + error: 'Недопустимый протокол (только http/https)', + }; + } + } catch { + return { success: false, error: 'Некорректный URL' }; + } + + const sysInfo = this.storage.getSystemInfo(); + const headers: Record = { + 'User-Agent': 'singbox', + 'x-hwid': this.storage.getHwid(), + 'x-device-os': sysInfo.os, + 'x-ver-os': sysInfo.version, + 'x-device-model': config.appName, + }; + + let configText: string; + let userInfo: Record = {}; + + try { + const response = await this.fetchUrl(url, headers); + + configText = response.body; + + // Parse subscription-userinfo header + const userInfoHeader = + response.headers['subscription-userinfo'] || ''; + if (userInfoHeader) { + const parts = ( + Array.isArray(userInfoHeader) + ? userInfoHeader[0] + : userInfoHeader + ).split(';'); + for (const part of parts) { + if (part.includes('=')) { + const [key, value] = part.trim().split('=', 2); + const num = parseInt(value, 10); + if (!isNaN(num)) { + userInfo[key] = num; + } + } + } + } + } catch (e: any) { + const msg = e.message || String(e); + if (msg.includes('HTTP Error')) { + return { success: false, error: `Ошибка HTTP: ${msg}` }; + } + return { success: false, error: `Ошибка подключения: ${msg}` }; + } + + // Try to parse as JSON first + let parsedConfig: any = null; + + try { + parsedConfig = JSON.parse(configText); + } catch { + // Not JSON — try Base64 decode or plain VLESS links + let content = configText.trim(); + + try { + if (/^[A-Za-z0-9+/=\s]+$/.test(content)) { + const decoded = Buffer.from(content, 'base64').toString('utf-8'); + content = decoded; + } + } catch {} + + // Parse VLESS links + const lines = content.split('\n'); + const vlessLinks = lines + .map((l) => l.trim()) + .filter((l) => l.startsWith('vless://')); + + if (vlessLinks.length === 0) { + return { + success: false, + error: 'Не найдены VLESS ссылки в ответе', + }; + } + + const outbounds: any[] = []; + for (const link of vlessLinks) { + try { + const params = this.vless.parseVlessUrl(link); + outbounds.push({ + type: 'vless', + tag: params.tag, + server: params.server, + server_port: params.server_port, + uuid: params.uuid, + flow: params.flow, + tls: { + enabled: true, + server_name: params.server_name, + utls: { + enabled: true, + fingerprint: params.fingerprint, + }, + reality: { + enabled: true, + public_key: params.public_key, + short_id: params.short_id, + }, + }, + packet_encoding: 'xudp', + }); + } catch (e) { + console.error(`[WebUI] Failed to parse VLESS link: ${e}`); + continue; + } + } + + if (outbounds.length === 0) { + return { + success: false, + error: 'Не удалось распарсить VLESS ссылки', + }; + } + + parsedConfig = { outbounds }; + } + + // Extract servers + const outbounds: any[] = parsedConfig.outbounds || []; + const servers: any[] = []; + + for (const outbound of outbounds) { + if ( + ['vless', 'vmess', 'trojan', 'shadowsocks', 'hysteria2'].includes( + outbound.type, + ) + ) { + servers.push({ + tag: outbound.tag || 'unknown', + type: outbound.type, + server: outbound.server || 'unknown', + server_port: outbound.server_port || 443, + }); + } + } + + if (servers.length === 0) { + return { + success: false, + error: 'Серверы не найдены в подписке', + }; + } + + return { + success: true, + servers, + config: parsedConfig, + userInfo, + }; + } + + /** Apply subscription config with selected server */ + applySubscriptionConfig( + subConfig: any, + selectedTag: string, + subUrl?: string, + userInfoData?: any, + ): { success: boolean; message?: string; error?: string } { + const outbounds: any[] = subConfig.outbounds || []; + const newOutbounds: any[] = []; + let selectedOutbound: any = null; + + for (const outbound of outbounds) { + if (outbound.tag === selectedTag) { + selectedOutbound = outbound; + } else if (['direct', 'block', 'dns'].includes(outbound.type)) { + newOutbounds.push(outbound); + } + // Skip selector type + } + + if (!selectedOutbound) { + return { + success: false, + error: `Сервер '${selectedTag}' не найден`, + }; + } + + // Load fallback configuration + const fallback = this.storage.loadFallbackConfig(); + const finalOutbounds: any[] = []; + let finalTag = selectedTag; + + if (fallback.enabled && fallback.host) { + finalOutbounds.push({ + type: 'urltest', + tag: 'auto-select', + outbounds: ['fallback-proxy', selectedTag], + url: 'http://www.gstatic.com/generate_204', + interval: '30s', + tolerance: 9999, + }); + + finalOutbounds.push({ + type: 'http', + tag: 'fallback-proxy', + server: fallback.host, + server_port: fallback.port, + }); + + finalTag = 'auto-select'; + } + + // Add selected VPN server + finalOutbounds.push(selectedOutbound); + // Add utility outbounds + finalOutbounds.push(...newOutbounds); + + // Build config + subConfig.dns = { independent_cache: true }; + delete subConfig.platform; + delete subConfig.experimental; + + subConfig.inbounds = [ + { + tag: 'mixed-in', + type: 'mixed', + sniff: true, + users: [], + listen: config.proxyBindIp, + listen_port: config.proxyPort, + set_system_proxy: false, + }, + ]; + + subConfig.outbounds = finalOutbounds; + subConfig.route = { + final: finalTag, + auto_detect_interface: true, + }; + + // Write config + this.storage.writeConfigFile(subConfig); + + // Save subscription for persistence + if (subUrl) { + this.storage.saveSubscription(subUrl, selectedTag, userInfoData); + } + + // Reload sing-box + this.triggerReload(); + + return { + success: true, + message: `Сервер '${selectedTag}' успешно применён!`, + }; + } + + /** Fetch a URL directly (not through proxy) with redirect support */ + private fetchUrl( + url: string, + headers: Record, + maxRedirects = 5, + ): Promise<{ + body: string; + headers: Record; + }> { + if (maxRedirects <= 0) { + return Promise.reject(new Error('Too many redirects')); + } + + return new Promise((resolve, reject) => { + let parsed: URL; + try { + parsed = new URL(url); + } catch { + reject(new Error('Invalid URL')); + return; + } + + const mod = parsed.protocol === 'https:' ? https : http; + + const req = mod.request( + { + hostname: parsed.hostname, + port: + parsed.port || (parsed.protocol === 'https:' ? 443 : 80), + path: parsed.pathname + parsed.search, + method: 'GET', + headers, + timeout: 15000, + }, + (res) => { + // Handle redirects + if ( + res.statusCode >= 300 && + res.statusCode < 400 && + res.headers.location + ) { + this.fetchUrl(res.headers.location, headers, maxRedirects - 1) + .then(resolve) + .catch(reject); + return; + } + + if (res.statusCode >= 400) { + reject(new Error(`HTTP Error: ${res.statusCode}`)); + return; + } + + const chunks: Buffer[] = []; + res.on('data', (chunk: Buffer) => chunks.push(chunk)); + res.on('end', () => { + const body = Buffer.concat(chunks).toString('utf-8'); + resolve({ + body, + headers: res.headers as Record, + }); + }); + }, + ); + + req.on('error', reject); + req.on('timeout', () => { + req.destroy(); + reject(new Error('Timeout')); + }); + req.end(); + }); + } +} diff --git a/web/api/src/storage/storage.module.ts b/web/api/src/storage/storage.module.ts new file mode 100644 index 0000000..9183e1b --- /dev/null +++ b/web/api/src/storage/storage.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { StorageService } from './storage.service'; + +@Module({ + providers: [StorageService], + exports: [StorageService], +}) +export class StorageModule {} diff --git a/web/api/src/storage/storage.service.ts b/web/api/src/storage/storage.service.ts new file mode 100644 index 0000000..6bf4c91 --- /dev/null +++ b/web/api/src/storage/storage.service.ts @@ -0,0 +1,151 @@ +import { Injectable } from '@nestjs/common'; +import * as fs from 'fs'; +import * as crypto from 'crypto'; +import * as os from 'os'; +import { config } from '../config/config'; + +@Injectable() +export class StorageService { + private ensureDataDir(): void { + fs.mkdirSync(config.dataDir, { recursive: true }); + } + + // --- Subscription --- + + saveSubscription( + url: string, + selectedServer?: string, + userInfo?: any, + ): void { + this.ensureDataDir(); + const data = { url, selectedServer, userInfo }; + fs.writeFileSync( + config.subscriptionFile, + JSON.stringify(data, null, 2), + 'utf-8', + ); + } + + loadSubscription(): any { + try { + if (fs.existsSync(config.subscriptionFile)) { + return JSON.parse(fs.readFileSync(config.subscriptionFile, 'utf-8')); + } + } catch {} + return null; + } + + // --- Fallback Config --- + + saveFallbackConfig(enabled: boolean, host: string, port: number): void { + this.ensureDataDir(); + const data = { enabled, host, port }; + fs.writeFileSync( + config.fallbackFile, + JSON.stringify(data, null, 2), + 'utf-8', + ); + } + + loadFallbackConfig(): { enabled: boolean; host: string; port: number } { + try { + if (fs.existsSync(config.fallbackFile)) { + return JSON.parse(fs.readFileSync(config.fallbackFile, 'utf-8')); + } + } catch {} + return { ...config.defaultFallback }; + } + + // --- Proxy Enabled --- + + saveProxyEnabled(enabled: boolean): void { + this.ensureDataDir(); + fs.writeFileSync( + config.proxyEnabledFile, + JSON.stringify({ enabled }), + 'utf-8', + ); + } + + loadProxyEnabled(): boolean { + try { + if (fs.existsSync(config.proxyEnabledFile)) { + const data = JSON.parse( + fs.readFileSync(config.proxyEnabledFile, 'utf-8'), + ); + return data.enabled ?? true; + } + } catch {} + return true; + } + + // --- Start Time --- + + saveStartTime(startTime: number): void { + this.ensureDataDir(); + fs.writeFileSync( + config.startTimeFile, + JSON.stringify({ startTime }), + 'utf-8', + ); + } + + loadStartTime(): number { + try { + if (fs.existsSync(config.startTimeFile)) { + const data = JSON.parse( + fs.readFileSync(config.startTimeFile, 'utf-8'), + ); + return data.startTime ?? 0; + } + } catch {} + return 0; + } + + // --- HWID --- + + getHwid(): string { + this.ensureDataDir(); + try { + if (fs.existsSync(config.hwidFile)) { + return fs.readFileSync(config.hwidFile, 'utf-8').trim(); + } + } catch {} + const hwid = crypto.randomBytes(8).toString('hex'); + fs.writeFileSync(config.hwidFile, hwid, 'utf-8'); + return hwid; + } + + // --- System Info --- + + getSystemInfo(): { os: string; version: string } { + return { + os: os.platform(), + version: os.release(), + }; + } + + // --- Config File --- + + readConfigFile(): any { + try { + if (fs.existsSync(config.configFile)) { + return JSON.parse(fs.readFileSync(config.configFile, 'utf-8')); + } + } catch {} + return null; + } + + writeConfigFile(data: any): void { + this.ensureDataDir(); + fs.writeFileSync( + config.configFile, + JSON.stringify(data, null, 2), + 'utf-8', + ); + } + + configFileExists(): boolean { + return fs.existsSync(config.configFile); + } +} diff --git a/web/api/src/vless/vless.module.ts b/web/api/src/vless/vless.module.ts new file mode 100644 index 0000000..dd90f83 --- /dev/null +++ b/web/api/src/vless/vless.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { VlessService } from './vless.service'; + +@Module({ + providers: [VlessService], + exports: [VlessService], +}) +export class VlessModule {} diff --git a/web/api/src/vless/vless.service.ts b/web/api/src/vless/vless.service.ts new file mode 100644 index 0000000..fc8f33f --- /dev/null +++ b/web/api/src/vless/vless.service.ts @@ -0,0 +1,158 @@ +import { Injectable } from '@nestjs/common'; +import { config } from '../config/config'; + +export interface VlessParams { + uuid: string; + server: string; + server_port: number; + tag: string; + public_key: string; + short_id: string; + server_name: string; + fingerprint: string; + flow: string; +} + +@Injectable() +export class VlessService { + parseVlessUrl(url: string): VlessParams { + if (!url.startsWith('vless://')) { + throw new Error('URL must start with vless://'); + } + + let urlNoScheme = url.slice(8); + + // Split by fragment (#tag) + let tag = 'reality'; + const hashIndex = urlNoScheme.indexOf('#'); + if (hashIndex !== -1) { + tag = decodeURIComponent(urlNoScheme.slice(hashIndex + 1)); + urlNoScheme = urlNoScheme.slice(0, hashIndex); + } + + // Split by query (?) + const qIndex = urlNoScheme.indexOf('?'); + if (qIndex === -1) { + throw new Error('Missing query parameters'); + } + + const uuidHostPort = urlNoScheme.slice(0, qIndex); + const queryString = urlNoScheme.slice(qIndex + 1); + + // Parse UUID@host:port + const atIndex = uuidHostPort.indexOf('@'); + if (atIndex === -1) { + throw new Error('Missing @ separator'); + } + + const uuid = uuidHostPort.slice(0, atIndex); + const hostPort = uuidHostPort.slice(atIndex + 1); + + const lastColon = hostPort.lastIndexOf(':'); + if (lastColon === -1) { + throw new Error('Missing port'); + } + + const host = hostPort.slice(0, lastColon); + const port = parseInt(hostPort.slice(lastColon + 1), 10); + + // Parse query parameters + const params: Record = {}; + for (const param of queryString.split('&')) { + const eqIndex = param.indexOf('='); + if (eqIndex !== -1) { + params[param.slice(0, eqIndex)] = decodeURIComponent( + param.slice(eqIndex + 1), + ); + } + } + + const pbk = params.pbk || ''; + const sid = params.sid || ''; + const sni = params.sni || host; + const fp = params.fp || 'chrome'; + const flow = params.flow || ''; + + if (!pbk || !sid) { + throw new Error('Missing required parameters: pbk or sid'); + } + + return { + uuid, + server: host, + server_port: port, + tag, + public_key: pbk, + short_id: sid, + server_name: sni, + fingerprint: fp, + flow, + }; + } + + generateVlessConfig(vlessParams: VlessParams): any { + return { + dns: { independent_cache: true }, + log: { level: 'debug', disabled: true, timestamp: true }, + route: { + final: vlessParams.tag, + auto_detect_interface: true, + }, + inbounds: [ + { + tag: 'mixed-in', + type: 'mixed', + sniff: true, + users: [], + listen: '0.0.0.0', + listen_port: config.proxyPort, + set_system_proxy: false, + }, + ], + outbounds: [ + { + type: 'vless', + tag: vlessParams.tag, + server: vlessParams.server, + server_port: vlessParams.server_port, + flow: vlessParams.flow, + tls: { + enabled: true, + server_name: vlessParams.server_name, + reality: { + enabled: true, + public_key: vlessParams.public_key, + short_id: vlessParams.short_id, + }, + utls: { + enabled: true, + fingerprint: vlessParams.fingerprint, + }, + }, + uuid: vlessParams.uuid, + }, + { tag: 'direct', type: 'direct' }, + ], + }; + } + + generateDirectConfig(): any { + return { + dns: { independent_cache: true }, + log: { level: 'debug', disabled: true, timestamp: true }, + route: { final: 'direct', auto_detect_interface: true }, + inbounds: [ + { + tag: 'mixed-in', + type: 'mixed', + sniff: true, + users: [], + listen: '0.0.0.0', + listen_port: config.proxyPort, + set_system_proxy: false, + }, + ], + outbounds: [{ tag: 'direct', type: 'direct' }], + }; + } +} diff --git a/web/api/tsconfig.build.json b/web/api/tsconfig.build.json new file mode 100644 index 0000000..64f86c6 --- /dev/null +++ b/web/api/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] +} diff --git a/web/api/tsconfig.json b/web/api/tsconfig.json new file mode 100644 index 0000000..e130573 --- /dev/null +++ b/web/api/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "types": ["node"], + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false + } +} diff --git a/web/app/__init__.py b/web/app/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/web/app/api.py b/web/app/api.py deleted file mode 100644 index 682870d..0000000 --- a/web/app/api.py +++ /dev/null @@ -1,742 +0,0 @@ -import http.server -import json -import urllib.request -import urllib.error -from urllib.parse import parse_qs, unquote, urlparse -from datetime import datetime, timezone - -from .config import ( - WEB_DIR, CONFIG_FILE, PROXY_PORT, DATA_DIR, APP_NAME, - RELOAD_PORT, PROXY_BIND_IP -) -from .utils import get_hwid, get_system_info -from .storage import ( - load_subscription, save_subscription, - load_fallback_config, save_fallback_config, - load_proxy_enabled, save_proxy_enabled, - load_start_time, save_start_time -) -from .network import measure_proxy_performance, measure_tcp_latency -from .vless import ( - parse_vless_url, generate_vless_config, generate_direct_config -) - -class ProxyControlHandler(http.server.BaseHTTPRequestHandler): - """HTTP Request Handler for Proxy Control""" - - def log_message(self, format, *args): - """Override to add timestamp prefix""" - print(f"[WebUI] {args[0]}") - - def send_json(self, data: dict, status: int = 200): - """Send JSON response""" - self.send_response(status) - self.send_header("Content-Type", "application/json; charset=utf-8") - self.send_header("Access-Control-Allow-Origin", "*") - self.end_headers() - self.wfile.write(json.dumps(data, ensure_ascii=False).encode("utf-8")) - - def send_html(self, content: bytes): - """Send HTML response""" - self.send_response(200) - self.send_header("Content-Type", "text/html; charset=utf-8") - self.end_headers() - self.wfile.write(content) - - def do_GET(self): - """Handle GET requests""" - if self.path == "/" or self.path == "/index.html": - self.serve_index() - elif self.path == "/status": - self.get_status() - elif self.path == "/subscription": - self.get_subscription() - elif self.path.startswith("/test-connection"): - self.test_connection() - elif self.path.startswith("/static/"): - self.serve_static() - elif self.path == "/fallback-config": - self.get_fallback_config() - elif self.path == "/active-proxy": - self.get_active_proxy() - elif self.path == "/proxy-enabled": - self.get_proxy_enabled() - else: - self.send_error(404) - - def do_POST(self): - """Handle POST requests""" - if self.path == "/apply": - self.apply_config() - elif self.path == "/fetch-subscription": - self.fetch_subscription() - elif self.path == "/apply-subscription": - self.apply_subscription() - elif self.path == "/ping-target": - self.ping_target() - elif self.path == "/fallback-config": - self.save_fallback_config_endpoint() - elif self.path == "/proxy-enabled": - self.set_proxy_enabled() - else: - self.send_error(404) - - def serve_index(self): - """Serve main HTML page with SSI-like includes""" - index_path = WEB_DIR / "index.html" - if not index_path.exists(): - self.send_error(404, "index.html not found") - return - - try: - content = index_path.read_text(encoding="utf-8") - - # Process includes: - import re - - def replace_include(match): - path = match.group(1) - full_path = WEB_DIR / path - if full_path.exists() and full_path.is_file(): - return full_path.read_text(encoding="utf-8") - return f"" - - # Simple one-pass replacement - processed_content = re.sub(r'', replace_include, content) - - self.send_html(processed_content.encode("utf-8")) - - except Exception as e: - self.send_error(500, f"Error serving index: {str(e)}") - - def serve_static(self): - """Serve static files""" - # Map /static/... to WEB_DIR/static/... - path_clean = self.path.split('?')[0] # Remove query params - file_path = WEB_DIR / path_clean.lstrip('/') - if file_path.exists() and file_path.is_file(): - content_type = "text/css" if str(file_path).endswith(".css") else "application/javascript" - self.send_response(200) - self.send_header("Content-Type", content_type) - self.end_headers() - self.wfile.write(file_path.read_bytes()) - else: - self.send_error(404) - - def get_fallback_config(self): - """Get fallback proxy configuration""" - fallback = load_fallback_config() - self.send_json({ - "enabled": fallback.get("enabled", False), - "host": fallback.get("host", "192.168.50.111"), - "port": fallback.get("port", 8080) - }) - - def get_active_proxy(self): - """Get information about current active proxy chain""" - result = { - "configured": False, - "fallbackEnabled": False, - "fallbackHost": None, - "vpnTag": None, - "vpnServer": None, - "activeOutbound": None - } - - if not CONFIG_FILE.exists(): - self.send_json(result) - return - - try: - config = json.loads(CONFIG_FILE.read_text()) - outbounds = config.get("outbounds", []) - route_final = config.get("route", {}).get("final") - - result["configured"] = True - - for outbound in outbounds: - out_type = outbound.get("type") - - if out_type == "urltest": - result["fallbackEnabled"] = True - elif out_type == "http" and outbound.get("tag") == "fallback-proxy": - result["fallbackHost"] = f"{outbound.get('server')}:{outbound.get('server_port')}" - elif out_type in ["vless", "vmess", "trojan", "shadowsocks", "hysteria2"]: - result["vpnTag"] = outbound.get("tag") - result["vpnServer"] = outbound.get("server") - - # Determine which is actually active - # For now, we show the configured route - result["activeOutbound"] = route_final - - # Check fallback proxy reachability (quick TCP check) - if result["fallbackEnabled"] and result["fallbackHost"]: - try: - host, port = result["fallbackHost"].split(":") - latency = measure_tcp_latency(host, int(port), timeout=1.0) - result["fallbackReachable"] = latency > 0 - result["fallbackLatency"] = latency if latency > 0 else None - except Exception: - result["fallbackReachable"] = False - result["fallbackLatency"] = None - - except Exception as e: - result["error"] = str(e) - - self.send_json(result) - - def save_fallback_config_endpoint(self): - """Save fallback proxy configuration and regenerate config""" - try: - content_length = int(self.headers.get("Content-Length", 0)) - body = self.rfile.read(content_length).decode("utf-8") - data = json.loads(body) - - enabled = data.get("enabled", False) - host = data.get("host", "").strip() - port = int(data.get("port", 8080)) - - if enabled and not host: - self.send_json({"success": False, "error": "Host is required"}, 400) - return - - save_fallback_config(enabled, host, port) - - # Regenerate current config if it exists - regenerated = self.regenerate_current_config() - - self.send_json({ - "success": True, - "message": "Fallback config saved", - "regenerated": regenerated - }) - - except json.JSONDecodeError: - self.send_json({"success": False, "error": "Invalid JSON"}, 400) - except Exception as e: - self.send_json({"success": False, "error": str(e)}, 500) - - def regenerate_current_config(self) -> bool: - """Regenerate current config with updated fallback settings""" - if not CONFIG_FILE.exists(): - return False - - try: - config = json.loads(CONFIG_FILE.read_text()) - outbounds = config.get("outbounds", []) - - # Find the VPN outbound (vless, vmess, etc.) - vpn_outbound = None - utility_outbounds = [] - - for outbound in outbounds: - if outbound.get("type") in ["vless", "vmess", "trojan", "shadowsocks", "hysteria2"]: - vpn_outbound = outbound - elif outbound.get("type") in ["direct", "block", "dns"]: - utility_outbounds.append(outbound) - - if not vpn_outbound: - return False - - selected_tag = vpn_outbound.get("tag") - - # Load fallback config - fallback = load_fallback_config() - fallback_enabled = fallback.get("enabled", False) - fallback_host = fallback.get("host", "") - fallback_port = fallback.get("port", 8080) - - # Build new outbounds - final_outbounds = [] - final_tag = selected_tag - - if fallback_enabled and fallback_host: - urltest_outbound = { - "type": "urltest", - "tag": "auto-select", - "outbounds": ["fallback-proxy", selected_tag], - "url": "http://www.gstatic.com/generate_204", - "interval": "30s", - "tolerance": 9999 - } - - fallback_outbound = { - "type": "http", - "tag": "fallback-proxy", - "server": fallback_host, - "server_port": fallback_port - } - - final_outbounds.append(urltest_outbound) - final_outbounds.append(fallback_outbound) - final_tag = "auto-select" - - final_outbounds.append(vpn_outbound) - final_outbounds.extend(utility_outbounds) - - config["outbounds"] = final_outbounds - config["route"]["final"] = final_tag - - # Write config - CONFIG_FILE.write_text(json.dumps(config, indent=2, ensure_ascii=False)) - - # Reload sing-box - try: - urllib.request.urlopen(f"http://127.0.0.1:{RELOAD_PORT}/reload", timeout=3) - except Exception: - pass - - return True - - except Exception as e: - print(f"[WebUI] Failed to regenerate config: {e}") - return False - - def get_status(self): - """Get current proxy status""" - config_exists = CONFIG_FILE.exists() - current_tag = None - current_server = None - proxy_enabled = load_proxy_enabled() - - if config_exists: - try: - config = json.loads(CONFIG_FILE.read_text()) - for outbound in config.get("outbounds", []): - if outbound.get("type") == "vless": - current_tag = outbound.get("tag", "unknown") - current_server = outbound.get("server", "unknown") - break - except Exception: - pass - - self.send_json({ - "active": config_exists and proxy_enabled, - "tag": current_tag, - "server": current_server, - "proxyPort": PROXY_PORT, - "proxyEnabled": proxy_enabled, - "startTime": load_start_time() if config_exists and proxy_enabled else 0 - }) - - def get_proxy_enabled(self): - """Get proxy enabled state""" - enabled = load_proxy_enabled() - self.send_json({"enabled": enabled}) - - def set_proxy_enabled(self): - """Set proxy enabled state and regenerate config""" - try: - content_length = int(self.headers.get("Content-Length", 0)) - body = self.rfile.read(content_length).decode("utf-8") - data = json.loads(body) - - enabled = data.get("enabled", True) - save_proxy_enabled(enabled) - - # Regenerate config based on state - if enabled: - # Restore normal VPN config - regenerated = self.regenerate_current_config() - if regenerated: - # Only update start time if actually enabling VPN - save_start_time(datetime.now(timezone.utc).timestamp()) - else: - # Generate direct config (bypass proxy) - regenerated = generate_direct_config() - save_start_time(0) - - self.send_json({ - "success": True, - "enabled": enabled, - "regenerated": regenerated - }) - - except json.JSONDecodeError: - self.send_json({"success": False, "error": "Invalid JSON"}, 400) - except Exception as e: - self.send_json({"success": False, "error": str(e)}, 500) - - def get_subscription(self): - """Get saved subscription info""" - sub = load_subscription() - if sub: - self.send_json({ - "saved": True, - "url": sub.get("url"), - "selectedServer": sub.get("selectedServer"), - "userInfo": sub.get("userInfo") - }) - else: - self.send_json({"saved": False}) - - def test_connection(self): - """Test active proxy connection""" - query_components = {} - if '?' in self.path: - _, query = self.path.split('?', 1) - query_components = parse_qs(query) - - enable_speed = query_components.get('speed', ['false'])[0].lower() == 'true' - - result = measure_proxy_performance(enable_speed_test=enable_speed) - self.send_json(result) - - def ping_target(self): - """Ping a specific target""" - try: - content_length = int(self.headers.get("Content-Length", 0)) - body = self.rfile.read(content_length).decode("utf-8") - data = json.loads(body) - - server = data.get("server") - port = int(data.get("port", 443)) - - if not server: - self.send_json({"error": "No server specified"}, 400) - return - - latency = measure_tcp_latency(server, port) - self.send_json({"latency": latency}) - - except Exception as e: - self.send_json({"error": str(e)}, 500) - - def apply_config(self): - """Apply new config from VLESS URL""" - try: - content_length = int(self.headers.get("Content-Length", 0)) - body = self.rfile.read(content_length).decode("utf-8") - data = json.loads(body) - url = data.get("url", "").strip() - - if not url: - self.send_json({"success": False, "error": "URL не указан"}, 400) - return - - if not url.startswith("vless://"): - self.send_json({"success": False, "error": "Неверный формат. Поддерживаются только vless:// ссылки"}, 400) - return - - # Parse VLESS URL - try: - vless_params = parse_vless_url(url) - except ValueError as e: - self.send_json({"success": False, "error": f"Ошибка парсинга URL: {str(e)}"}, 400) - return - - # Generate config - config = generate_vless_config(vless_params) - - # Ensure data directory exists - DATA_DIR.mkdir(parents=True, exist_ok=True) - - # Write config file - CONFIG_FILE.write_text(json.dumps(config, indent=2, ensure_ascii=False)) - - # Trigger reload via internal control port - try: - urllib.request.urlopen(f"http://localhost:{RELOAD_PORT}/reload", timeout=5) - except Exception as e: - print(f"[WebUI] Warning: reload request failed: {e}") - - self.send_json({ - "success": True, - "message": f"Конфигурация '{vless_params['tag']}' успешно применена!" - }) - - # Save new start time as connection is reset - save_start_time(datetime.now(timezone.utc).timestamp()) - - except json.JSONDecodeError: - self.send_json({"success": False, "error": "Неверный JSON"}, 400) - except Exception as e: - self.send_json({"success": False, "error": str(e)}, 500) - - def fetch_subscription(self): - """Fetch servers list from subscription URL""" - try: - content_length = int(self.headers.get("Content-Length", 0)) - body = self.rfile.read(content_length).decode("utf-8") - data = json.loads(body) - url = data.get("url", "").strip() - - if not url: - self.send_json({"success": False, "error": "URL подписки не указан"}, 400) - return - - # Validate URL scheme to prevent SSRF - try: - parsed_url = urlparse(url) - if parsed_url.scheme not in ('http', 'https'): - self.send_json({"success": False, "error": "Недопустимый протокол (только http/https)"}, 400) - return - except Exception: - self.send_json({"success": False, "error": "Некорректный URL"}, 400) - return - - # Fetch subscription config - sys_info = get_system_info() - req = urllib.request.Request( - url, - headers={ - "User-Agent": "singbox", - "x-hwid": get_hwid(), - "x-device-os": sys_info["os"], - "x-ver-os": sys_info["version"], - "x-device-model": APP_NAME - } - ) - - config = None - config_text = "" - user_info = {} - - try: - with urllib.request.urlopen(req, timeout=15) as response: - config_text = response.read().decode("utf-8") - - # Parse User Info header - user_info_header = response.headers.get("subscription-userinfo", "") - if user_info_header: - parts = user_info_header.split(';') - for part in parts: - if '=' in part: - key, value = part.strip().split('=', 1) - try: - user_info[key] = int(value) - except ValueError: - pass - - except urllib.error.HTTPError as e: - self.send_json({"success": False, "error": f"Ошибка HTTP: {e.code}"}, 400) - return - except urllib.error.URLError as e: - self.send_json({"success": False, "error": f"Ошибка подключения: {e.reason}"}, 400) - return - - # Try to parse as JSON first - try: - config = json.loads(config_text) - except json.JSONDecodeError: - # Not JSON - try Base64 decode or plain text VLESS links - content = config_text.strip() - - # Try Base64 decode - import base64 - import re - try: - # Check if it looks like Base64 - if re.match(r'^[A-Za-z0-9+/=\s]+$', content): - decoded = base64.b64decode(content).decode('utf-8') - content = decoded - except Exception: - pass # Not Base64, continue with original content - - # Parse VLESS links - lines = content.strip().split('\n') - vless_links = [line.strip() for line in lines if line.strip().startswith('vless://')] - - if not vless_links: - self.send_json({"success": False, "error": "Не найдены VLESS ссылки в ответе"}, 400) - return - - # Parse each VLESS link and create outbounds - outbounds = [] - for link in vless_links: - try: - params = parse_vless_url(link) - outbound = { - "type": "vless", - "tag": params['tag'], - "server": params['server'], - "server_port": params['server_port'], - "uuid": params['uuid'], - "flow": params['flow'], - "tls": { - "enabled": True, - "server_name": params['server_name'], - "utls": {"enabled": True, "fingerprint": params['fingerprint']}, - "reality": { - "enabled": True, - "public_key": params['public_key'], - "short_id": params['short_id'] - } - }, - "packet_encoding": "xudp" - } - outbounds.append(outbound) - except Exception as e: - print(f"[WebUI] Failed to parse VLESS link: {e}") - continue - - if not outbounds: - self.send_json({"success": False, "error": "Не удалось распарсить VLESS ссылки"}, 400) - return - - # Create a mock config with parsed outbounds - config = {"outbounds": outbounds} - - # Extract outbound servers - outbounds = config.get("outbounds", []) - servers = [] - - for outbound in outbounds: - if outbound.get("type") in ["vless", "vmess", "trojan", "shadowsocks", "hysteria2"]: - servers.append({ - "tag": outbound.get("tag", "unknown"), - "type": outbound.get("type"), - "server": outbound.get("server", "unknown"), - "server_port": outbound.get("server_port", 443) - }) - - if not servers: - self.send_json({"success": False, "error": "Серверы не найдены в подписке"}, 400) - return - - self.send_json({ - "success": True, - "servers": servers, - "config": config, - "userInfo": user_info - }) - - except json.JSONDecodeError: - self.send_json({"success": False, "error": "Неверный JSON в ответе"}, 400) - except Exception as e: - self.send_json({"success": False, "error": str(e)}, 500) - - def apply_subscription(self): - """Apply config from subscription with selected server""" - try: - content_length = int(self.headers.get("Content-Length", 0)) - body = self.rfile.read(content_length).decode("utf-8") - data = json.loads(body) - - config = data.get("config") - selected_tag = data.get("selectedServer") - sub_url = data.get("subUrl") # URL подписки для сохранения - user_info = data.get("userInfo") - - if not config: - self.send_json({"success": False, "error": "Конфигурация не указана"}, 400) - return - - if not selected_tag: - self.send_json({"success": False, "error": "Сервер не выбран"}, 400) - return - - # Modify config to use only selected server - outbounds = config.get("outbounds", []) - new_outbounds = [] - selected_outbound = None - - for outbound in outbounds: - if outbound.get("tag") == selected_tag: - selected_outbound = outbound - elif outbound.get("type") in ["direct", "block", "dns"]: - new_outbounds.append(outbound) - elif outbound.get("type") == "selector": - # Skip selector, we'll add selected server directly - pass - - if not selected_outbound: - self.send_json({"success": False, "error": f"Сервер '{selected_tag}' не найден"}, 400) - return - - # Load fallback configuration - fallback = load_fallback_config() - fallback_enabled = fallback.get("enabled", False) - fallback_host = fallback.get("host", "") - fallback_port = fallback.get("port", 8080) - - # Build outbounds list - final_outbounds = [] - final_tag = selected_tag - - if fallback_enabled and fallback_host: - # Add URLTest for automatic fallback selection - # High tolerance (9999ms) ensures first working proxy is preferred - urltest_outbound = { - "type": "urltest", - "tag": "auto-select", - "outbounds": ["fallback-proxy", selected_tag], - "url": "http://www.gstatic.com/generate_204", - "interval": "30s", - "tolerance": 9999 # Use first working proxy, not fastest - } - - # Add HTTP fallback proxy - fallback_outbound = { - "type": "http", - "tag": "fallback-proxy", - "server": fallback_host, - "server_port": fallback_port - } - - final_outbounds.append(urltest_outbound) - final_outbounds.append(fallback_outbound) - final_tag = "auto-select" - - # Add selected VPN server - final_outbounds.append(selected_outbound) - - # Add utility outbounds (direct, block, dns) - final_outbounds.extend(new_outbounds) - - # Update route - routes = { - "final": final_tag, - "auto_detect_interface": True - } - - # Simplify DNS configuration to match client.json format - config["dns"] = { - "independent_cache": True - } - - # Remove platform-specific and experimental fields from root config - config.pop("platform", None) - config.pop("experimental", None) - - # Replace TUN inbounds with mixed proxy - config["inbounds"] = [ - { - "tag": "mixed-in", - "type": "mixed", - "sniff": True, - "users": [], - "listen": PROXY_BIND_IP, - "listen_port": PROXY_PORT, - "set_system_proxy": False - } - ] - - config["outbounds"] = final_outbounds - config["route"] = routes - - # Ensure data directory exists - DATA_DIR.mkdir(parents=True, exist_ok=True) - - # Write config file - CONFIG_FILE.write_text(json.dumps(config, indent=2, ensure_ascii=False)) - - # Save subscription URL for persistence - if sub_url: - save_subscription(sub_url, selected_tag, user_info) - - # Trigger reload via internal control port - try: - urllib.request.urlopen(f"http://localhost:{RELOAD_PORT}/reload", timeout=5) - except Exception as e: - print(f"[WebUI] Warning: reload request failed: {e}") - - self.send_json({ - "success": True, - "message": f"Сервер '{selected_tag}' успешно применён!" - }) - - except json.JSONDecodeError: - self.send_json({"success": False, "error": "Неверный JSON"}, 400) - except Exception as e: - self.send_json({"success": False, "error": str(e)}, 500) diff --git a/web/app/config.py b/web/app/config.py deleted file mode 100644 index 485a6e4..0000000 --- a/web/app/config.py +++ /dev/null @@ -1,31 +0,0 @@ -import os -from pathlib import Path - -# Environment Configuration -PORT = int(os.environ.get("PORT", 3456)) -PROXY_PORT = int(os.environ.get("PROXY_PORT", 8080)) -RELOAD_PORT = int(os.environ.get("RELOAD_PORT", 9090)) -PROXY_BIND_IP = os.environ.get("PROXY_BIND_IP", "0.0.0.0") -APP_NAME = "VPN-Proxy-Control by Dokril" - -# Path Configuration -# web/app/config.py -> web/app -> web -> base -APP_DIR = Path(__file__).parent.parent -BASE_DIR = APP_DIR.parent -WEB_DIR = APP_DIR -DATA_DIR = BASE_DIR / "data" - -# File Paths -CONFIG_FILE = DATA_DIR / "client.json" -HWID_FILE = DATA_DIR / "hwid" -SUBSCRIPTION_FILE = DATA_DIR / "subscription.json" -FALLBACK_FILE = DATA_DIR / "fallback.json" -PROXY_ENABLED_FILE = DATA_DIR / "proxy_enabled.json" -START_TIME_FILE = DATA_DIR / "start_time.json" - -# Default fallback proxy settings -DEFAULT_FALLBACK = { - "enabled": False, - "host": "192.168.50.111", - "port": 8080 -} diff --git a/web/app/network.py b/web/app/network.py deleted file mode 100644 index e9f1468..0000000 --- a/web/app/network.py +++ /dev/null @@ -1,108 +0,0 @@ -import socket -import time -import urllib.request -from .config import PROXY_PORT - -def measure_tcp_latency(host: str, port: int, timeout: float = 2.0) -> int: - """Measure TCP latency to a host:port in milliseconds""" - start_time = time.time() - try: - with socket.create_connection((host, port), timeout=timeout): - latency = (time.time() - start_time) * 1000 - return int(latency) - except Exception: - return -1 - - -def measure_proxy_performance(enable_speed_test: bool = False) -> dict: - """Measure proxy latency, speed and public IP via local proxy""" - proxy_url = f"http://127.0.0.1:{PROXY_PORT}" - proxies = {"http": proxy_url, "https": proxy_url} - - # 1. Measure Latency (Ping) - latency = "Timeout" - try: - start_time = time.time() - # Use a reliable endpoint for ping - opener = urllib.request.build_opener(urllib.request.ProxyHandler(proxies)) - req = urllib.request.Request("http://www.gstatic.com/generate_204", headers={"User-Agent": "singbox-test"}) - with opener.open(req, timeout=5) as response: - lat_ms = int((time.time() - start_time) * 1000) - latency = f"{lat_ms}ms" - except Exception as e: - latency = "Error" - - # 2. Get Public IP (IPv4) - ip = "Unknown" - try: - opener = urllib.request.build_opener(urllib.request.ProxyHandler(proxies)) - # Use v4.ident.me to force IPv4 - req = urllib.request.Request("http://v4.ident.me", headers={"User-Agent": "curl/7.68.0"}) - with opener.open(req, timeout=5) as response: - ip = response.read().decode('utf-8').strip() - except Exception: - # Fallback to ipify if ident.me fails or returns garbage - try: - req = urllib.request.Request("http://api.ipify.org", headers={"User-Agent": "curl/7.68.0"}) - with opener.open(req, timeout=5) as response: - ip = response.read().decode('utf-8').strip() - except Exception: - pass - - # 3. Measure Download Speed - speed_mbps = 0.0 - if enable_speed_test: - test_files = [ - # Tele2 Speedtest (Usually very reliable and fast) - ("https://speedtest.selectel.ru/100MB", 100), - # ThinkBroadband (Reliable backup) - ("https://speedtest.selectel.ru/1GB", 1000) - ] - - for url, size_mb in test_files: - try: - print(f"[WebUI] Testing speed with: {url}") - start_time = time.time() - opener = urllib.request.build_opener(urllib.request.ProxyHandler(proxies)) - # Set a longer timeout for speed tests - with opener.open(url, timeout=30) as response: - downloaded = 0 - # Larger chunk size for better throughput measurement - chunk_size = 1024 * 256 # 256KB chunks - - # Download for at least 2 seconds or up to 25MB for accurate measurement - min_test_duration = 2.0 # seconds - max_download_bytes = 25 * 1024 * 1024 # 25MB - - while True: - chunk = response.read(chunk_size) - if not chunk: - break - downloaded += len(chunk) - - elapsed = time.time() - start_time - # Stop if we've downloaded enough AND tested for minimum duration - if downloaded >= max_download_bytes or (elapsed >= min_test_duration and downloaded >= 2 * 1024 * 1024): - break - - duration = time.time() - start_time - if duration > 0.1 and downloaded > 0: - # Calculate speed in Mbps (megabits per second) - # downloaded bytes * 8 bits/byte / 1,000,000 / seconds - speed_mbps = round((downloaded * 8) / (1000 * 1000) / duration, 1) - print(f"[WebUI] Speed test: downloaded {downloaded / (1024*1024):.1f}MB in {duration:.1f}s = {speed_mbps} Mbps") - break # Stop if successful - except Exception as e: - print(f"[WebUI] Speed test failed for {url}: {e}") - continue - - result = { - "latency": latency, - "ip": ip - } - - if enable_speed_test: - # If speed is still 0.0 but we tried, return Error or 0.0 - result["speed"] = f"{speed_mbps} Mbps" - - return result diff --git a/web/app/storage.py b/web/app/storage.py deleted file mode 100644 index 6e0132d..0000000 --- a/web/app/storage.py +++ /dev/null @@ -1,80 +0,0 @@ -import json -from .config import ( - DATA_DIR, SUBSCRIPTION_FILE, FALLBACK_FILE, PROXY_ENABLED_FILE, - START_TIME_FILE, DEFAULT_FALLBACK -) - -def save_subscription(url: str, selected_server: str = None, user_info: dict = None): - """Save subscription URL, selected server and user info to file""" - DATA_DIR.mkdir(parents=True, exist_ok=True) - data = { - "url": url, - "selectedServer": selected_server, - "userInfo": user_info - } - SUBSCRIPTION_FILE.write_text(json.dumps(data, ensure_ascii=False, indent=2)) - - -def load_subscription() -> dict: - """Load subscription from file""" - if SUBSCRIPTION_FILE.exists(): - try: - return json.loads(SUBSCRIPTION_FILE.read_text()) - except json.JSONDecodeError: - pass - return None - - -def save_fallback_config(enabled: bool, host: str, port: int): - """Save fallback proxy configuration to file""" - DATA_DIR.mkdir(parents=True, exist_ok=True) - data = { - "enabled": enabled, - "host": host, - "port": port - } - FALLBACK_FILE.write_text(json.dumps(data, ensure_ascii=False, indent=2)) - - -def load_fallback_config() -> dict: - """Load fallback proxy configuration from file""" - if FALLBACK_FILE.exists(): - try: - return json.loads(FALLBACK_FILE.read_text()) - except json.JSONDecodeError: - pass - return DEFAULT_FALLBACK.copy() - - -def save_proxy_enabled(enabled: bool): - """Save proxy enabled state to file""" - DATA_DIR.mkdir(parents=True, exist_ok=True) - PROXY_ENABLED_FILE.write_text(json.dumps({"enabled": enabled})) - - -def load_proxy_enabled() -> bool: - """Load proxy enabled state from file""" - if PROXY_ENABLED_FILE.exists(): - try: - data = json.loads(PROXY_ENABLED_FILE.read_text()) - return data.get("enabled", True) - except json.JSONDecodeError: - pass - return True # Default: proxy enabled - - -def save_start_time(start_time: float): - """Save VPN start time to file""" - DATA_DIR.mkdir(parents=True, exist_ok=True) - START_TIME_FILE.write_text(json.dumps({"startTime": start_time})) - - -def load_start_time() -> float: - """Load VPN start time from file""" - if START_TIME_FILE.exists(): - try: - data = json.loads(START_TIME_FILE.read_text()) - return data.get("startTime", 0.0) - except json.JSONDecodeError: - pass - return 0.0 diff --git a/web/app/utils.py b/web/app/utils.py deleted file mode 100644 index ad369f0..0000000 --- a/web/app/utils.py +++ /dev/null @@ -1,26 +0,0 @@ -import platform -import uuid -from .config import DATA_DIR, HWID_FILE - -def get_hwid() -> str: - """Get or generate hardware ID""" - DATA_DIR.mkdir(parents=True, exist_ok=True) - - if HWID_FILE.exists(): - return HWID_FILE.read_text().strip() - - # Generate new random HWID - hwid = uuid.uuid4().hex[:16] - HWID_FILE.write_text(hwid) - return hwid - - -def get_system_info() -> dict: - """Get system information for headers""" - system = platform.system().lower() # windows, linux, darwin - version = platform.release() # 10, 5.15.0, 22.0.0 - - return { - "os": system, - "version": version - } diff --git a/web/app/vless.py b/web/app/vless.py deleted file mode 100644 index 7521b95..0000000 --- a/web/app/vless.py +++ /dev/null @@ -1,176 +0,0 @@ -from urllib.parse import unquote -import json -import urllib.request -from .config import PROXY_PORT, DATA_DIR, CONFIG_FILE - -def parse_vless_url(url: str) -> dict: - """Parse VLESS URL and extract connection parameters""" - if not url.startswith("vless://"): - raise ValueError("URL must start with vless://") - - # Remove scheme - url_no_scheme = url[8:] - - # Split by fragment (#tag) - if '#' in url_no_scheme: - url_part, tag = url_no_scheme.split('#', 1) - tag = unquote(tag) - else: - url_part = url_no_scheme - tag = "reality" - - # Split by query (?) - if '?' in url_part: - uuid_host_port, query_string = url_part.split('?', 1) - else: - raise ValueError("Missing query parameters") - - # Parse UUID@host:port - if '@' not in uuid_host_port: - raise ValueError("Missing @ separator") - - uuid_str, host_port = uuid_host_port.split('@', 1) - - if ':' not in host_port: - raise ValueError("Missing port") - - host, port_str = host_port.rsplit(':', 1) - port = int(port_str) - - # Parse query parameters - params = {} - for param in query_string.split('&'): - if '=' in param: - key, value = param.split('=', 1) - params[key] = unquote(value) - - # Extract required parameters - pbk = params.get('pbk', '') - sid = params.get('sid', '') - sni = params.get('sni', host) - fp = params.get('fp', 'chrome') - flow = params.get('flow', '') - - if not pbk or not sid: - raise ValueError("Missing required parameters: pbk or sid") - - return { - 'uuid': uuid_str, - 'server': host, - 'server_port': port, - 'tag': tag, - 'public_key': pbk, - 'short_id': sid, - 'server_name': sni, - 'fingerprint': fp, - 'flow': flow - } - - -def generate_vless_config(vless_params: dict) -> dict: - """Generate sing-box configuration from VLESS parameters""" - config = { - "dns": { - "independent_cache": True - }, - "log": { - "level": "debug", - "disabled": True, - "timestamp": True - }, - "route": { - "final": vless_params['tag'], - "auto_detect_interface": True - }, - "inbounds": [ - { - "tag": "mixed-in", - "type": "mixed", - "sniff": True, - "users": [], - "listen": "0.0.0.0", - "listen_port": PROXY_PORT, - "set_system_proxy": False - } - ], - "outbounds": [ - { - "type": "vless", - "tag": vless_params['tag'], - "server": vless_params['server'], - "server_port": vless_params['server_port'], - "flow": vless_params['flow'], - "tls": { - "enabled": True, - "server_name": vless_params['server_name'], - "reality": { - "enabled": True, - "public_key": vless_params['public_key'], - "short_id": vless_params['short_id'] - }, - "utls": { - "enabled": True, - "fingerprint": vless_params['fingerprint'] - } - }, - "uuid": vless_params['uuid'] - }, - { - "tag": "direct", - "type": "direct" - } - ] - } - - return config - - -def generate_direct_config() -> bool: - """Generate a direct connection config (bypass all proxies)""" - try: - config = { - "dns": { - "independent_cache": True - }, - "log": { - "level": "debug", - "disabled": True, - "timestamp": True - }, - "route": { - "final": "direct", - "auto_detect_interface": True - }, - "inbounds": [ - { - "tag": "mixed-in", - "type": "mixed", - "sniff": True, - "users": [], - "listen": "0.0.0.0", - "listen_port": PROXY_PORT, - "set_system_proxy": False - } - ], - "outbounds": [ - { - "tag": "direct", - "type": "direct" - } - ] - } - - DATA_DIR.mkdir(parents=True, exist_ok=True) - CONFIG_FILE.write_text(json.dumps(config, indent=2, ensure_ascii=False)) - - # Reload sing-box - try: - urllib.request.urlopen("http://127.0.0.1:9090/reload", timeout=3) - except Exception: - pass - - return True - - except Exception as e: - print(f"[WebUI] Failed to generate direct config: {e}") - return False diff --git a/web/server.py b/web/server.py deleted file mode 100644 index 7ad54a0..0000000 --- a/web/server.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple HTTP Web Server for VPN Proxy Control -Provides a web UI to manage sing-box subscriptions -""" - -import socketserver -from app.config import PORT -from app.api import ProxyControlHandler - -class ThreadingHTTPServer(socketserver.ThreadingTCPServer): - allow_reuse_address = True - -def main(): - """Start the web server""" - # Use ThreadingTCPServer for concurrent requests - with ThreadingHTTPServer(("", PORT), ProxyControlHandler) as httpd: - print(f"[WebUI] Server started on port {PORT}") - print(f"[WebUI] Open http://localhost:{PORT} in your browser") - httpd.serve_forever() - -if __name__ == "__main__": - main() diff --git a/web/static/js/app.js b/web/static/js/app.js index 111b621..83ca43d 100644 --- a/web/static/js/app.js +++ b/web/static/js/app.js @@ -296,6 +296,7 @@ async function checkServerLatencies(nodes) { try { const res = await fetch('/ping-target', { method: 'POST', + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ server: node.server, port: node.server_port || 443 }) }); const data = await res.json(); From a3816cbedc77ddb37b7577febf9b9690e3b14963 Mon Sep 17 00:00:00 2001 From: Dmitriy Petrov Date: Sat, 14 Mar 2026 18:19:02 +0300 Subject: [PATCH 2/3] feat: add network module and service for TCP latency measurement and proxy performance --- docker-compose.yml | 1 - docker/Dockerfile.singbox | 5 +- web/api/package.json | 15 +- web/api/pnpm-lock.yaml | 2738 +++++++++++++++++++++++++++++++++++++ 4 files changed, 2750 insertions(+), 9 deletions(-) create mode 100644 web/api/pnpm-lock.yaml diff --git a/docker-compose.yml b/docker-compose.yml index d5c4c79..06ec541 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,6 @@ version: "3.9" services: sing-proxy: - container_name: sing-proxy build: context: . dockerfile: docker/Dockerfile.singbox diff --git a/docker/Dockerfile.singbox b/docker/Dockerfile.singbox index 2d1fd12..232f172 100644 --- a/docker/Dockerfile.singbox +++ b/docker/Dockerfile.singbox @@ -4,6 +4,9 @@ ARG SINGBOX_VER=1.12.13 # Устанавливаем зависимости, включая dos2unix для исправления скриптов RUN apk add --no-cache curl ca-certificates tar jq bash coreutils netcat-openbsd nodejs npm dos2unix && update-ca-certificates +# Устанавливаем pnpm +RUN npm install -g pnpm + # Автоматическое определение архитектуры и установка sing-box RUN ARCH=$(uname -m) && \ if [ "$ARCH" = "x86_64" ]; then SB_ARCH="amd64"; \ @@ -20,7 +23,7 @@ COPY --chown=suser:suser web/ /app/web/ # Собираем NestJS бэкенд WORKDIR /app/web/api -RUN npm ci && npm run build && npm prune --omit=dev +RUN CI=true pnpm install --frozen-lockfile && pnpm run build && CI=true pnpm prune --prod WORKDIR /app # Исправляем окончания строк (важно для Windows пользователей) и даем права на запуск diff --git a/web/api/package.json b/web/api/package.json index c84e128..d5b0ba2 100644 --- a/web/api/package.json +++ b/web/api/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "VPN-Proxy backend API", "private": true, + "packageManager": "pnpm@10.32.1", "scripts": { "build": "nest build", "start": "nest start", @@ -10,16 +11,16 @@ "start:prod": "node dist/main" }, "dependencies": { - "@nestjs/common": "^10.4.0", - "@nestjs/core": "^10.4.0", - "@nestjs/platform-express": "^10.4.0", + "@nestjs/common": "^11.1.16", + "@nestjs/core": "^11.1.16", + "@nestjs/platform-express": "^11.1.16", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1" }, "devDependencies": { - "@nestjs/cli": "^10.4.0", - "@types/express": "^4.17.21", - "@types/node": "^22.0.0", + "@nestjs/cli": "^11.0.16", + "@types/express": "^5.0.6", + "@types/node": "^24.0.0", "typescript": "^5.7.0" } -} +} \ No newline at end of file diff --git a/web/api/pnpm-lock.yaml b/web/api/pnpm-lock.yaml new file mode 100644 index 0000000..5f17ae5 --- /dev/null +++ b/web/api/pnpm-lock.yaml @@ -0,0 +1,2738 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@nestjs/common': + specifier: ^11.1.16 + version: 11.1.16(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': + specifier: ^11.1.16 + version: 11.1.16(@nestjs/common@11.1.16(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.16)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/platform-express': + specifier: ^11.1.16 + version: 11.1.16(@nestjs/common@11.1.16(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.16) + reflect-metadata: + specifier: ^0.2.2 + version: 0.2.2 + rxjs: + specifier: ^7.8.1 + version: 7.8.2 + devDependencies: + '@nestjs/cli': + specifier: ^11.0.16 + version: 11.0.16(@types/node@24.12.0) + '@types/express': + specifier: ^5.0.6 + version: 5.0.6 + '@types/node': + specifier: ^24.0.0 + version: 24.12.0 + typescript: + specifier: ^5.7.0 + version: 5.9.3 + +packages: + + '@angular-devkit/core@19.2.17': + resolution: {integrity: sha512-Ah008x2RJkd0F+NLKqIpA34/vUGwjlprRCkvddjDopAWRzYn6xCkz1Tqwuhn0nR1Dy47wTLKYD999TYl5ONOAQ==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + chokidar: ^4.0.0 + peerDependenciesMeta: + chokidar: + optional: true + + '@angular-devkit/core@19.2.19': + resolution: {integrity: sha512-JbLL+4IMLMBgjLZlnPG4lYDfz4zGrJ/s6Aoon321NJKuw1Kb1k5KpFu9dUY0BqLIe8xPQ2UJBpI+xXdK5MXMHQ==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + chokidar: ^4.0.0 + peerDependenciesMeta: + chokidar: + optional: true + + '@angular-devkit/schematics-cli@19.2.19': + resolution: {integrity: sha512-7q9UY6HK6sccL9F3cqGRUwKhM7b/XfD2YcVaZ2WD7VMaRlRm85v6mRjSrfKIAwxcQU0UK27kMc79NIIqaHjzxA==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + hasBin: true + + '@angular-devkit/schematics@19.2.17': + resolution: {integrity: sha512-ADfbaBsrG8mBF6Mfs+crKA/2ykB8AJI50Cv9tKmZfwcUcyAdmTr+vVvhsBCfvUAEokigSsgqgpYxfkJVxhJYeg==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + + '@angular-devkit/schematics@19.2.19': + resolution: {integrity: sha512-J4Jarr0SohdrHcb40gTL4wGPCQ952IMWF1G/MSAQfBAPvA9ZKApYhpxcY7PmehVePve+ujpus1dGsJ7dPxz8Kg==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@borewit/text-codec@0.2.2': + resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} + + '@colors/colors@1.5.0': + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} + engines: {node: '>=18'} + + '@inquirer/checkbox@4.3.2': + resolution: {integrity: sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.3.2': + resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@4.2.23': + resolution: {integrity: sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@4.0.23': + resolution: {integrity: sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} + + '@inquirer/input@4.3.1': + resolution: {integrity: sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@3.0.23': + resolution: {integrity: sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@4.0.23': + resolution: {integrity: sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@7.10.1': + resolution: {integrity: sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@7.3.2': + resolution: {integrity: sha512-G1ytyOoHh5BphmEBxSwALin3n1KGNYB6yImbICcRQdzXfOGbuJ9Jske/Of5Sebk339NSGGNfUshnzK8YWkTPsQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@4.1.11': + resolution: {integrity: sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@3.2.2': + resolution: {integrity: sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@4.4.2': + resolution: {integrity: sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@lukeed/csprng@1.1.0': + resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} + engines: {node: '>=8'} + + '@nestjs/cli@11.0.16': + resolution: {integrity: sha512-P0H+Vcjki6P5160E5QnMt3Q0X5FTg4PZkP99Ig4lm/4JWqfw32j3EXv3YBTJ2DmxLwOQ/IS9F7dzKpMAgzKTGg==} + engines: {node: '>= 20.11'} + hasBin: true + peerDependencies: + '@swc/cli': ^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 + '@swc/core': ^1.3.62 + peerDependenciesMeta: + '@swc/cli': + optional: true + '@swc/core': + optional: true + + '@nestjs/common@11.1.16': + resolution: {integrity: sha512-JSIeW+USuMJkkcNbiOdcPkVCeI3TSnXstIVEPpp3HiaKnPRuSbUUKm9TY9o/XpIcPHWUOQItAtC5BiAwFdVITQ==} + 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 + + '@nestjs/core@11.1.16': + resolution: {integrity: sha512-tXWXyCiqWthelJjrE0KLFjf0O98VEt+WPVx5CrqCf+059kIxJ8y1Vw7Cy7N4fwQafWNrmFL2AfN87DDMbVAY0w==} + engines: {node: '>= 20'} + peerDependencies: + '@nestjs/common': ^11.0.0 + '@nestjs/microservices': ^11.0.0 + '@nestjs/platform-express': ^11.0.0 + '@nestjs/websockets': ^11.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 + + '@nestjs/platform-express@11.1.16': + resolution: {integrity: sha512-IOegr5+ZfUiMKgk+garsSU4MOkPRhm46e6w8Bp1GcO4vCdl9Piz6FlWAzKVfa/U3Hn/DdzSVJOW3TWcQQFdBDw==} + peerDependencies: + '@nestjs/common': ^11.0.0 + '@nestjs/core': ^11.0.0 + + '@nestjs/schematics@11.0.9': + resolution: {integrity: sha512-0NfPbPlEaGwIT8/TCThxLzrlz3yzDNkfRNpbL7FiplKq3w4qXpJg0JYwqgMEJnLQZm3L/L/5XjoyfJHUO3qX9g==} + peerDependencies: + typescript: '>=4.8.2' + + '@nuxt/opencollective@0.4.1': + resolution: {integrity: sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==} + engines: {node: ^14.18.0 || >=16.10.0, npm: '>=5.10.0'} + hasBin: true + + '@tokenizer/inflate@0.4.1': + resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/eslint-scope@3.7.7': + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} + + '@types/eslint@9.6.1': + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/express-serve-static-core@5.1.1': + resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} + + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@24.12.0': + resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==} + + '@types/qs@6.15.0': + resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + + '@webassemblyjs/ast@1.14.1': + resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} + + '@webassemblyjs/floating-point-hex-parser@1.13.2': + resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} + + '@webassemblyjs/helper-api-error@1.13.2': + resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} + + '@webassemblyjs/helper-buffer@1.14.1': + resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} + + '@webassemblyjs/helper-numbers@1.13.2': + resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': + resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} + + '@webassemblyjs/helper-wasm-section@1.14.1': + resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} + + '@webassemblyjs/ieee754@1.13.2': + resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} + + '@webassemblyjs/leb128@1.13.2': + resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} + + '@webassemblyjs/utf8@1.13.2': + resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} + + '@webassemblyjs/wasm-edit@1.14.1': + resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} + + '@webassemblyjs/wasm-gen@1.14.1': + resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} + + '@webassemblyjs/wasm-opt@1.14.1': + resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} + + '@webassemblyjs/wasm-parser@1.14.1': + resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} + + '@webassemblyjs/wast-printer@1.14.1': + resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + + '@xtuc/ieee754@1.2.0': + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + + '@xtuc/long@4.2.2': + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn-import-phases@1.0.4: + resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} + engines: {node: '>=10.13.0'} + peerDependencies: + acorn: ^8.14.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv-formats@2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-keywords@3.5.2: + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 + + ajv-keywords@5.1.0: + resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} + peerDependencies: + ajv: ^8.8.2 + + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} + + append-field@1.0.0: + resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-timsort@1.0.3: + resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.10.8: + resolution: {integrity: sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@5.0.4: + resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} + engines: {node: 18 || 20 || >=22} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001778: + resolution: {integrity: sha512-PN7uxFL+ExFJO61aVmP1aIEG4i9whQd4eoSCebav62UwDyp5OHh06zN4jqKSMePVgxHifCw1QJxdRkA1Pisekg==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + chrome-trace-event@1.0.4: + resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} + engines: {node: '>=6.0'} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + comment-json@4.4.1: + resolution: {integrity: sha512-r1To31BQD5060QdkC+Iheai7gHwoSZobzunqkf2/kQ6xIAfJyrKNAFUwdKvkK7Qgu7pVTKQEa7ok7Ed3ycAJgg==} + engines: {node: '>= 6'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + content-disposition@1.0.1: + resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.313: + resolution: {integrity: sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + enhanced-resolve@5.20.0: + resolution: {integrity: sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==} + engines: {node: '>=10.13.0'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + file-type@21.3.0: + resolution: {integrity: sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==} + engines: {node: '>=20'} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + fork-ts-checker-webpack-plugin@9.1.0: + resolution: {integrity: sha512-mpafl89VFPJmhnJ1ssH+8wmM2b50n+Rew5x42NeI2U78aRWgtkEtGmctp7iT16UjquJTjorEmIfESj3DxdW84Q==} + engines: {node: '>=14.21.3'} + peerDependencies: + typescript: '>3.6.0' + webpack: ^5.11.0 + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-monkey@1.1.0: + resolution: {integrity: sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + + glob@13.0.0: + resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} + engines: {node: 20 || >=22} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + iterare@1.2.1: + resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} + engines: {node: '>=6'} + + jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-esm@1.0.3: + resolution: {integrity: sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==} + engines: {node: '>=13.2.0'} + + loader-runner@4.3.1: + resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==} + engines: {node: '>=6.11.5'} + + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + lru-cache@11.2.7: + resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==} + engines: {node: 20 || >=22} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + memfs@3.5.3: + resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} + engines: {node: '>= 4.0.0'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + multer@2.1.1: + resolution: {integrity: sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==} + engines: {node: '>= 10.16.0'} + + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + + node-emoji@1.11.0: + resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} + + node-releases@2.0.36: + resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + + path-to-regexp@8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qs@6.15.0: + resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} + engines: {node: '>=0.6'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + schema-utils@3.3.0: + resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} + engines: {node: '>= 10.13.0'} + + schema-utils@4.3.3: + resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} + engines: {node: '>= 10.13.0'} + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strtok3@10.3.4: + resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} + engines: {node: '>=18'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + symbol-observable@4.0.0: + resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} + engines: {node: '>=0.10'} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + terser-webpack-plugin@5.4.0: + resolution: {integrity: sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + + terser@5.46.0: + resolution: {integrity: sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==} + engines: {node: '>=10'} + hasBin: true + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + token-types@6.1.2: + resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} + engines: {node: '>=14.16'} + + tsconfig-paths-webpack-plugin@4.2.0: + resolution: {integrity: sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==} + engines: {node: '>=10.13.0'} + + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + uid@2.0.2: + resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} + engines: {node: '>=8'} + + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} + engines: {node: '>=18'} + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + watchpack@2.5.1: + resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} + engines: {node: '>=10.13.0'} + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + + webpack-node-externals@3.0.0: + resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} + engines: {node: '>=6'} + + webpack-sources@3.3.4: + resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==} + engines: {node: '>=10.13.0'} + + webpack@5.104.1: + resolution: {integrity: sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + +snapshots: + + '@angular-devkit/core@19.2.17(chokidar@4.0.3)': + dependencies: + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + jsonc-parser: 3.3.1 + picomatch: 4.0.2 + rxjs: 7.8.1 + source-map: 0.7.4 + optionalDependencies: + chokidar: 4.0.3 + + '@angular-devkit/core@19.2.19(chokidar@4.0.3)': + dependencies: + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + jsonc-parser: 3.3.1 + picomatch: 4.0.2 + rxjs: 7.8.1 + source-map: 0.7.4 + optionalDependencies: + chokidar: 4.0.3 + + '@angular-devkit/schematics-cli@19.2.19(@types/node@24.12.0)(chokidar@4.0.3)': + dependencies: + '@angular-devkit/core': 19.2.19(chokidar@4.0.3) + '@angular-devkit/schematics': 19.2.19(chokidar@4.0.3) + '@inquirer/prompts': 7.3.2(@types/node@24.12.0) + ansi-colors: 4.1.3 + symbol-observable: 4.0.0 + yargs-parser: 21.1.1 + transitivePeerDependencies: + - '@types/node' + - chokidar + + '@angular-devkit/schematics@19.2.17(chokidar@4.0.3)': + dependencies: + '@angular-devkit/core': 19.2.17(chokidar@4.0.3) + jsonc-parser: 3.3.1 + magic-string: 0.30.17 + ora: 5.4.1 + rxjs: 7.8.1 + transitivePeerDependencies: + - chokidar + + '@angular-devkit/schematics@19.2.19(chokidar@4.0.3)': + dependencies: + '@angular-devkit/core': 19.2.19(chokidar@4.0.3) + jsonc-parser: 3.3.1 + magic-string: 0.30.17 + ora: 5.4.1 + rxjs: 7.8.1 + transitivePeerDependencies: + - chokidar + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-validator-identifier@7.28.5': {} + + '@borewit/text-codec@0.2.2': {} + + '@colors/colors@1.5.0': + optional: true + + '@inquirer/ansi@1.0.2': {} + + '@inquirer/checkbox@4.3.2(@types/node@24.12.0)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@24.12.0) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@24.12.0) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 24.12.0 + + '@inquirer/confirm@5.1.21(@types/node@24.12.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@24.12.0) + '@inquirer/type': 3.0.10(@types/node@24.12.0) + optionalDependencies: + '@types/node': 24.12.0 + + '@inquirer/core@10.3.2(@types/node@24.12.0)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@24.12.0) + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 24.12.0 + + '@inquirer/editor@4.2.23(@types/node@24.12.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@24.12.0) + '@inquirer/external-editor': 1.0.3(@types/node@24.12.0) + '@inquirer/type': 3.0.10(@types/node@24.12.0) + optionalDependencies: + '@types/node': 24.12.0 + + '@inquirer/expand@4.0.23(@types/node@24.12.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@24.12.0) + '@inquirer/type': 3.0.10(@types/node@24.12.0) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 24.12.0 + + '@inquirer/external-editor@1.0.3(@types/node@24.12.0)': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 24.12.0 + + '@inquirer/figures@1.0.15': {} + + '@inquirer/input@4.3.1(@types/node@24.12.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@24.12.0) + '@inquirer/type': 3.0.10(@types/node@24.12.0) + optionalDependencies: + '@types/node': 24.12.0 + + '@inquirer/number@3.0.23(@types/node@24.12.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@24.12.0) + '@inquirer/type': 3.0.10(@types/node@24.12.0) + optionalDependencies: + '@types/node': 24.12.0 + + '@inquirer/password@4.0.23(@types/node@24.12.0)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@24.12.0) + '@inquirer/type': 3.0.10(@types/node@24.12.0) + optionalDependencies: + '@types/node': 24.12.0 + + '@inquirer/prompts@7.10.1(@types/node@24.12.0)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@24.12.0) + '@inquirer/confirm': 5.1.21(@types/node@24.12.0) + '@inquirer/editor': 4.2.23(@types/node@24.12.0) + '@inquirer/expand': 4.0.23(@types/node@24.12.0) + '@inquirer/input': 4.3.1(@types/node@24.12.0) + '@inquirer/number': 3.0.23(@types/node@24.12.0) + '@inquirer/password': 4.0.23(@types/node@24.12.0) + '@inquirer/rawlist': 4.1.11(@types/node@24.12.0) + '@inquirer/search': 3.2.2(@types/node@24.12.0) + '@inquirer/select': 4.4.2(@types/node@24.12.0) + optionalDependencies: + '@types/node': 24.12.0 + + '@inquirer/prompts@7.3.2(@types/node@24.12.0)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@24.12.0) + '@inquirer/confirm': 5.1.21(@types/node@24.12.0) + '@inquirer/editor': 4.2.23(@types/node@24.12.0) + '@inquirer/expand': 4.0.23(@types/node@24.12.0) + '@inquirer/input': 4.3.1(@types/node@24.12.0) + '@inquirer/number': 3.0.23(@types/node@24.12.0) + '@inquirer/password': 4.0.23(@types/node@24.12.0) + '@inquirer/rawlist': 4.1.11(@types/node@24.12.0) + '@inquirer/search': 3.2.2(@types/node@24.12.0) + '@inquirer/select': 4.4.2(@types/node@24.12.0) + optionalDependencies: + '@types/node': 24.12.0 + + '@inquirer/rawlist@4.1.11(@types/node@24.12.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@24.12.0) + '@inquirer/type': 3.0.10(@types/node@24.12.0) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 24.12.0 + + '@inquirer/search@3.2.2(@types/node@24.12.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@24.12.0) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@24.12.0) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 24.12.0 + + '@inquirer/select@4.4.2(@types/node@24.12.0)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@24.12.0) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@24.12.0) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 24.12.0 + + '@inquirer/type@3.0.10(@types/node@24.12.0)': + optionalDependencies: + '@types/node': 24.12.0 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@lukeed/csprng@1.1.0': {} + + '@nestjs/cli@11.0.16(@types/node@24.12.0)': + dependencies: + '@angular-devkit/core': 19.2.19(chokidar@4.0.3) + '@angular-devkit/schematics': 19.2.19(chokidar@4.0.3) + '@angular-devkit/schematics-cli': 19.2.19(@types/node@24.12.0)(chokidar@4.0.3) + '@inquirer/prompts': 7.10.1(@types/node@24.12.0) + '@nestjs/schematics': 11.0.9(chokidar@4.0.3)(typescript@5.9.3) + ansis: 4.2.0 + chokidar: 4.0.3 + cli-table3: 0.6.5 + commander: 4.1.1 + fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.104.1) + glob: 13.0.0 + node-emoji: 1.11.0 + ora: 5.4.1 + tsconfig-paths: 4.2.0 + tsconfig-paths-webpack-plugin: 4.2.0 + typescript: 5.9.3 + webpack: 5.104.1 + webpack-node-externals: 3.0.0 + transitivePeerDependencies: + - '@types/node' + - esbuild + - uglify-js + - webpack-cli + + '@nestjs/common@11.1.16(reflect-metadata@0.2.2)(rxjs@7.8.2)': + dependencies: + file-type: 21.3.0 + iterare: 1.2.1 + load-esm: 1.0.3 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + tslib: 2.8.1 + uid: 2.0.2 + transitivePeerDependencies: + - supports-color + + '@nestjs/core@11.1.16(@nestjs/common@11.1.16(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.16)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 11.1.16(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nuxt/opencollective': 0.4.1 + fast-safe-stringify: 2.1.1 + iterare: 1.2.1 + path-to-regexp: 8.3.0 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + tslib: 2.8.1 + uid: 2.0.2 + optionalDependencies: + '@nestjs/platform-express': 11.1.16(@nestjs/common@11.1.16(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.16) + + '@nestjs/platform-express@11.1.16(@nestjs/common@11.1.16(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.16)': + dependencies: + '@nestjs/common': 11.1.16(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.16(@nestjs/common@11.1.16(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.16)(reflect-metadata@0.2.2)(rxjs@7.8.2) + cors: 2.8.6 + express: 5.2.1 + multer: 2.1.1 + path-to-regexp: 8.3.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@nestjs/schematics@11.0.9(chokidar@4.0.3)(typescript@5.9.3)': + dependencies: + '@angular-devkit/core': 19.2.17(chokidar@4.0.3) + '@angular-devkit/schematics': 19.2.17(chokidar@4.0.3) + comment-json: 4.4.1 + jsonc-parser: 3.3.1 + pluralize: 8.0.0 + typescript: 5.9.3 + transitivePeerDependencies: + - chokidar + + '@nuxt/opencollective@0.4.1': + dependencies: + consola: 3.4.2 + + '@tokenizer/inflate@0.4.1': + dependencies: + debug: 4.4.3 + token-types: 6.1.2 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 24.12.0 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 24.12.0 + + '@types/eslint-scope@3.7.7': + dependencies: + '@types/eslint': 9.6.1 + '@types/estree': 1.0.8 + + '@types/eslint@9.6.1': + dependencies: + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + + '@types/estree@1.0.8': {} + + '@types/express-serve-static-core@5.1.1': + dependencies: + '@types/node': 24.12.0 + '@types/qs': 6.15.0 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@5.0.6': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.1 + '@types/serve-static': 2.2.0 + + '@types/http-errors@2.0.5': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@24.12.0': + dependencies: + undici-types: 7.16.0 + + '@types/qs@6.15.0': {} + + '@types/range-parser@1.2.7': {} + + '@types/send@1.2.1': + dependencies: + '@types/node': 24.12.0 + + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 24.12.0 + + '@webassemblyjs/ast@1.14.1': + dependencies: + '@webassemblyjs/helper-numbers': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + + '@webassemblyjs/floating-point-hex-parser@1.13.2': {} + + '@webassemblyjs/helper-api-error@1.13.2': {} + + '@webassemblyjs/helper-buffer@1.14.1': {} + + '@webassemblyjs/helper-numbers@1.13.2': + dependencies: + '@webassemblyjs/floating-point-hex-parser': 1.13.2 + '@webassemblyjs/helper-api-error': 1.13.2 + '@xtuc/long': 4.2.2 + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} + + '@webassemblyjs/helper-wasm-section@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/wasm-gen': 1.14.1 + + '@webassemblyjs/ieee754@1.13.2': + dependencies: + '@xtuc/ieee754': 1.2.0 + + '@webassemblyjs/leb128@1.13.2': + dependencies: + '@xtuc/long': 4.2.2 + + '@webassemblyjs/utf8@1.13.2': {} + + '@webassemblyjs/wasm-edit@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/helper-wasm-section': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-opt': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + '@webassemblyjs/wast-printer': 1.14.1 + + '@webassemblyjs/wasm-gen@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wasm-opt@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + + '@webassemblyjs/wasm-parser@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-api-error': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wast-printer@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@xtuc/long': 4.2.2 + + '@xtuc/ieee754@1.2.0': {} + + '@xtuc/long@4.2.2': {} + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + acorn-import-phases@1.0.4(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv-formats@2.1.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + + ajv-formats@3.0.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv-keywords@3.5.2(ajv@6.14.0): + dependencies: + ajv: 6.14.0 + + ajv-keywords@5.1.0(ajv@8.18.0): + dependencies: + ajv: 8.18.0 + fast-deep-equal: 3.1.3 + + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-colors@4.1.3: {} + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansis@4.2.0: {} + + append-field@1.0.0: {} + + argparse@2.0.1: {} + + array-timsort@1.0.3: {} + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.10.8: {} + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.0 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@5.0.4: + dependencies: + balanced-match: 4.0.4 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.10.8 + caniuse-lite: 1.0.30001778 + electron-to-chromium: 1.5.313 + node-releases: 2.0.36 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001778: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chardet@2.1.1: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + chrome-trace-event@1.0.4: {} + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-spinners@2.9.2: {} + + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + + cli-width@4.1.0: {} + + clone@1.0.4: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@2.20.3: {} + + commander@4.1.1: {} + + comment-json@4.4.1: + dependencies: + array-timsort: 1.0.3 + core-util-is: 1.0.3 + esprima: 4.0.1 + + concat-map@0.0.1: {} + + concat-stream@2.0.0: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 + + consola@3.4.2: {} + + content-disposition@1.0.1: {} + + content-type@1.0.5: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + core-util-is@1.0.3: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cosmiconfig@8.3.6(typescript@5.9.3): + dependencies: + import-fresh: 3.3.1 + js-yaml: 4.1.1 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.9.3 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deepmerge@4.3.1: {} + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + depd@2.0.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.313: {} + + emoji-regex@8.0.0: {} + + encodeurl@2.0.0: {} + + enhanced-resolve@5.20.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@2.0.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + esprima@4.0.1: {} + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + etag@1.8.1: {} + + events@3.3.0: {} + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.0.1 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-safe-stringify@2.1.1: {} + + fast-uri@3.1.0: {} + + file-type@21.3.0: + dependencies: + '@tokenizer/inflate': 0.4.1 + strtok3: 10.3.4 + token-types: 6.1.2 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.104.1): + dependencies: + '@babel/code-frame': 7.29.0 + chalk: 4.1.2 + chokidar: 4.0.3 + cosmiconfig: 8.3.6(typescript@5.9.3) + deepmerge: 4.3.1 + fs-extra: 10.1.0 + memfs: 3.5.3 + minimatch: 3.1.5 + node-abort-controller: 3.1.1 + schema-utils: 3.3.0 + semver: 7.7.4 + tapable: 2.3.0 + typescript: 5.9.3 + webpack: 5.104.1 + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-monkey@1.1.0: {} + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + glob-to-regexp@0.4.1: {} + + glob@13.0.0: + dependencies: + minimatch: 10.2.4 + minipass: 7.1.3 + path-scurry: 2.0.2 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + inherits@2.0.4: {} + + ipaddr.js@1.9.1: {} + + is-arrayish@0.2.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-interactive@1.0.0: {} + + is-promise@4.0.0: {} + + is-unicode-supported@0.1.0: {} + + iterare@1.2.1: {} + + jest-worker@27.5.1: + dependencies: + '@types/node': 24.12.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json5@2.2.3: {} + + jsonc-parser@3.3.1: {} + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + lines-and-columns@1.2.4: {} + + load-esm@1.0.3: {} + + loader-runner@4.3.1: {} + + lodash@4.17.23: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + lru-cache@11.2.7: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + media-typer@1.1.0: {} + + memfs@3.5.3: + dependencies: + fs-monkey: 1.1.0 + + merge-descriptors@2.0.0: {} + + merge-stream@2.0.0: {} + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mimic-fn@2.1.0: {} + + minimatch@10.2.4: + dependencies: + brace-expansion: 5.0.4 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.12 + + minimist@1.2.8: {} + + minipass@7.1.3: {} + + ms@2.1.3: {} + + multer@2.1.1: + dependencies: + append-field: 1.0.0 + busboy: 1.6.0 + concat-stream: 2.0.0 + type-is: 1.6.18 + + mute-stream@2.0.0: {} + + negotiator@1.0.0: {} + + neo-async@2.6.2: {} + + node-abort-controller@3.1.1: {} + + node-emoji@1.11.0: + dependencies: + lodash: 4.17.23 + + node-releases@2.0.36: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + ora@5.4.1: + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.0 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parseurl@1.3.3: {} + + path-scurry@2.0.2: + dependencies: + lru-cache: 11.2.7 + minipass: 7.1.3 + + path-to-regexp@8.3.0: {} + + path-type@4.0.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.2: {} + + pluralize@8.0.0: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + punycode@2.3.1: {} + + qs@6.15.0: + dependencies: + side-channel: 1.1.0 + + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@4.1.2: {} + + reflect-metadata@0.2.2: {} + + require-from-string@2.0.2: {} + + resolve-from@4.0.0: {} + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.3.0 + transitivePeerDependencies: + - supports-color + + rxjs@7.8.1: + dependencies: + tslib: 2.8.1 + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + schema-utils@3.3.0: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 6.14.0 + ajv-keywords: 3.5.2(ajv@6.14.0) + + schema-utils@4.3.3: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 8.18.0 + ajv-formats: 2.1.1(ajv@8.18.0) + ajv-keywords: 5.1.0(ajv@8.18.0) + + semver@7.7.4: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + source-map@0.7.4: {} + + statuses@2.0.2: {} + + streamsearch@1.1.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-bom@3.0.0: {} + + strtok3@10.3.4: + dependencies: + '@tokenizer/token': 0.3.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + symbol-observable@4.0.0: {} + + tapable@2.3.0: {} + + terser-webpack-plugin@5.4.0(webpack@5.104.1): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + terser: 5.46.0 + webpack: 5.104.1 + + terser@5.46.0: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.16.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + toidentifier@1.0.1: {} + + token-types@6.1.2: + dependencies: + '@borewit/text-codec': 0.2.2 + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + + tsconfig-paths-webpack-plugin@4.2.0: + dependencies: + chalk: 4.1.2 + enhanced-resolve: 5.20.0 + tapable: 2.3.0 + tsconfig-paths: 4.2.0 + + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + + typedarray@0.0.6: {} + + typescript@5.9.3: {} + + uid@2.0.2: + dependencies: + '@lukeed/csprng': 1.1.0 + + uint8array-extras@1.5.0: {} + + undici-types@7.16.0: {} + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + vary@1.1.2: {} + + watchpack@2.5.1: + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + + webpack-node-externals@3.0.0: {} + + webpack-sources@3.3.4: {} + + webpack@5.104.1: + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.16.0 + acorn-import-phases: 1.0.4(acorn@8.16.0) + browserslist: 4.28.1 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.20.0 + es-module-lexer: 2.0.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.1 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 4.3.3 + tapable: 2.3.0 + terser-webpack-plugin: 5.4.0(webpack@5.104.1) + watchpack: 2.5.1 + webpack-sources: 3.3.4 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + yargs-parser@21.1.1: {} + + yoctocolors-cjs@2.1.3: {} From ef752d66bc005688859f4b214564cdd323ef8ecc Mon Sep 17 00:00:00 2001 From: Dmitriy Petrov Date: Fri, 8 May 2026 16:04:38 +0300 Subject: [PATCH 3/3] Rebuild vpn proxy around gateway mode --- .env.example | 8 + .gitea/workflows/docker-build.yml | 70 - .gitea/workflows/gateway-build.yml | 28 + .github/copilot-instructions.md | 104 - .gitignore | 66 +- Dockerfile | 44 + README.md | 379 +- docker-compose.gateway.yml | 24 + docker-compose.server.yml | 40 - docker-compose.yml | 21 - docker/Dockerfile.singbox | 36 - docker/entrypoint.sh | 70 - docs/DOCKER.md | 208 - docs/SERVER.md | 278 -- docs/roadmap.md | 105 + entrypoint.sh | 63 + index.html | 12 + install.ps1 | 104 - manage.ps1 | 96 - package.json | 19 + scripts/lib/Common.ps1 | 118 - scripts/lib/Net.ps1 | 144 - scripts/lib/System.ps1 | 175 - scripts/setup-discord.ps1 | 420 -- scripts/setup-singbox.ps1 | 417 -- scripts/uninstall-all.ps1 | 58 - scripts/view-logs.ps1 | 121 - src/server/config.js | 21 + src/server/index.js | 274 ++ src/server/singbox.js | 175 + src/server/subscription.js | 169 + src/web/App.jsx | 451 ++ src/web/styles.css | 440 ++ vite.config.js | 11 + web/api/.gitignore | 2 - web/api/nest-cli.json | 8 - web/api/package-lock.json | 6294 ------------------------ web/api/package.json | 26 - web/api/pnpm-lock.yaml | 2738 ----------- web/api/src/app.module.ts | 7 - web/api/src/config/config.ts | 33 - web/api/src/main.ts | 23 - web/api/src/network/network.module.ts | 8 - web/api/src/network/network.service.ts | 294 -- web/api/src/proxy/proxy.controller.ts | 315 -- web/api/src/proxy/proxy.module.ts | 13 - web/api/src/proxy/proxy.service.ts | 507 -- web/api/src/storage/storage.module.ts | 8 - web/api/src/storage/storage.service.ts | 151 - web/api/src/vless/vless.module.ts | 8 - web/api/src/vless/vless.service.ts | 158 - web/api/tsconfig.build.json | 4 - web/api/tsconfig.json | 22 - web/components/connection_info.html | 37 - web/components/fallback_config.html | 50 - web/components/footer.html | 15 - web/components/header.html | 27 - web/components/logs.html | 18 - web/components/map.html | 4 - web/components/proxy_chain.html | 90 - web/components/server_list.html | 14 - web/components/subscription.html | 25 - web/components/switch.html | 32 - web/index.html | 55 - web/static/css/style.css | 130 - web/static/js/app.js | 733 --- 66 files changed, 1884 insertions(+), 14734 deletions(-) create mode 100644 .env.example delete mode 100644 .gitea/workflows/docker-build.yml create mode 100644 .gitea/workflows/gateway-build.yml delete mode 100644 .github/copilot-instructions.md create mode 100644 Dockerfile create mode 100644 docker-compose.gateway.yml delete mode 100644 docker-compose.server.yml delete mode 100644 docker-compose.yml delete mode 100644 docker/Dockerfile.singbox delete mode 100644 docker/entrypoint.sh delete mode 100644 docs/DOCKER.md delete mode 100644 docs/SERVER.md create mode 100644 docs/roadmap.md create mode 100644 entrypoint.sh create mode 100644 index.html delete mode 100644 install.ps1 delete mode 100644 manage.ps1 create mode 100644 package.json delete mode 100644 scripts/lib/Common.ps1 delete mode 100644 scripts/lib/Net.ps1 delete mode 100644 scripts/lib/System.ps1 delete mode 100644 scripts/setup-discord.ps1 delete mode 100644 scripts/setup-singbox.ps1 delete mode 100644 scripts/uninstall-all.ps1 delete mode 100644 scripts/view-logs.ps1 create mode 100644 src/server/config.js create mode 100644 src/server/index.js create mode 100644 src/server/singbox.js create mode 100644 src/server/subscription.js create mode 100644 src/web/App.jsx create mode 100644 src/web/styles.css create mode 100644 vite.config.js delete mode 100644 web/api/.gitignore delete mode 100644 web/api/nest-cli.json delete mode 100644 web/api/package-lock.json delete mode 100644 web/api/package.json delete mode 100644 web/api/pnpm-lock.yaml delete mode 100644 web/api/src/app.module.ts delete mode 100644 web/api/src/config/config.ts delete mode 100644 web/api/src/main.ts delete mode 100644 web/api/src/network/network.module.ts delete mode 100644 web/api/src/network/network.service.ts delete mode 100644 web/api/src/proxy/proxy.controller.ts delete mode 100644 web/api/src/proxy/proxy.module.ts delete mode 100644 web/api/src/proxy/proxy.service.ts delete mode 100644 web/api/src/storage/storage.module.ts delete mode 100644 web/api/src/storage/storage.service.ts delete mode 100644 web/api/src/vless/vless.module.ts delete mode 100644 web/api/src/vless/vless.service.ts delete mode 100644 web/api/tsconfig.build.json delete mode 100644 web/api/tsconfig.json delete mode 100644 web/components/connection_info.html delete mode 100644 web/components/fallback_config.html delete mode 100644 web/components/footer.html delete mode 100644 web/components/header.html delete mode 100644 web/components/logs.html delete mode 100644 web/components/map.html delete mode 100644 web/components/proxy_chain.html delete mode 100644 web/components/server_list.html delete mode 100644 web/components/subscription.html delete mode 100644 web/components/switch.html delete mode 100644 web/index.html delete mode 100644 web/static/css/style.css delete mode 100644 web/static/js/app.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7bb7c8d --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +PORT=3456 +PROXY_PORT=8080 +TPROXY_PORT=7895 +TPROXY_MARK=1 +TPROXY_TABLE=100 +TPROXY_CHAIN=VPN_PROXY_TPROXY +ROUTING_RU_DIRECT=true +LOG_LEVEL=info diff --git a/.gitea/workflows/docker-build.yml b/.gitea/workflows/docker-build.yml deleted file mode 100644 index 7b7bd7a..0000000 --- a/.gitea/workflows/docker-build.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Build and Deploy Sing-proxy - -on: - push: - branches: [master] - workflow_dispatch: - -env: - DEPLOY_PATH: /opt/vpn-proxy - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Clone repository - env: - GIT_TOKEN: ${{ secrets.REGISTRY_TOKEN }} - run: | - SERVER_HOST=$(echo "${{ gitea.server_url }}" | sed 's|https\?://||') - git clone --depth 2 "http://${{ gitea.actor }}:${GIT_TOKEN}@${SERVER_HOST}/${{ gitea.repository }}.git" . - git checkout ${{ gitea.sha }} - - - name: Build and push image - run: | - REGISTRY_HOST=$(echo "${{ gitea.server_url }}" | sed 's|https\?://||') - IMAGE="${REGISTRY_HOST}/${{ gitea.repository }}/sing-proxy" - - echo "${{ secrets.REGISTRY_TOKEN }}" | docker login "$REGISTRY_HOST" -u "${{ gitea.actor }}" --password-stdin - - docker build \ - -f docker/Dockerfile.singbox \ - -t "${IMAGE}:latest" \ - . - - docker push "${IMAGE}:latest" - echo "Pushed: ${IMAGE}:latest" - - deploy: - needs: build - runs-on: lxc-111 - steps: - - name: Deploy to LXC 111 - run: | - REGISTRY_HOST=$(echo "${{ gitea.server_url }}" | sed 's|https\?://||') - IMAGE="${REGISTRY_HOST}/${{ gitea.repository }}/sing-proxy" - - echo "Logging into registry..." - echo "${{ secrets.REGISTRY_TOKEN }}" | docker login "$REGISTRY_HOST" -u "${{ gitea.actor }}" --password-stdin - - echo "Pulling latest image..." - docker pull "${IMAGE}:latest" - - echo "Stopping old container..." - docker stop sing-proxy 2>/dev/null || true - docker rm sing-proxy 2>/dev/null || true - - echo "Starting new container..." - docker run -d \ - --name sing-proxy \ - --network host \ - --restart unless-stopped \ - -e PORT=3456 \ - -e PROXY_PORT=8080 \ - -v ${{ env.DEPLOY_PATH }}/data:/app/data \ - --memory=256m \ - "${IMAGE}:latest" - - echo "Deployment complete!" - sleep 3 - docker ps | grep sing-proxy diff --git a/.gitea/workflows/gateway-build.yml b/.gitea/workflows/gateway-build.yml new file mode 100644 index 0000000..8c461b7 --- /dev/null +++ b/.gitea/workflows/gateway-build.yml @@ -0,0 +1,28 @@ +name: Build Gateway Image + +on: + push: + branches: [master] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Clone repository + env: + GIT_TOKEN: ${{ secrets.REGISTRY_TOKEN }} + run: | + SERVER_HOST=$(echo "${{ gitea.server_url }}" | sed 's|https\?://||') + git clone --depth 2 "http://${{ gitea.actor }}:${GIT_TOKEN}@${SERVER_HOST}/${{ gitea.repository }}.git" . + git checkout ${{ gitea.sha }} + + - name: Build and push gateway image + run: | + REGISTRY_HOST=$(echo "${{ gitea.server_url }}" | sed 's|https\?://||') + IMAGE="${REGISTRY_HOST}/${{ gitea.repository }}/gateway" + + echo "${{ secrets.REGISTRY_TOKEN }}" | docker login "$REGISTRY_HOST" -u "${{ gitea.actor }}" --password-stdin + docker build -t "${IMAGE}:latest" -t "${IMAGE}:${{ gitea.sha }}" . + docker push "${IMAGE}:latest" + docker push "${IMAGE}:${{ gitea.sha }}" diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index 6877a2f..0000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,104 +0,0 @@ -# Project Guidelines - -## Overview - -VPN-Proxy is a self-hosted VPN/proxy management system using **sing-box** as the core proxy engine (VLESS + REALITY TLS). It consists of a NestJS (TypeScript) backend, a vanilla HTML/JS frontend, PowerShell scripts for Windows management, and Docker for deployment. Documentation and UI are in **Russian**. - -## Architecture - -``` -Browser → NestJS web server (PORT, default 3456) - ├─ Serves index.html with SSI-like includes () - └─ API endpoints in web/api/src/proxy/proxy.controller.ts - ↓ writes config - data/client.json → sing-box binary (PROXY_PORT, default 8080) - ↓ reload via HTTP to RELOAD_PORT (9090, internal) - ↓ - VPN traffic out -``` - -### Key layers - -| Layer | Location | Notes | -|-------|----------|-------| -| Frontend | `web/index.html`, `web/components/`, `web/static/` | Tailwind via CDN, no build step | -| Backend | `web/api/` | NestJS + TypeScript, minimal deps | -| Proxy core | `docker/entrypoint.sh` + sing-box binary | Config in `data/client.json` | -| Windows client | `manage.ps1`, `scripts/` | PowerShell 7+ required, runs as Admin | -| Docker | `docker-compose.yml` (dev), `docker-compose.server.yml` (prod, host network) | - -### State files (`data/`) - -All JSON. Do not change their structure without updating both backend and JS consumers: -- `client.json` — active sing-box config -- `subscription.json` — subscription URL + selected server -- `fallback.json` — fallback proxy settings -- `proxy_enabled.json` — on/off toggle -- `start_time.json` — uptime timestamp -- `hwid` — immutable device ID (16-char hex), generated once - -## Build and Run - -```powershell -# Docker (dev, bridged network) -docker compose up -d # starts on localhost:3456 + 8080 -docker compose up -d --build # rebuild after changes - -# Docker (Linux VPS, host network for UDP) -docker compose -f docker-compose.server.yml up -d - -# Logs -docker logs -f sing-proxy - -# Windows native (PowerShell 7, Admin) -.\manage.ps1 - -# Backend dev (local) -cd web/api -npm install -npm run start:dev -``` - -Environment variables: `PORT` (3456), `PROXY_PORT` (8080), `RELOAD_PORT` (9090), `PROXY_BIND_IP` (0.0.0.0). - -## Conventions - -### Code style -- **TypeScript**: NestJS conventions — modules, controllers, services. `camelCase` for methods, `PascalCase` for classes -- **PowerShell**: `PascalCase` functions (e.g., `Write-Success`, `Manage-ScheduledTask`) -- **JSON keys**: `camelCase` (e.g., `serverPort`, `selectedServer`) -- **HTML element IDs**: `camelCase` (e.g., `subUrlInput`, `fallbackToggle`) - -### Adding features -- New API endpoint → controller in `web/api/src/proxy/proxy.controller.ts` + JS call in `web/static/js/app.js` -- Business logic → `web/api/src/proxy/proxy.service.ts` -- VLESS config changes → `web/api/src/vless/vless.service.ts` -- Persistent state → `web/api/src/storage/storage.service.ts` (JSON file I/O) -- Network utilities → `web/api/src/network/network.service.ts` -- Windows scripts → `scripts/setup-*.ps1`, shared helpers in `scripts/lib/` - -### Backend module structure -``` -web/api/src/ - main.ts — Bootstrap & static assets - app.module.ts — Root module - config/config.ts — Environment configuration - storage/ — JSON file persistence + HWID - vless/ — VLESS URL parsing + sing-box config generation - network/ — TCP latency + proxy performance measurement - proxy/ — API controller + business logic service -``` - -### VLESS handling -- Parsing is strict: requires `vless://uuid@host:port?pbk=...&sid=...` format (REALITY params mandatory) -- Subscription URLs must be `http://` or `https://` only - -## Pitfalls - -- **Windows Docker cannot use `network_mode: host`** — UDP (Discord voice, games) won't work in Docker on Windows. Use native sing-box via `manage.ps1` instead. -- **Port 9090 is internal only** — used for reload triggers via netcat, never expose externally. -- **`hwid` is immutable** — after first generation, changing it requires manual file deletion. -- **DOS line endings** — the Dockerfile runs `dos2unix` on shell scripts. Keep this in place. -- **sing-box needs a config before starting** — apply config via the web UI first; it won't bootstrap empty. -- **No test suite exists** — validate changes manually via Docker. -- **NestJS build required** — the Dockerfile runs `npm ci && npm run build` during image build. For local dev use `npm run start:dev`. diff --git a/.gitignore b/.gitignore index 169ec65..a7cadfd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,60 +1,24 @@ -# Python -__pycache__/ -*.py[cod] -*$py.class -*.so -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST +# Local archive with the previous implementation and runtime secrets +_archive/ -# Virtual Env -venv/ -.venv/ -env/ +# Runtime state .env +*.env.local +data/ +.vpn-proxy/ -# PyInstaller -*.manifest -*.spec +# Node/Vite +node_modules/ +dist/ +coverage/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* -# MacOS +# OS/editors .DS_Store -.AppleDouble -.LSOverride -._* -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# IDE & Editors .idea/ .vscode/ *.swp *.swo -*~ - -# Project Specific -data/ -_legacy/ -*.log -sing-box - -# Docker -docker-compose.override.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d65ac55 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,44 @@ +FROM node:22-bookworm-slim AS ui-build +WORKDIR /app +COPY package.json ./ +RUN npm install +COPY index.html vite.config.js ./ +COPY src/web ./src/web +RUN npm run build + +FROM debian:bookworm-slim +ARG SINGBOX_VERSION=1.12.13 + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates curl iptables iproute2 nodejs dumb-init \ + && rm -rf /var/lib/apt/lists/* + +RUN set -eux; \ + arch="$(dpkg --print-architecture)"; \ + case "$arch" in \ + amd64) sb_arch="amd64" ;; \ + arm64) sb_arch="arm64" ;; \ + *) echo "Unsupported architecture: $arch" >&2; exit 1 ;; \ + esac; \ + curl -fsSL "https://github.com/SagerNet/sing-box/releases/download/v${SINGBOX_VERSION}/sing-box-${SINGBOX_VERSION}-linux-${sb_arch}.tar.gz" -o /tmp/sing-box.tgz; \ + tar -xzf /tmp/sing-box.tgz -C /tmp; \ + mv "/tmp/sing-box-${SINGBOX_VERSION}-linux-${sb_arch}/sing-box" /usr/local/bin/sing-box; \ + chmod +x /usr/local/bin/sing-box; \ + rm -rf /tmp/sing-box* + +WORKDIR /app +COPY --from=ui-build /app/dist /app/dist +COPY src/server /app/src/server +COPY entrypoint.sh /entrypoint.sh + +RUN chmod +x /entrypoint.sh \ + && mkdir -p /etc/sing-box /var/lib/vpn-proxy /var/lib/sing-box + +ENV PORT=3456 \ + PROXY_PORT=8080 \ + TPROXY_PORT=7895 \ + DATA_DIR=/var/lib/vpn-proxy \ + SING_BOX_CONFIG=/etc/sing-box/config.json \ + SING_BOX_CACHE=/var/lib/sing-box/cache.db + +ENTRYPOINT ["dumb-init", "/entrypoint.sh"] diff --git a/README.md b/README.md index e22d0f2..2554425 100644 --- a/README.md +++ b/README.md @@ -1,363 +1,34 @@ -# 🌐 VPN Proxy — Домашний VPN в одной программе +# VPN Proxy Gateway -> **Простыми словами:** ваш компьютер подключается к удалённому VPN-серверу, и весь интернет-трафик идёт через него. Это нужно для доступа к заблокированным сайтам или для защиты данных в публичных Wi-Fi сетях. +Новая версия проекта начинается с `gateway`-режима: контейнер поднимается в `network_mode: host`, применяет TProxy-правила на хосте и запускает `sing-box` как прозрачный gateway для устройств в локальной сети. ---- +## Что уже заложено -## 📖 Что это такое? +- Web UI на Vite + React. +- Один простой Node control-server вместо отдельного backend framework. +- Парсинг subscription URL: JSON config, base64 список, plain-text VLESS links. +- Routing lists управляются из UI: можно отправлять отдельные домены/CIDR/порты в `direct`, `vpn` или `block`. +- Генерация `sing-box` config для gateway: + - `tproxy` inbound на `7895`; + - `mixed` inbound на `8080`; + - private IP ranges напрямую; + - RU rule sets напрямую; + - остальное через выбранный outbound. +- Docker entrypoint с idempotent TProxy setup/cleanup. -Это набор инструментов, который позволяет: +## Быстрый старт -1. **Запустить VPN-прокси** на вашем компьютере -2. **Управлять через удобное меню** — всё настраивается автоматически -3. **Подключить браузер или приложения** (например, VS Code, Discord) через этот прокси -4. **Работает с UDP** — голосовые звонки и игры тоже работают! - -### 🎯 Для кого это? - -- Пользователи, которым нужен VPN для работы или доступа к заблокированным ресурсам -- Разработчики, которые хотят направить трафик VS Code или других программ через VPN -- Геймеры, которым нужно запустить игры или Discord через VPN -- Люди, которые получили VLESS ссылку от VPN-провайдера - ---- - -## 🧩 Как это работает? - -``` -┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐ -│ Ваш браузер │────▶│ VPN Proxy │────▶│ VPN Сервер │────▶ Интернет -│ или Discord │ │ (порт 1080) │ │ (в другой стране)│ -└─────────────────┘ └──────────────────┘ └──────────────────┘ +```bash +cp .env.example .env +docker compose -f docker-compose.gateway.yml up -d --build ``` ---- +UI будет доступен на хосте по `http://:3456`. -## 🔧 Перед началом: Требования +## Важные ограничения v0.1 -### ✅ PowerShell 7 (Обязательно!) - -> ⚠️ **Важно:** Скрипты требуют PowerShell 7. Стандартный Windows PowerShell 5.1 **не подойдёт!** - -#### Проверьте вашу версию - -Откройте любой PowerShell и выполните: - -```powershell -$PSVersionTable.PSVersion.Major -``` - -- Если результат **7 или выше** — всё хорошо, переходите к установке ✅ -- Если **5 или ниже** — нужно установить PowerShell 7 👇 - -#### Установка PowerShell 7 - -**Способ 1: Через winget (самый простой)** - -Откройте обычный PowerShell или Командную строку и выполните: - -```powershell -winget install Microsoft.PowerShell -``` - -После установки закройте окно и откройте **PowerShell 7** (он появится в меню Пуск). - -**Способ 2: Скачать вручную** - -1. Перейдите: https://github.com/PowerShell/PowerShell/releases/latest -2. Скачайте файл `PowerShell-7.x.x-win-x64.msi` (где x.x.x — версия) -3. Запустите установщик и следуйте инструкциям -4. После установки используйте **PowerShell 7** из меню Пуск - -> 💡 **Как отличить?** PowerShell 7 имеет чёрный фон и надпись "pwsh" или "PowerShell 7". Старый PowerShell — синий фон. - ---- - -### ✅ URL Подписки или VLESS-ссылка - -Получите от вашего VPN-провайдера: - -- **Подписку**: URL, который начинается с `http://` или `https://` -- **VLESS-ссылку**: начинается с `vless://...` - ---- - -## 🚀 Установка на Windows - -### ⚡ Быстрая установка (Одной командой) - -Самый быстрый способ — использовать наш автоматический установщик. Он сам скачает проект и распакует его в `C:\Tools\vpn-proxy`. - -1. Откройте **PowerShell 7** от имени **Администратора** -2. Скопируйте и вставьте команду: - -```powershell -Set-ExecutionPolicy RemoteSigned -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iwr https://git.dokops.ru/dokril/vpn-proxy/raw/branch/master/install.ps1 | iex -``` - -> 💡 Если команда выдаст ошибку 404, попробуйте заменить `master` на `main` в ссылке, или используйте ручную установку ниже. - ---- - -### 📦 Ручная установка (если авто-установка не работает) - -Если вы предпочитаете всё делать сами: - -#### Шаг 1: Скачайте проект - -Мы рекомендуем использовать папку `C:\Tools`. - -```powershell -# 1. Создаем папку и переходим -New-Item -ItemType Directory -Force -Path "C:\Tools" | Out-Null -cd C:\Tools - -# 2. Клонируем или скачиваем архив -git clone https://git.dokops.ru/dokril/vpn-proxy - -# (Или скачайте ZIP вручную и распакуйте в C:\Tools\vpn-proxy) -``` - -#### Шаг 2: Запустите - -```powershell -cd C:\Tools\vpn-proxy -.\manage.ps1 -``` - -### Шаг 3: Выберите пункт [1] — VPN Клиент - -``` - [1] 📦 VPN Клиент (Sing-box) [НЕ УСТАНОВЛЕН] - Основной способ. Поддерживает UDP и игры. - - [2] 🎮 Настройка Discord/Vesktop [НЕ АКТИВЕН] - Маршрутизация приложений через прокси. - - --------------------------------------- - [3] 🔄 Обновить статус - [U] ❌ Удалить всё (Uninstall) - [q] Выход - -👉 Ваш выбор: 1 -``` - -### Шаг 4: Введите VLESS-ссылку или URL подписки - -Скрипт попросит ввести ссылку. Вставьте и нажмите Enter. - -**Готово!** 🎉 Прокси запущен на `127.0.0.1:1080` - -### 📂 Где всё хранится? - -Всё организовано в папке `C:\Tools`: - -1. **Сам проект:** `C:\Tools\vpn-proxy` - - `scripts/` — Скрипты управления (PowerShell) - - `web/` — Веб-интерфейс (для Docker/Python запуска) - - `docker/` — Конфигурация контейнеров -2. **Sing-box (VPN клиент):** `C:\Tools\sing-box` - - Здесь лежит `config.json` с вашими настройками и сам исполняемый файл -3. **ProxiFyre (для Discord):** `C:\Program Files\ProxiFyre` (системная служба) - ---- - -## ✅ Проверка работы - -После установки меню покажет статус и адреса подключения: - -``` - [1] 📦 VPN Клиент (Sing-box) [РАБОТАЕТ] - Основной способ. Поддерживает UDP и игры. - - 📡 ПОДКЛЮЧЕНИЕ К ПРОКСИ - ───────────────────────────── - Локально: 127.0.0.1:1080 - Из сети: - 192.168.1.100:1080 -``` - -### Проверка через терминал - -```powershell -# Без прокси — покажет ваш домашний IP -Invoke-WebRequest -Uri "https://ipinfo.io/ip" | Select-Object -ExpandProperty Content - -# Через прокси — должен показать IP VPN-сервера -Invoke-WebRequest -Proxy "http://127.0.0.1:1080" -Uri "https://ipinfo.io/ip" | Select-Object -ExpandProperty Content -``` - -Если IP-адреса разные — VPN работает! 🎉 - ---- - -## 🎮 Настройка Discord / Vesktop - -Discord не поддерживает системные настройки прокси, поэтому нужна дополнительная настройка. - -### Требования - -- ✅ Установленный VPN клиент (пункт [1] в меню) -- ✅ VPN клиент должен быть запущен (статус "РАБОТАЕТ") - -### Установка - -1. Запустите `.\manage.ps1` -2. Выберите пункт **[2] — Настройка Discord/Vesktop** -3. Выберите какое приложение настроить: - - Discord - - Vesktop - - Оба - -**Что устанавливается:** - -- Windows Packet Filter — драйвер для перехвата трафика -- ProxiFyre — служба, которая направляет трафик Discord через прокси - -После установки Discord/Vesktop будут автоматически работать через VPN! - ---- - -## ⚙️ Настройка приложений - -### Для VS Code - -Откройте настройки (Ctrl + ,), найдите "proxy" и добавьте: - -``` -http.proxy: http://127.0.0.1:1080 -``` - -Или добавьте в `settings.json`: - -```json -{ - "http.proxy": "http://127.0.0.1:1080", - "http.proxyStrictSSL": true -} -``` - -### Для браузера - -В настройках прокси вашего браузера укажите: - -- **Тип**: HTTP или SOCKS5 -- **Адрес**: `127.0.0.1` -- **Порт**: `1080` - -> 💡 **Совет:** Используйте расширение [Proxy SwitchyOmega](https://chrome.google.com/webstore/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif) для удобного переключения прокси в Chrome. - -### Для других программ - -Укажите SOCKS5 прокси: `127.0.0.1:1080` - ---- - -## 📋 Управление - -При повторном запуске `.\manage.ps1` скрипт покажет меню управления: - -| Действие | Как сделать | -| ----------------- | ------------------------------------ | -| Посмотреть статус | Запустить `.\manage.ps1` | -| Сменить сервер | Пункт [1] → "Сменить VLESS/Подписку" | -| Перезапустить | Пункт [1] → "Перезапустить" | -| Остановить | Пункт [1] → "Остановить" | -| Полностью удалить | Пункт [U] | - ---- - -## 🌍 Подключение из локальной сети - -Если вы хотите использовать прокси с других устройств (телефон, планшет): - -1. Посмотрите IP-адрес в меню (раздел "Из сети:") -2. На другом устройстве настройте прокси: `IP_ВАШЕГО_ПК:1080` - -Например: `192.168.1.100:1080` - ---- - -## ❓ Часто задаваемые вопросы - -### Ошибка "Файл не может быть загружен, так как выполнение сценариев отключено" - -**Решение:** Включите выполнение скриптов: - -```powershell -Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser -``` - -### Ошибка при запуске — непонятные символы или синтаксис - -**Причина:** Вы используете старый PowerShell 5.1 - -**Решение:** Установите PowerShell 7 (см. раздел "Перед началом") - -### Discord не подключается к голосовым каналам - -**Причина:** ProxiFyre не запущен или VPN клиент остановлен - -**Решение:** - -1. Запустите `.\manage.ps1` -2. Убедитесь что пункт [1] показывает "РАБОТАЕТ" -3. Убедитесь что пункт [2] показывает "АКТИВЕН" - -### Как узнать, работает ли VPN? - -1. Откройте https://ipinfo.io в браузере — это ваш реальный IP -2. Настройте прокси в браузере -3. Откройте https://ipinfo.io снова — должен показать другой IP - ---- - -## 🔧 Продвинутые варианты - -### Docker с веб-интерфейсом - -Если вы предпочитаете управлять через браузер с красивым интерфейсом: - -> 💡 **Порты Docker версии:** -> -> - **Веб-интерфейс:** `http://localhost:3456` -> - **Прокси:** `127.0.0.1:8080` (обратите внимание, отличается от нативной версии!) - -> ⚠️ **Внимание:** В этом режиме **Discord работать не будет**! -> Docker на Windows не поддерживает UDP-проксирование, которое необходимо для голосовых чатов. Если вам нужен рабочий Discord — используйте **основной способ** (пункт [1] в меню). - -📖 **[Инструкция по Docker](docs/DOCKER.md)** - -### Установка на удалённый сервер (VPS) - -Если вы хотите развернуть прокси на своём сервере в другой стране: - -📖 **[Инструкция по установке на сервер](docs/SERVER.md)** - ---- - -## 📚 Словарь терминов - -| Термин | Объяснение | -| ------------ | ----------------------------------------------------------------------------- | -| **Прокси** | Программа-посредник, которая передаёт ваши запросы в интернет от своего имени | -| **VPN** | Зашифрованный туннель между вашим компьютером и удалённым сервером | -| **VLESS** | Современный протокол VPN-соединения | -| **sing-box** | Программа-клиент для подключения к VPN | -| **SOCKS5** | Тип прокси, поддерживающий любой трафик (включая UDP для игр) | -| **Порт** | "Номер двери" для сетевых соединений | - ---- - -## 🆘 Нужна помощь? - -Если что-то не работает: - -1. Убедитесь что используете **PowerShell 7** -2. Запустите от имени **Администратора** -3. Проверьте статус в главном меню -4. Попробуйте переустановить: пункт [U], затем пункт [1] - ---- - -_Создано для простого и безопасного доступа в интернет_ 🛡️ +- IPv4 TProxy first. IPv6 routing будет отдельным этапом. +- DNS-перехват пока не включен. Для корректного gateway-сценария лучше выдать клиентам DNS через роутер/DHCP. +- Контейнер должен запускаться с `network_mode: host`, `NET_ADMIN`, `NET_RAW`. +- `_archive/` игнорируется git, потому что там лежит старая реализация и runtime state. +- Gateway не видит process name на клиентском ПК, поэтому правила для игр задаются через домены, suffix, IP CIDR и порты. diff --git a/docker-compose.gateway.yml b/docker-compose.gateway.yml new file mode 100644 index 0000000..0152b5a --- /dev/null +++ b/docker-compose.gateway.yml @@ -0,0 +1,24 @@ +services: + vpn-proxy-gateway: + build: + context: . + dockerfile: Dockerfile + container_name: vpn-proxy-gateway + network_mode: host + cap_add: + - NET_ADMIN + - NET_RAW + env_file: + - .env + environment: + DATA_DIR: /var/lib/vpn-proxy + SING_BOX_CONFIG: /etc/sing-box/config.json + SING_BOX_CACHE: /var/lib/sing-box/cache.db + volumes: + - vpn-proxy-data:/var/lib/vpn-proxy + - sing-box-cache:/var/lib/sing-box + restart: unless-stopped + +volumes: + vpn-proxy-data: + sing-box-cache: diff --git a/docker-compose.server.yml b/docker-compose.server.yml deleted file mode 100644 index dcf681d..0000000 --- a/docker-compose.server.yml +++ /dev/null @@ -1,40 +0,0 @@ -# ========================================== -# СЕРВЕРНАЯ КОНФИГУРАЦИЯ (Linux VPS) -# ========================================== -# Используйте этот файл на удалённом сервере: -# docker compose -f docker-compose.server.yml up -d -# -# network_mode: host решает проблему UDP ASSOCIATE -# для SOCKS5 прокси (важно для Discord голоса!) -# ========================================== - -version: "3.9" -services: - sing-proxy: - container_name: sing-proxy - image: ${REGISTRY_HOST:-192.168.50.109:3000}/dokril/vpn-proxy/sing-proxy:latest - - # HOST MODE — контейнер использует сеть хоста напрямую - # Это решает проблему UDP ASSOCIATE для SOCKS5 - # ВАЖНО: работает только на Linux, не на Windows/macOS! - network_mode: host - - environment: - # Порт веб-интерфейса (по умолчанию 3456) - - PORT=${PORT:-3456} - # Порт прокси HTTP/SOCKS5 (по умолчанию 8080) - - PROXY_PORT=${PROXY_PORT:-8080} - - volumes: - - ./data:/app/data - restart: unless-stopped - deploy: - resources: - limits: - memory: 256m - -# Порты при network_mode: host не нужны. -# Сервисы доступны напрямую на хосте: -# - 3456: Веб-интерфейс (PORT) -# - 8080: SOCKS5/HTTP прокси (PROXY_PORT) - diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 06ec541..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,21 +0,0 @@ -version: "3.9" -services: - sing-proxy: - build: - context: . - dockerfile: docker/Dockerfile.singbox - ports: - # Веб-интерфейс (можно переопределить: PORT=9090 docker compose up) - - "${PORT:-3456}:${PORT:-3456}" - # Прокси HTTP/SOCKS5 (можно переопределить: PROXY_PORT=8082 docker compose up) - - "${PROXY_PORT:-8080}:${PROXY_PORT:-8080}" - environment: - - PORT=${PORT:-3456} - - PROXY_PORT=${PROXY_PORT:-8080} - volumes: - - ./data:/app/data - restart: unless-stopped - deploy: - resources: - limits: - memory: 256m diff --git a/docker/Dockerfile.singbox b/docker/Dockerfile.singbox deleted file mode 100644 index 232f172..0000000 --- a/docker/Dockerfile.singbox +++ /dev/null @@ -1,36 +0,0 @@ -FROM alpine:3.20 -ARG SINGBOX_VER=1.12.13 - -# Устанавливаем зависимости, включая dos2unix для исправления скриптов -RUN apk add --no-cache curl ca-certificates tar jq bash coreutils netcat-openbsd nodejs npm dos2unix && update-ca-certificates - -# Устанавливаем pnpm -RUN npm install -g pnpm - -# Автоматическое определение архитектуры и установка sing-box -RUN ARCH=$(uname -m) && \ - if [ "$ARCH" = "x86_64" ]; then SB_ARCH="amd64"; \ - elif [ "$ARCH" = "aarch64" ]; then SB_ARCH="arm64"; \ - else SB_ARCH="amd64"; fi && \ - curl -L -o /tmp/sb.tar.gz https://github.com/SagerNet/sing-box/releases/download/v${SINGBOX_VER}/sing-box-${SINGBOX_VER}-linux-${SB_ARCH}.tar.gz \ - && tar -xf /tmp/sb.tar.gz -C /tmp \ - && mv /tmp/sing-box-${SINGBOX_VER}-linux-${SB_ARCH}/sing-box /usr/local/bin/sing-box \ - && chmod +x /usr/local/bin/sing-box \ - && adduser -D -u 1000 suser - -COPY --chown=suser:suser docker/entrypoint.sh /app/ -COPY --chown=suser:suser web/ /app/web/ - -# Собираем NestJS бэкенд -WORKDIR /app/web/api -RUN CI=true pnpm install --frozen-lockfile && pnpm run build && CI=true pnpm prune --prod -WORKDIR /app - -# Исправляем окончания строк (важно для Windows пользователей) и даем права на запуск -RUN dos2unix /app/*.sh && chmod +x /app/entrypoint.sh - -# Порты по умолчанию (можно переопределить через ENV) -# PORT - веб-интерфейс, PROXY_PORT - прокси -EXPOSE 3456 8080 9090 - -ENTRYPOINT ["/app/entrypoint.sh"] \ No newline at end of file diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh deleted file mode 100644 index 024dcd1..0000000 --- a/docker/entrypoint.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env bash -set -e - -CONFIG_FILE="/app/data/client.json" -SINGBOX_PID="" - -# Порты из ENV (по умолчанию: 3456 для веба, 8080 для прокси) -PORT="${PORT:-3456}" -PROXY_PORT="${PROXY_PORT:-8080}" - -# Ensure data directory exists -mkdir -p /app/data - -start_singbox() { - if [[ -f "$CONFIG_FILE" ]]; then - echo "$(date): Starting sing-box..." - sing-box run -c "$CONFIG_FILE" & - SINGBOX_PID=$! - echo "$(date): sing-box started with PID $SINGBOX_PID" - else - echo "$(date): Config file not found. Use web UI at :$PORT to apply config." - SINGBOX_PID="" - fi -} - -stop_singbox() { - if [[ -n "$SINGBOX_PID" ]]; then - echo "$(date): Stopping sing-box (PID $SINGBOX_PID)..." - kill "$SINGBOX_PID" 2>/dev/null || true - wait "$SINGBOX_PID" 2>/dev/null || true - SINGBOX_PID="" - fi -} - -restart_singbox() { - stop_singbox - start_singbox -} - -start_singbox - -# Start Web UI Server with configurable port -echo "$(date): Starting Web UI on port $PORT..." -PORT=$PORT PROXY_PORT=$PROXY_PORT node /app/web/api/dist/main.js & -WEBUI_PID=$! - -# HTTP Control Server (Simple Netcat loop) -# Listens on 9090. -# Endpoint: /reload -> Restart sing-box (used by web_server.py after config change) -( - while true; do - # Read the request using nc. - REQ=$(echo -e "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" | nc -l -p 9090 -q 1) - echo "$(date): Received request on 9090" - - if echo "$REQ" | grep -q "GET /reload"; then - echo "$(date): Action: RELOAD (Restart sing-box)" - restart_singbox - else - echo "$(date): Unknown request or ping." - fi - done -) & -CONTROL_PID=$! - -# Keep container alive - wait for any background process -echo "$(date): Entrypoint ready. Waiting for processes..." - -# Wait indefinitely - if WebUI dies, restart container -wait $WEBUI_PID diff --git a/docs/DOCKER.md b/docs/DOCKER.md deleted file mode 100644 index 146aee0..0000000 --- a/docs/DOCKER.md +++ /dev/null @@ -1,208 +0,0 @@ -# 🐳 Docker — Веб-интерфейс для управления VPN - -> **Это продвинутый способ** установки с красивым веб-интерфейсом. Для большинства пользователей рекомендуется использовать [основной способ через PowerShell](../README.md). - ---- - -## 📖 Что это даёт? - -- 🌐 **Веб-интерфейс** — управление через браузер на http://localhost:3456 -- 📡 **Подписки** — автоматическое получение списка серверов -- 🔄 **Переключение серверов** — в один клик -- 💾 **Сохранение настроек** — URL и выбранный сервер сохраняются - ---- - -## 🔧 Требования - -### Docker Desktop - -1. Скачайте: https://www.docker.com/products/docker-desktop/ -2. Установите и запустите -3. Убедитесь, что иконка 🐳 есть в трее (панель задач) - -> 💡 На Windows может потребоваться WSL2. Docker Desktop предложит его установить автоматически. - ---- - -## 🚀 Установка - -### Шаг 1: Откройте терминал - -Откройте PowerShell или Командную строку и перейдите в папку проекта: - -```powershell -cd путь\к\папке\vpn-proxy -``` - -### Шаг 2: Соберите контейнер - -```powershell -docker compose build -``` - -Это создаст образ со всеми необходимыми компонентами. Выполняется один раз. - -### Шаг 3: Запустите - -```powershell -docker compose up -d -``` - -Флаг `-d` запускает контейнер в фоновом режиме. - -### Шаг 4: Откройте веб-интерфейс - -Перейдите в браузере: **http://localhost:3456** - ---- - -## 🌐 Использование веб-интерфейса - -### Режим подписки - -1. Вставьте URL подписки в поле "Подписка" -2. Нажмите **"Загрузить серверы"** -3. Выберите сервер из списка -4. Нажмите **"Применить"** - -### Режим VLESS - -1. Перейдите на вкладку "VLESS Ключ" -2. Вставьте VLESS-ссылку (`vless://...`) -3. Нажмите **"Применить"** - -> 💡 Настройки сохраняются в папке `data/` и восстанавливаются при перезапуске. - ---- - -## 🌐 Порты - -| Порт | Назначение | URL | -| ------ | --------------------------- | --------------------- | -| `3456` | Веб-интерфейс | http://localhost:3456 | -| `8080` | HTTP/SOCKS5 прокси | `127.0.0.1:8080` | -| `9090` | API управления (внутренний) | — | - -### 🔧 Изменение порта прокси - -Если порт `8080` уже занят, можно запустить на другом порту (например, `8082`): - -**Способ 1: Через переменную окружения (Mac/Linux)** - -```bash -PROXY_PORT=8082 docker compose up -d -``` - -**Способ 2: Через переменную окружения (Windows PowerShell)** - -```powershell -$env:PROXY_PORT=8082; docker compose up -d -``` - -**Способ 3: Через .env файл (универсальный)** - -Создайте файл `.env` в корне проекта: - -``` -PROXY_PORT=8082 -``` - -Затем запустите: - -```bash -docker compose up -d -``` - -> 💡 URL подключения изменится на `http://127.0.0.1:8082` и `socks5://127.0.0.1:8082` - ---- - -## 📋 Управление контейнером - -| Действие | Команда | -| ----------------- | ---------------------------------- | -| Посмотреть статус | `docker ps` | -| Посмотреть логи | `docker logs --tail 50 sing-proxy` | -| Остановить | `docker compose stop` | -| Запустить снова | `docker compose start` | -| Перезапустить | `docker compose restart` | -| Полностью удалить | `docker compose down` | -| Пересобрать | `docker compose up -d --build` | - ---- - -## 🔄 Обновление - -Если вы обновили код из репозитория: - -```powershell -# Остановить текущий контейнер -docker compose down - -# Пересобрать с новыми изменениями -docker compose build --no-cache - -# Запустить заново -docker compose up -d -``` - -> 💡 Подписка и настройки сохраняются в папке `data/` и не потеряются. - ---- - -## ⚙️ Настройка приложений - -### Для VS Code - -```json -{ - "http.proxy": "http://127.0.0.1:8080", - "http.proxyStrictSSL": true -} -``` - -### Для браузера - -- **Адрес**: `127.0.0.1` -- **Порт**: `8080` -- **Тип**: HTTP или SOCKS5 - ---- - -## ❓ Проблемы и решения - -### Страница localhost:3456 не открывается - -**Причина:** Контейнер не запущен. - -```powershell -# Проверьте статус -docker ps - -# Если контейнера нет — запустите -docker compose up -d -``` - -### "Connection refused" - -**Причина:** VPN-ссылка не применена. - -1. Откройте http://localhost:3456 -2. Примените VLESS-ссылку или загрузите подписку - -### Медленное подключение - -Попробуйте другой сервер в веб-интерфейсе — некоторые серверы могут быть перегружены. - ---- - -## ⚠️ Ограничения Docker на Windows - -- **UDP для Discord:** Docker на Windows/macOS имеет проблемы с UDP ASSOCIATE для SOCKS5. Для Discord рекомендуется использовать [нативную установку](../README.md). - -- **Для полной поддержки UDP** используйте [установку на Linux сервер](SERVER.md) с `network_mode: host`. - ---- - -[← Вернуться к основной инструкции](../README.md) diff --git a/docs/SERVER.md b/docs/SERVER.md deleted file mode 100644 index c89ad3d..0000000 --- a/docs/SERVER.md +++ /dev/null @@ -1,278 +0,0 @@ -# 🌍 Установка на Сервер (Linux VPS) - -> Эта инструкция для установки прокси на удалённый сервер. После установки вы сможете подключаться к нему с любого устройства. - ---- - -## 📖 Зачем это нужно? - -- 🌐 **Один прокси для всех устройств** — компьютер, телефон, планшет -- 🔒 **Работает 24/7** — не нужно держать компьютер включённым -- 📡 **Полная поддержка UDP** — голосовые звонки и игры работают отлично -- 🏠 **Доступ из любого места** — дома, на работе, в поездке - ---- - -## 🔧 Требования к серверу - -- **ОС:** Ubuntu 20.04+, Debian 11+, или любой современный Linux -- **Ресурсы:** Минимум 512 MB RAM, 1 CPU -- **Порты:** 3456 (веб-интерфейс), 8080 (прокси) -- **Доступ:** SSH подключение - -> 💡 Подойдёт любой VPS за $3-5/месяц от DigitalOcean, Vultr, Hetzner и др. - ---- - -## 🚀 Установка - -### Шаг 1: Подключитесь к серверу - -Откройте терминал (PowerShell на Windows, Terminal на Mac/Linux): - -```bash -ssh root@ваш_сервер_ip -``` - -Введите пароль когда попросят. - -> 💡 **Совет:** Если вы на Windows и нет ssh команды, используйте PuTTY или Windows Terminal. - ---- - -### Шаг 2: Установите Docker - -Если Docker ещё не установлен: - -```bash -# Автоматическая установка Docker -curl -fsSL https://get.docker.com | sh - -# Проверка что Docker работает -docker --version -``` - ---- - -### Шаг 3: Загрузите проект - -**Вариант A: Через Git** - -```bash -git clone https://github.com/your-repo/vpn-proxy.git -cd vpn-proxy -``` - -**Вариант B: Загрузка файлов вручную** - -Если git недоступен, скачайте ZIP архив и распакуйте на сервере. - ---- - -### Шаг 4: Запустите контейнер - -> ⚠️ **Важно:** Используйте `docker-compose.server.yml` — он настроен для серверов! - -```bash -docker compose -f docker-compose.server.yml up -d -``` - -Это запустит контейнер с `network_mode: host`, что решает проблемы с UDP. - ---- - -### Шаг 5: Откройте порты в файрволе - -**Для UFW (Ubuntu/Debian):** - -```bash -ufw allow 3456/tcp # Веб-интерфейс -ufw allow 8080/tcp # Прокси TCP -ufw allow 8080/udp # Прокси UDP (для голоса/игр) -ufw reload -``` - -**Для firewalld (CentOS/RHEL):** - -```bash -firewall-cmd --permanent --add-port=3456/tcp -firewall-cmd --permanent --add-port=8080/tcp -firewall-cmd --permanent --add-port=8080/udp -firewall-cmd --reload -``` - -**Для iptables:** - -```bash -iptables -A INPUT -p tcp --dport 3456 -j ACCEPT -iptables -A INPUT -p tcp --dport 8080 -j ACCEPT -iptables -A INPUT -p udp --dport 8080 -j ACCEPT -``` - ---- - -### Шаг 6: Настройте VPN через веб-интерфейс - -1. Откройте в браузере: `http://ваш_сервер_ip:3456` -2. Вставьте VLESS-ссылку или URL подписки -3. Нажмите "Применить" - ---- - -## ✅ Проверка работы - -На сервере: - -```bash -# Проверить что контейнер запущен -docker ps - -# Посмотреть логи -docker logs --tail 20 sing-proxy -``` - -С вашего компьютера: - -```bash -# Проверить прокси -curl -x http://ваш_сервер_ip:8080 https://ipinfo.io/ip -``` - -Должен показать IP VPN-сервера (не IP вашего VPS). - ---- - -## 🖥️ Подключение с Windows - -### Настройка в manage.ps1 - -При настройке Discord (пункт [2]) вы можете указать адрес удалённого прокси: - -``` -Введите адрес прокси (IP:порт): ваш_сервер_ip:8080 -``` - -### Настройка в браузере/приложениях - -- **Адрес:** `ваш_сервер_ip` -- **Порт:** `8080` -- **Тип:** HTTP или SOCKS5 - ---- - -## 📋 Управление - -| Действие | Команда | -|----------|---------| -| Посмотреть статус | `docker ps` | -| Логи | `docker logs --tail 50 sing-proxy` | -| Остановить | `docker compose -f docker-compose.server.yml stop` | -| Запустить | `docker compose -f docker-compose.server.yml start` | -| Перезапустить | `docker compose -f docker-compose.server.yml restart` | -| Удалить | `docker compose -f docker-compose.server.yml down` | - ---- - -## 🔐 Рекомендации по безопасности - -### 1. Смените стандартные порты - -Отредактируйте `docker-compose.server.yml`: - -```yaml -environment: - - PORT=54321 # Вместо 3456 - - PROXY_PORT=12345 # Вместо 8080 -``` - -### 2. Ограничьте доступ к веб-интерфейсу - -Если веб-интерфейс нужен только для первоначальной настройки: - -```bash -# Закрыть веб-порт после настройки -ufw delete allow 3456/tcp -``` - -### 3. Используйте SSH туннель - -Для безопасного доступа к веб-интерфейсу: - -```bash -ssh -L 3456:localhost:3456 root@ваш_сервер_ip -``` - -Затем откройте http://localhost:3456 в браузере. - ---- - -## 🔄 Обновление - -```bash -cd vpn-proxy - -# Получить обновления -git pull - -# Пересобрать контейнер -docker compose -f docker-compose.server.yml down -docker compose -f docker-compose.server.yml build --no-cache -docker compose -f docker-compose.server.yml up -d -``` - ---- - -## ❓ Проблемы и решения - -### Порт 3456 не открывается - -**Причина:** Файрвол блокирует подключения. - -**Решение:** Проверьте настройки файрвола, см. Шаг 5. - -### "Permission denied" при запуске Docker - -**Решение:** - -```bash -# Добавить пользователя в группу docker -sudo usermod -aG docker $USER - -# Перезайти -exit -ssh root@ваш_сервер_ip -``` - -### Контейнер постоянно перезапускается - -```bash -# Посмотреть логи ошибок -docker logs sing-proxy -``` - -Обычно проблема в неверной VLESS-ссылке. - ---- - -## 📐 Изменение портов - -По умолчанию: -- **3456** — веб-интерфейс -- **8080** — прокси - -Для изменения создайте файл `.env` в папке проекта: - -```env -PORT=54321 -PROXY_PORT=12345 -``` - -И перезапустите: - -```bash -docker compose -f docker-compose.server.yml up -d -``` - ---- - -[← Вернуться к основной инструкции](../README.md) diff --git a/docs/roadmap.md b/docs/roadmap.md new file mode 100644 index 0000000..a272789 --- /dev/null +++ b/docs/roadmap.md @@ -0,0 +1,105 @@ +# Roadmap: VPN Proxy rebuild + +## Целевая модель + +Проект должен стать multi-mode системой вокруг `sing-box`: + +| Режим | Назначение | Runtime | Статус | +| --- | --- | --- | --- | +| `gateway` | LXC/VPS как gateway для роутера и всей сети | Docker `network_mode: host` + TProxy | делаем первым | +| `desktop-proxy` | Mac/Linux локальный HTTP/SOCKS proxy с fallback | Docker bridged ports | позже переносим из старой реализации | +| `windows-gaming` | Windows для игр/Discord/Vesktop | native `sing-box.exe` + ProxiFyre | позже приводим в порядок | + +## Gateway mode + +Цель: контейнер, который становится прозрачным gateway для сети. + +Требования: + +- `sing-box` внутри контейнера. +- `network_mode: host`. +- `CAP_NET_ADMIN` и `CAP_NET_RAW`. +- TProxy inbound на `7895`. +- Mixed HTTP/SOCKS inbound на `8080`. +- Web UI на `3456`. +- Subscription URL вводится в UI, парсится, пользователь выбирает сервер. +- Пользовательские routing lists управляются из UI. +- Генерируется `/etc/sing-box/config.json`. +- `sing-box check` перед применением. +- Restart `sing-box` после применения. +- Idempotent iptables setup. +- Cleanup iptables/ip rule/ip route при остановке контейнера. + +Маршрутизация v1: + +- private IP ranges -> `direct`. +- пользовательские списки -> `direct`, `vpn` или `block`. +- `geoip-ru` -> `direct`. +- `geosite-category-ru` -> `direct`. +- все остальное -> выбранный VPN outbound. + +Порядок правил: + +1. safety private-direct, чтобы не ломать LAN. +2. custom routing lists из UI. +3. RU direct rules. +4. default VPN outbound. + +Формат пользовательского списка: + +- `name`. +- `enabled`. +- `outbound`: `direct`, `vpn`, `block`. +- `domains`: exact domains. +- `domainSuffixes`: доменные suffix, удобно для игр/сервисов. +- `domainKeywords`: keyword matching. +- `ipCidrs`: CIDR ranges. +- `ports`: TCP/UDP ports. +- `networks`: `tcp`, `udp`. +- UI должен автосохранять списки с debounce, чтобы polling state не затирал незавершенное редактирование. + +Важно: gateway не видит process name на клиентском ПК. Для сценария вроде "League of Legends всегда direct" нужны домены, CIDR и порты Riot, а не имя процесса. + +Отдельно решить позже: + +- DNS strategy: DHCP DNS, DNS redirect или local DNS inbound. +- IPv6 TProxy. +- nftables backend. +- health checks и smoke diagnostics. +- secret storage через Infisical/Vault/env. + +## Desktop proxy mode + +Цель: сохранить удобный Docker-сценарий для Mac/Linux без TProxy. + +Требования: + +- UI на `3456`. +- Mixed inbound на `8080`. +- Subscription parser. +- Выбор сервера. +- Fallback proxy через `urltest`. +- Direct mode toggle. +- Не требует `NET_ADMIN`. + +## Windows gaming mode + +Цель: сохранить сценарий для Discord/Vesktop/игр. + +Требования: + +- Native `sing-box.exe`. +- Scheduled task или Windows service. +- ProxiFyre + WinPacketFilter для приложений, которые не умеют proxy. +- Управление из PowerShell helper. +- Позже можно сделать Electron/Tauri UI поверх privileged helper. + +## Рабочий порядок + +1. Сделать новый gateway root. +2. Реализовать Docker image + entrypoint TProxy lifecycle. +3. Реализовать маленький control-server. +4. Реализовать Vite + React UI для subscription -> server select -> apply. +5. Добавить gateway docs/install script. +6. Потом переносить desktop-proxy. +7. Потом приводить Windows mode к новой архитектуре. diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..5f690db --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +set -euo pipefail + +TPROXY_PORT="${TPROXY_PORT:-7895}" +TPROXY_MARK="${TPROXY_MARK:-1}" +TPROXY_TABLE="${TPROXY_TABLE:-100}" +TPROXY_CHAIN="${TPROXY_CHAIN:-VPN_PROXY_TPROXY}" +BYPASS_CIDRS="${BYPASS_CIDRS:-0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.168.0.0/16 224.0.0.0/4 240.0.0.0/4}" + +log() { + printf '[gateway-entrypoint] %s\n' "$*" +} + +ipt() { + iptables -w "$@" +} + +cleanup_tproxy() { + log "cleanup tproxy rules" + ipt -t mangle -D PREROUTING -j "$TPROXY_CHAIN" 2>/dev/null || true + ipt -t mangle -F "$TPROXY_CHAIN" 2>/dev/null || true + ipt -t mangle -X "$TPROXY_CHAIN" 2>/dev/null || true + ip rule del fwmark "$TPROXY_MARK" table "$TPROXY_TABLE" 2>/dev/null || true + ip route flush table "$TPROXY_TABLE" 2>/dev/null || true +} + +setup_tproxy() { + log "setup tproxy on port ${TPROXY_PORT}, mark ${TPROXY_MARK}, table ${TPROXY_TABLE}" + cleanup_tproxy + + ip rule add fwmark "$TPROXY_MARK" table "$TPROXY_TABLE" 2>/dev/null || true + ip route replace local 0.0.0.0/0 dev lo table "$TPROXY_TABLE" + + ipt -t mangle -N "$TPROXY_CHAIN" + ipt -t mangle -A "$TPROXY_CHAIN" -m mark --mark "$TPROXY_MARK" -j RETURN + + for cidr in $BYPASS_CIDRS; do + ipt -t mangle -A "$TPROXY_CHAIN" -d "$cidr" -j RETURN + done + + ipt -t mangle -A "$TPROXY_CHAIN" -p tcp -j TPROXY --on-port "$TPROXY_PORT" --tproxy-mark "$TPROXY_MARK/$TPROXY_MARK" + ipt -t mangle -A "$TPROXY_CHAIN" -p udp -j TPROXY --on-port "$TPROXY_PORT" --tproxy-mark "$TPROXY_MARK/$TPROXY_MARK" + ipt -t mangle -A PREROUTING -j "$TPROXY_CHAIN" +} + +setup_tproxy + +node /app/src/server/index.js & +APP_PID=$! + +shutdown() { + log "shutdown requested" + kill "$APP_PID" 2>/dev/null || true + wait "$APP_PID" 2>/dev/null || true + cleanup_tproxy +} + +trap 'shutdown; exit 0' SIGTERM SIGINT + +wait "$APP_PID" +STATUS=$? +cleanup_tproxy +exit "$STATUS" diff --git a/index.html b/index.html new file mode 100644 index 0000000..c596d96 --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + VPN Proxy Gateway + + +
+ + + diff --git a/install.ps1 b/install.ps1 deleted file mode 100644 index 4e90d3f..0000000 --- a/install.ps1 +++ /dev/null @@ -1,104 +0,0 @@ -# ========================================== -# 🚀 VPN PROXY INSTALLER -# ========================================== -# This script automatically downloads and installs VPN Proxy -# Usage: -# iwr https://git.dokops.ru/dokril/vpn-proxy/raw/branch/master/install.ps1 | iex - -# Enable UTF-8 for emoji support -[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 -$ErrorActionPreference = "Stop" - -# --- 1. Check Admin Rights --- -if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]"Administrator")) { - Write-Warning "⚠️ Administrator rights required!" - Write-Host "🔄 Restarting script as Administrator..." -ForegroundColor Cyan - - # Save script to temp file if running from memory (iex) - if ($MyInvocation.MyCommand.CommandType -eq 'Script') { - Start-Process powershell -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$($MyInvocation.MyCommand.Path)`"" -Verb RunAs - } - else { - # If running via IEX, we cannot simple restart the file. - # We ask user to run terminal as admin. - Write-Error "Please run PowerShell as Administrator and try again." - } - exit -} - -# --- 2. Settings --- -$InstallRoot = "C:\Tools" -$InstallDir = "$InstallRoot\vpn-proxy" -# Exact link provided by user -$ZipUrl = "https://git.dokops.ru/dokril/vpn-proxy/archive/master.zip" -$TempZip = "$env:TEMP\vpn-proxy-install.zip" - -Write-Host "🚀 Starting VPN Proxy installation..." -ForegroundColor Green -Write-Host "📂 Install path: $InstallDir" -ForegroundColor Gray - -# Move to temp folder to avoid blocking deletion if we are already in C:\Tools\vpn-proxy -Set-Location $env:TEMP - -# --- 3. Prepare Directory --- -if (-not (Test-Path $InstallRoot)) { - New-Item -ItemType Directory -Path $InstallRoot -Force | Out-Null -} - -# --- 4. Downloading --- -Write-Host "⬇️ Downloading update archive..." -ForegroundColor Cyan -try { - Invoke-WebRequest -Uri $ZipUrl -OutFile $TempZip -} -catch { - Write-Error "❌ Failed to download from $ZipUrl`nCheck your internet connection." - exit 1 -} - -# --- 5. Extracting --- -Write-Host "📦 Extracting..." -ForegroundColor Cyan - -# If folder exists, delete old one -if (Test-Path $InstallDir) { - try { - Remove-Item $InstallDir -Recurse -Force -ErrorAction Stop - } - catch { - Write-Warning "⚠️ Failed to delete old folder $InstallDir" - Write-Warning " Error: $($_.Exception.Message)" - Write-Warning " Make sure files are not open in other programs and you are not inside this folder." - - $retry = Read-Host " Press Enter to try again (or Ctrl+C to cancel)" - try { - Remove-Item $InstallDir -Recurse -Force -ErrorAction Stop - } - catch { - Write-Error "❌ Still failed to delete folder. Installation aborted." - exit 1 - } - } -} - -Expand-Archive -Path $TempZip -DestinationPath $InstallRoot -Force - -# Archives usually extract to vpn-proxy-master or vpn-proxy-main -# We need to rename it to vpn-proxy -$ExtractedFolder = Get-ChildItem -Path $InstallRoot -Directory | Where-Object { $_.Name -match "vpn-proxy-(master|main)" } | Select-Object -First 1 - -if ($ExtractedFolder) { - Rename-Item -Path $ExtractedFolder.FullName -NewName "vpn-proxy" -Force -} - -# Remove temp archive -Remove-Item $TempZip -Force - -if (-not (Test-Path "$InstallDir\manage.ps1")) { - Write-Error "❌ Installation error: manage.ps1 not found in $InstallDir" - exit 1 -} - -# --- 6. Finish --- -Write-Host "✅ Installation complete!" -ForegroundColor Green -Write-Host "" -Write-Host "To start the control menu, run:" -ForegroundColor Cyan -Write-Host "& `"$InstallDir\manage.ps1`"" -ForegroundColor Yellow -Write-Host "" diff --git a/manage.ps1 b/manage.ps1 deleted file mode 100644 index 7340a47..0000000 --- a/manage.ps1 +++ /dev/null @@ -1,96 +0,0 @@ -# ========================================== -# 🚀 VPN PROXY CONTROL CENTER (WINDOWS) -# ========================================== -# Главный скрипт управления. Запускать от имени Администратора. -# Использование: .\manage.ps1 [-Debug] - -param([switch]$Debug) - -$ScriptDir = if ($PSScriptRoot) { $PSScriptRoot } else { Split-Path -Parent $MyInvocation.MyCommand.Path } -$LibDir = "$ScriptDir\scripts\lib" - -# Проверка библиотек -if (!(Test-Path "$LibDir\Common.ps1")) { - Write-Host "❌ Ошибка: Не найдены библиотеки в $LibDir" -ForegroundColor Red - exit 1 -} - -. "$LibDir\Common.ps1" -. "$LibDir\System.ps1" - -# Установка режима отладки -if ($Debug) { - Set-DebugMode -Enabled $true -} - -Ensure-Admin - -while ($true) { - Write-Header "VPN PROXY CONTROL CENTER" -ClearScreen - - # --- СБОР СТАТУСОВ --- - - # 1. Native Sing-box - $sbStatus = Get-TaskStatus -Name "SingBoxProxy" - $sbStr = if ($sbStatus -eq "Running") { "РАБОТАЕТ" } else { "ОСТАНОВЛЕН" } - $sbColor = if ($sbStatus -eq "Running") { "Green" } else { "Yellow" } - if (!$sbStatus) { $sbStr = "НЕ УСТАНОВЛЕН"; $sbColor = "Gray" } - - # 2. Discord Proxy - $discSvc = Get-Service -Name "ProxiFyreService" -ErrorAction SilentlyContinue - $discStr = if ($discSvc.Status -eq 'Running') { "АКТИВЕН" } else { "НЕ АКТИВЕН" } - $discColor = if ($discSvc.Status -eq 'Running') { "Green" } else { "Gray" } - - # --- ОТРИСОВКА МЕНЮ --- - - Write-Host " [1] 📦 VPN Клиент (Sing-box)" -NoNewline -ForegroundColor White - Write-Host " [$sbStr]" -ForegroundColor $sbColor - Write-Host " Основной способ. Поддерживает UDP и игры." -ForegroundColor Gray - - # Показываем информацию о подключении если sing-box работает - if ($sbStatus -eq "Running") { - $LocalProxyPort = 1080 - . "$LibDir\Net.ps1" - $ips = Get-LocalIPs - - Write-Host "" - Write-Host " 📡 ПОДКЛЮЧЕНИЕ К ПРОКСИ" -ForegroundColor Cyan - Write-Host " ─────────────────────────────" -ForegroundColor DarkGray - Write-Host " Локально: " -NoNewline -ForegroundColor Gray - Write-Host "127.0.0.1:$LocalProxyPort" -ForegroundColor Green - - if ($ips) { - Write-Host " Из сети:" -ForegroundColor Gray - foreach ($ip in $ips) { - Write-Host " ${ip}:$LocalProxyPort" -ForegroundColor Yellow - } - } - Write-Host "" - } - Write-Host "" - - Write-Host " [2] 🎮 Настройка Discord/Vesktop" -NoNewline -ForegroundColor White - Write-Host " [$discStr]" -ForegroundColor $discColor - Write-Host " Маршрутизация приложений через прокси." -ForegroundColor Gray - Write-Host "" - - Write-Host " ---------------------------------------" -ForegroundColor DarkGray - - - Write-Host " [3] 🔄 Обновить статус" -ForegroundColor White - Write-Host " [L] 📜 Просмотр логов" -ForegroundColor White - Write-Host " [U] ❌ Удалить всё (Uninstall)" -ForegroundColor Red - Write-Host " [q] Выход" -ForegroundColor White - Write-Host "" - - $choice = Read-Host "👉 Ваш выбор" - - switch ($choice) { - "1" { & "$ScriptDir\scripts\setup-singbox.ps1" } - "2" { & "$ScriptDir\scripts\setup-discord.ps1" } - "3" { continue } - "l" { & "$ScriptDir\scripts\view-logs.ps1" } - "u" { & "$ScriptDir\scripts\uninstall-all.ps1" } - "q" { exit } - } -} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c4e95cc --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "vpn-proxy-gateway", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "Gateway-first VPN proxy control panel for sing-box TProxy deployments.", + "scripts": { + "dev": "vite --host 0.0.0.0", + "build": "vite build", + "start": "node src/server/index.js" + }, + "dependencies": { + "@vitejs/plugin-react": "^5.0.0", + "vite": "^7.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": {} +} diff --git a/scripts/lib/Common.ps1 b/scripts/lib/Common.ps1 deleted file mode 100644 index 0ed302b..0000000 --- a/scripts/lib/Common.ps1 +++ /dev/null @@ -1,118 +0,0 @@ -# ========================================== -# 🛠️ COMMON UTILS -# ========================================== - -# --- ГЛОБАЛЬНЫЕ НАСТРОЙКИ --- - -# Режим отладки (передаётся через -Debug) -if (-not (Test-Path variable:script:DebugMode)) { - $script:DebugMode = $false -} - -function Set-DebugMode { - param([bool]$Enabled) - $script:DebugMode = $Enabled - if ($Enabled) { - Write-Host " 🔧 Debug режим включён" -ForegroundColor Magenta - } -} - -function Get-DebugMode { - return $script:DebugMode -} - -# --- ЦВЕТА И ВЫВОД --- - -function Write-Step { param($msg) Write-Host "`n📦 $msg" -ForegroundColor Cyan } -function Write-Success { param($msg) Write-Host " ✅ $msg" -ForegroundColor Green } -function Write-Warning { param($msg) Write-Host " ⚠️ $msg" -ForegroundColor Yellow } -function Write-Error { param($msg) Write-Host " ❌ $msg" -ForegroundColor Red } -function Write-Info { param($msg) Write-Host " ℹ️ $msg" -ForegroundColor Gray } - -function Write-DebugLog { - param($msg) - if ($script:DebugMode) { - Write-Host " [DEBUG] $msg" -ForegroundColor DarkGray - } -} - -function Write-Header { - param($Title, [switch]$ClearScreen) - - if ($ClearScreen -and -not $script:DebugMode) { - Clear-Host - } - - Write-Host "" - Write-Host "==========================================" -ForegroundColor Cyan - Write-Host " $Title" -ForegroundColor Cyan - Write-Host "==========================================" -ForegroundColor Cyan - Write-Host "" -} - -# --- ЗАПУСК КОМАНД --- - -function Invoke-Silent { - param( - [string]$FilePath, - [string]$Arguments, - [switch]$Wait - ) - - $psi = New-Object System.Diagnostics.ProcessStartInfo - $psi.FileName = $FilePath - $psi.Arguments = $Arguments - $psi.UseShellExecute = $false - $psi.CreateNoWindow = $true - - if (-not $script:DebugMode) { - $psi.RedirectStandardOutput = $true - $psi.RedirectStandardError = $true - } - - $process = [System.Diagnostics.Process]::Start($psi) - - if ($Wait) { - $process.WaitForExit() - return $process.ExitCode - } - - return $process -} - -# --- ПОЛЕЗНЫЕ ФУНКЦИИ --- - -function Get-ScriptDirectory { - if ($PSScriptRoot) { return $PSScriptRoot } - return Split-Path -Parent $MyInvocation.MyCommand.Path -} - -function Ensure-Admin { - $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") - if (-not $isAdmin) { - Write-Host "⛔ Требуются права АДМИНИСТРАТОРА!" -ForegroundColor Red - Write-Host " Пожалуйста, запустите скрипт от имени администратора." -ForegroundColor Gray - Start-Sleep -Seconds 3 - exit 1 - } -} - -function Show-Menu { - param( - [string]$Title, - [System.Collections.Specialized.OrderedDictionary]$Options, - [string]$Prompt = "👉 Ваш выбор" - ) - - if ($Title) { - Write-Host "`n$Title" -ForegroundColor Yellow - } - - $keys = $Options.Keys - foreach ($key in $keys) { - Write-Host " [$key] $($Options[$key])" -ForegroundColor White - } - Write-Host "" - - return Read-Host "$Prompt" -} diff --git a/scripts/lib/Net.ps1 b/scripts/lib/Net.ps1 deleted file mode 100644 index 74ca1a0..0000000 --- a/scripts/lib/Net.ps1 +++ /dev/null @@ -1,144 +0,0 @@ -# ========================================== -# 🌐 NET UTILS -# ========================================== - -# --- CONFIG --- -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - -# --- ФУНКЦИИ --- - -$script:HwidFile = "C:\Tools\sing-box\hwid" -$script:AppName = "VPN-Proxy-Control by Dokril" - -function Get-HWID { - # Генерация или чтение HWID из файла - if (Test-Path $script:HwidFile) { - return (Get-Content $script:HwidFile -Raw).Trim() - } - - # Генерируем новый HWID - $hwid = [Guid]::NewGuid().ToString("N").Substring(0, 16) - - # Сохраняем - $dir = Split-Path $script:HwidFile -Parent - if (!(Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } - Set-Content -Path $script:HwidFile -Value $hwid - - return $hwid -} - -function Get-SubscriptionHeaders { - # Формируем заголовки как в server.py - $osName = "windows" - $osVersion = [Environment]::OSVersion.Version.ToString() - - return @{ - "User-Agent" = "singbox" - "x-hwid" = (Get-HWID) - "x-device-os" = $osName - "x-ver-os" = $osVersion - "x-device-model" = $script:AppName - } -} - -function Download-File { - param( - [string]$Url, - [string]$Destination, - [string]$UserAgent = "VPN-Proxy-Installer" - ) - - try { - $req = [System.Net.HttpWebRequest]::Create($Url) - $req.UserAgent = $UserAgent - $resp = $req.GetResponse() - - $stream = $resp.GetResponseStream() - $fs = [System.IO.File]::Create($Destination) - $msgLen = $resp.ContentLength - - $buffer = New-Object byte[] 10240 - $count = 0 - $total = 0 - - do { - $count = $stream.Read($buffer, 0, $buffer.Length) - $fs.Write($buffer, 0, $count) - $total += $count - # Можно добавить прогресс бар, но пока просто качаем - } while ($count -gt 0) - - $fs.Close() - $stream.Close() - $resp.Close() - - # Unblock file to prevent "Mark of the Web" issues - Unblock-File -Path $Destination -ErrorAction SilentlyContinue - - return $true - } - catch { - Write-Error "Ошибка скачивания: $_" - return $false - } -} - - -function Get-SubscriptionData { - param( - [string]$Url, - [string]$UserAgent = "singbox", - $Headers = @{} - ) - - Write-Info "Загружаю подписку..." - - $rawContent = $null - $userInfo = @{} - - # 1. Получаем ответ - try { - $response = Invoke-WebRequest -Uri $Url -Headers $Headers -TimeoutSec 15 -UseBasicParsing - $rawContent = $response.Content - - # Парсим subscription-userinfo header - $userInfoHeader = $response.Headers["subscription-userinfo"] - if ($userInfoHeader) { - $parts = $userInfoHeader -split ";" - foreach ($part in $parts) { - if ($part -match "(\w+)=(\d+)") { - $userInfo[$matches[1]] = [int64]$matches[2] - } - } - } - } - catch { - return @{ - success = $false - error = "Ошибка загрузки: $($_.Exception.Message)" - rawContent = $null - } - } - - # 2. Пробуем парсить как JSON - try { - $config = $rawContent | ConvertFrom-Json - return @{ - success = $true - config = $config - rawContent = $rawContent - userInfo = $userInfo - } - } - catch { - # JSON не распарсился — возвращаем rawContent для дальнейшей обработки - return @{ - success = $false - error = "Ответ не в формате JSON (возможно Base64 или список ссылок)" - rawContent = $rawContent - userInfo = $userInfo - } - } -} - - diff --git a/scripts/lib/System.ps1 b/scripts/lib/System.ps1 deleted file mode 100644 index 58c8795..0000000 --- a/scripts/lib/System.ps1 +++ /dev/null @@ -1,175 +0,0 @@ -# ========================================== -# 🖥️ SYSTEM UTILS -# ========================================== - - -# --- СИСТЕМНАЯ ИНФОРМАЦИЯ --- - -function Get-SystemInfo { - return @{ - os = "windows" - version = [System.Environment]::OSVersion.Version.Major.ToString() - } -} - -function Ensure-VCRedist { - Write-Info "Проверка Visual C++ Redistributable..." - - # Check registry for VC++ 2015-2022 (x64) - # Key: HKLM:\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64 - $regPath = "HKLM:\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64" - if (Test-Path $regPath) { - $installed = (Get-ItemProperty -Path $regPath).Installed - if ($installed -eq 1) { - Write-Success "Visual C++ Redistributable уже установлен." - return - } - } - - Write-Warning "Visual C++ Redistributable не найден. Устанавливаю..." - - $vcUrl = "https://aka.ms/vs/17/release/vc_redist.x64.exe" - $vcFile = "$env:TEMP\vc_redist.x64.exe" - - if (Download-File -Url $vcUrl -Destination $vcFile) { - Write-Step "Установка библиотек Visual C++..." - $process = Start-Process -FilePath $vcFile -ArgumentList "/install", "/quiet", "/norestart" -PassThru -Wait - - if ($process.ExitCode -eq 0 -or $process.ExitCode -eq 3010) { - # 3010 = reboot required (usually works without immediate reboot) - Write-Success "Библиотеки установлены!" - } - else { - Write-Error "Ошибка установки VC++ (Код: $($process.ExitCode))" - Write-Host " Попробуйте установить вручную: https://aka.ms/vs/17/release/vc_redist.x64.exe" - } - - Remove-Item $vcFile -Force -ErrorAction SilentlyContinue - } -} - -# --- DOCKER --- - -function Test-Docker { - $status = @{ - Installed = $false - Running = $false - Compose = $false - } - - try { - $ver = docker --version 2>&1 - if ($LASTEXITCODE -eq 0) { $status.Installed = $true } - } - catch {} - - if ($status.Installed) { - try { - $info = docker info 2>&1 - if ($LASTEXITCODE -eq 0) { $status.Running = $true } - } - catch {} - } - - if ($status.Running) { - try { - $comp = docker compose version 2>&1 - if ($LASTEXITCODE -eq 0) { $status.Compose = $true } - } - catch { - # Check legacy - try { - $comp = docker-compose --version 2>&1 - if ($LASTEXITCODE -eq 0) { $status.Compose = $true } - } - catch {} - } - } - - return $status -} - -# --- СЛУЖБЫ И ЗАДАЧИ --- - -function Manage-ScheduledTask { - param( - [string]$Name, - [string]$ExePath, - [string]$Arguments, - [string]$WorkDir, - [string]$Action = "Install" # Install, Uninstall, Start, Stop - ) - - switch ($Action) { - "Install" { - # Удаляем старую - Unregister-ScheduledTask -TaskName $Name -Confirm:$false -ErrorAction SilentlyContinue - - $act = New-ScheduledTaskAction -Execute "$ExePath" -Argument "$Arguments" -WorkingDirectory $WorkDir - $trig = New-ScheduledTaskTrigger -AtStartup - $princ = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest - $sett = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) - - Register-ScheduledTask -TaskName $Name -Action $act -Trigger $trig -Principal $princ -Settings $sett -Force | Out-Null - return $true - } - "Uninstall" { - Unregister-ScheduledTask -TaskName $Name -Confirm:$false -ErrorAction SilentlyContinue - } - "Start" { - Start-ScheduledTask -TaskName $Name -ErrorAction SilentlyContinue - } - "Stop" { - Stop-ScheduledTask -TaskName $Name -ErrorAction SilentlyContinue - # Пытаемся убить процесс по имени exe - if ($ExePath) { - $procName = [System.IO.Path]::GetFileNameWithoutExtension($ExePath) - if ($procName) { - Stop-Process -Name $procName -Force -ErrorAction SilentlyContinue - } - } - } - } -} - -function Get-TaskStatus { - param([string]$Name) - $task = Get-ScheduledTask -TaskName $Name -ErrorAction SilentlyContinue - if ($task) { - # Если задача в статусе Running — возвращаем Running - if ($task.State -eq "Running") { - return "Running" - } - - # Если задача Ready — проверяем, работает ли процесс sing-box - # (scheduled task может быть Ready даже когда процесс работает) - $process = Get-Process -Name "sing-box" -ErrorAction SilentlyContinue - if ($process) { - return "Running" - } - - return $task.State - } - return $null -} - - - -function Ensure-FirewallPort { - param( - [int]$Port, - [string]$Name, - [string]$Protocol = "TCP" - ) - - $rule = Get-NetFirewallRule -DisplayName $Name -ErrorAction SilentlyContinue - if (-not $rule) { - New-NetFirewallRule -DisplayName $Name -Direction Inbound -LocalPort $Port -Protocol $Protocol -Action Allow -Profile Any | Out-Null - return $true - } - return $false -} - -function Get-LocalIPs { - return (Get-NetIPAddress -AddressFamily IPv4 -InterfaceAlias * | Where-Object { $_.IPAddress -notmatch "^127\." -and $_.IPAddress -notmatch "^169\.254\." }).IPAddress -} diff --git a/scripts/setup-discord.ps1 b/scripts/setup-discord.ps1 deleted file mode 100644 index 12bccac..0000000 --- a/scripts/setup-discord.ps1 +++ /dev/null @@ -1,420 +0,0 @@ -# ========================================== -# 🎮 DISCORD PROXY SETUP -# ========================================== - -param( - [switch]$Force, - [switch]$Debug -) - -$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path -. "$ScriptDir\lib\Common.ps1" -. "$ScriptDir\lib\Net.ps1" -. "$ScriptDir\lib\System.ps1" - -if ($Debug) { Set-DebugMode -Enabled $true } - -Write-Header "НАСТРОЙКА DISCORD / VESKTOP" -ClearScreen - -Ensure-Admin - -$InstallPath = "C:\Tools\ProxiFyre" -$ConfigPath = "$InstallPath\app-config.json" -$DriverUrl = "https://github.com/wiresock/ndisapi/releases/download/v3.6.2/Windows.Packet.Filter.3.6.2.1.x64.msi" -$AppUrl = "https://github.com/wiresock/proxifyre/releases/download/v2.1.4/ProxiFyre-v2.1.4-x64-signed.zip" - -# --- ФУНКЦИИ --- - -function Test-ProxyConnection { - param([string]$ProxyAddr) - - Write-Info "Проверка подключения к прокси $ProxyAddr..." - - try { - $parts = $ProxyAddr -split ":" - $host_ = $parts[0] - $port = [int]$parts[1] - - # 1. Проверяем TCP соединение - $tcp = New-Object System.Net.Sockets.TcpClient - $tcp.Connect($host_, $port) - $tcp.Close() - Write-Success "TCP соединение установлено" - - # 2. Пробуем получить внешний IP через прокси (используем curl для SOCKS5) - try { - $result = & curl.exe -s -x "socks5://$ProxyAddr" "http://v4.ident.me" --connect-timeout 5 2>$null - if ($result -match "^\d+\.\d+\.\d+\.\d+$") { - Write-Success "Внешний IP через прокси: $result" - return $true - } - } - catch {} - - Write-Warning "TCP работает, но не удалось получить IP. Возможно прокси не полностью настроен." - return $true - } - catch { - Write-Error "Не удалось подключиться к $ProxyAddr" - Write-Host " Убедитесь, что прокси запущен и доступен." -ForegroundColor Gray - return $false - } -} - -function Get-CurrentConfig { - if (Test-Path $ConfigPath) { - try { - $cfg = Get-Content $ConfigPath -Raw | ConvertFrom-Json - return @{ - Apps = $cfg.proxies[0].appNames -join ", " - Proxy = $cfg.proxies[0].socks5ProxyEndpoint - } - } - catch {} - } - return $null -} - -function Install-ProxiFyre { - # 0. Остановка старых процессов (чтобы файлы не были заблокированы) - Write-Step "Остановка старых процессов..." - Stop-Service "ProxiFyreService" -Force -ErrorAction SilentlyContinue - Stop-Process -Name "ProxiFyre" -Force -ErrorAction SilentlyContinue - Start-Sleep -Seconds 2 - - # Установка драйвера - Write-Step "Установка драйвера..." - $msi = "$env:TEMP\WinpkFilter.msi" - if (Download-File -Url $DriverUrl -Destination $msi) { - Start-Process msiexec.exe -ArgumentList "/i `"$msi`" /qn /norestart" -Wait - Write-Success "Драйвер готов" - } - - # Установка ProxiFyre - Write-Step "Установка ProxiFyre..." - New-Item -ItemType Directory -Path $InstallPath -Force | Out-Null - $zip = "$env:TEMP\ProxiFyre.zip" - if (Download-File -Url $AppUrl -Destination $zip) { - Expand-Archive -Path $zip -DestinationPath $InstallPath -Force - $exe = Get-ChildItem $InstallPath -Recurse -Filter "ProxiFyre.exe" | Select-Object -First 1 - if ($exe.DirectoryName -ne $InstallPath) { - Copy-Item "$($exe.DirectoryName)\*" $InstallPath -Recurse -Force - } - Write-Success "Распаковано" - } - - # Создание правил Firewall - Write-Step "Настройка Windows Firewall..." - $exePath = "$InstallPath\ProxiFyre.exe" - $ruleName = "ProxiFyre" - - # Удаляем старые правила - Remove-NetFirewallRule -DisplayName "$ruleName*" -ErrorAction SilentlyContinue - - # Входящее правило - New-NetFirewallRule -DisplayName "$ruleName (Inbound)" ` - -Direction Inbound ` - -Action Allow ` - -Program $exePath ` - -Profile Domain, Private, Public ` - -Description "Разрешить входящие соединения для ProxiFyre" | Out-Null - - # Исходящее правило - New-NetFirewallRule -DisplayName "$ruleName (Outbound)" ` - -Direction Outbound ` - -Action Allow ` - -Program $exePath ` - -Profile Domain, Private, Public ` - -Description "Разрешить исходящие соединения для ProxiFyre" | Out-Null - - Write-Success "Правила Firewall созданы" -} - -function Configure-And-Start { - param($TargetApps, $ProxyAddr) - - # Конфиг (гарантируем, что appNames - массив) - $appNamesArray = @($TargetApps) - $cfg = @{ - logLevel = "Info" - proxies = @(@{ - appNames = $appNamesArray - socks5ProxyEndpoint = $ProxyAddr - supportedProtocols = @("TCP", "UDP") - }) - excludes = @() - } - $cfg | ConvertTo-Json -Depth 5 | Set-Content $ConfigPath -Encoding UTF8 - - # Служба - Write-Step "Перезапуск службы..." - if (Get-DebugMode) { - & "$InstallPath\ProxiFyre.exe" stop - & "$InstallPath\ProxiFyre.exe" install - & "$InstallPath\ProxiFyre.exe" start - } - else { - & "$InstallPath\ProxiFyre.exe" stop 2>&1 | Out-Null - & "$InstallPath\ProxiFyre.exe" install 2>&1 | Out-Null - & "$InstallPath\ProxiFyre.exe" start 2>&1 | Out-Null - } - - # Мониторинг запуска (10 сек) - Write-Info "Проверка стабильности запуска (10 сек)..." - $lastLogSize = 0 - $logFile = $null - - for ($i = 1; $i -le 10; $i++) { - Start-Sleep -Seconds 1 - - # 1. Ищем файл логов (если еще не нашли) - if (-not $logFile) { - $logFile = Get-ChildItem "$InstallPath\*.log" -ErrorAction SilentlyContinue | Sort-Object LastWriteTime | Select-Object -Last 1 - } - - # 2. Выводим новые строки лога - if ($logFile) { - try { - $stream = [System.IO.File]::Open($logFile.FullName, 'Open', 'Read', 'ReadWrite') - if ($stream.Length -gt $lastLogSize) { - $stream.Seek($lastLogSize, 'Begin') | Out-Null - $reader = New-Object System.IO.StreamReader($stream) - $content = $reader.ReadToEnd() - $newPos = $stream.Position # Сохраняем позицию - $reader.Dispose() # Закрывает поток - $lastLogSize = $newPos - - if (-not [string]::IsNullOrWhiteSpace($content)) { - $content -split "`r`n" | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | ForEach-Object { - Write-Host " LOG: $_" -ForegroundColor DarkGray - } - } - } - else { - $stream.Dispose() - } - } - catch {} - } - - # 3. Проверяем статус службы - $svc = Get-Service -Name "ProxiFyreService" -ErrorAction SilentlyContinue - if ($svc.Status -ne 'Running') { - Write-Error "Служба упала при запуске! (Код 1064 или другая ошибка)" - Write-Host " Попробуйте запустить вручную для диагностики." -ForegroundColor Gray - return - } - } - - Write-Success "Готово! Служба стабильна." -} - -function Get-AppPath { - Write-Host "`n📁 Укажите путь до папки с приложением" -ForegroundColor Yellow - Write-Host " (Будут проксированы все .exe из этой папки)" -ForegroundColor Gray - - # Стандартные пути установки Discord-клиентов - $defaultPaths = @{ - "Discord" = "$env:LOCALAPPDATA\Discord" - "Discord PTB" = "$env:LOCALAPPDATA\DiscordPTB" - "Discord Canary" = "$env:LOCALAPPDATA\DiscordCanary" - "Vesktop" = "$env:LOCALAPPDATA\vesktop" - "Lightcord" = "$env:LOCALAPPDATA\Lightcord" - } - - $suggestions = @() - foreach ($app in $defaultPaths.Keys) { - if (Test-Path $defaultPaths[$app]) { - $suggestions += @{ Name = $app; Path = $defaultPaths[$app] } - } - } - - if ($suggestions.Count -gt 0) { - Write-Host "`n Найденные приложения:" -ForegroundColor Cyan - for ($i = 0; $i -lt $suggestions.Count; $i++) { - Write-Host " [$($i+1)] $($suggestions[$i].Name): $($suggestions[$i].Path)" -ForegroundColor Gray - } - Write-Host " [c] Указать свой путь" -ForegroundColor Gray - - $choice = Read-Host "`n Выберите" - if ($choice -match "^\d+$" -and [int]$choice -ge 1 -and [int]$choice -le $suggestions.Count) { - return @($suggestions[[int]$choice - 1].Path) - } - } - - # Ручной ввод пути - while ($true) { - $path = Read-Host " Путь до папки" - - if ([string]::IsNullOrWhiteSpace($path)) { - Write-Warning "Путь не указан" - continue - } - - if (Test-Path $path) { - $exeCount = (Get-ChildItem $path -Filter "*.exe" -Recurse -ErrorAction SilentlyContinue).Count - if ($exeCount -gt 0) { - Write-Success "Найдено $exeCount исполняемых файлов" - return @($path) - } - else { - Write-Warning "В папке не найдено .exe файлов" - } - } - else { - Write-Error "Папка не существует: $path" - } - - $retry = Read-Host " Попробовать другой путь? (y/n)" - if ($retry -ne 'y') { return $null } - } -} - -function Select-Apps { - Write-Host "`n🎮 Какие приложения проксировать?" -ForegroundColor Yellow - $appOpts = [Ordered]@{ - "1" = "Discord (по имени процесса)" - "2" = "Vesktop (по имени процесса)" - "3" = "Discord + Vesktop (по имени процесса)" - "4" = "Указать путь до папки приложения" - } - $appChoice = Show-Menu -Options $appOpts - - # Discord запускается через Update.exe, нужно перехватывать оба процесса - $result = switch ($appChoice) { - "1" { @("Discord", "Update") } - "2" { @("Vesktop") } - "3" { @("Vesktop", "Discord", "Update") } - "4" { Get-AppPath } - default { @("Discord", "Update") } - } - return $result -} - -function Get-ProxyAddress { - # Проверяем локальный sing-box - $singboxStatus = Get-TaskStatus -Name "SingBoxProxy" - $localProxy = "127.0.0.1:1080" - - if ($singboxStatus -eq "Running") { - Write-Info "Обнаружен работающий VPN клиент (Sing-box)." - Write-Host " Рекомендуется использовать локальный прокси: " -NoNewline -ForegroundColor Gray - Write-Host $localProxy -ForegroundColor Green - - $useLocal = Read-Host " Использовать локальный? (y/n) [y]" - if ($useLocal -ne 'n') { - return $localProxy - } - } - else { - Write-Warning "VPN клиент не запущен!" - Write-Host " Вы можете указать адрес удалённого прокси." -ForegroundColor Gray - } - - # Запрашиваем адрес - while ($true) { - $proxyAddr = Read-Host "`n Введите адрес прокси (IP:порт)" - - if ([string]::IsNullOrWhiteSpace($proxyAddr)) { - Write-Warning "Адрес не указан" - continue - } - - if ($proxyAddr -notmatch "^[\d\.]+:\d+$") { - Write-Error "Неверный формат. Ожидается: IP:порт (например 192.168.1.100:1080)" - continue - } - - # Проверяем подключение - if (Test-ProxyConnection -ProxyAddr $proxyAddr) { - return $proxyAddr - } - - $retry = Read-Host " Попробовать другой адрес? (y/n)" - if ($retry -ne 'y') { return $null } - } -} - -# --- MAIN --- - -$isInstalled = Test-Path "$InstallPath\ProxiFyre.exe" -$discSvc = Get-Service -Name "ProxiFyreService" -ErrorAction SilentlyContinue -$currentConfig = Get-CurrentConfig - -if ($isInstalled -and $currentConfig -and -not $Force) { - # Уже установлено — показываем меню управления - Write-Info "ProxiFyre уже установлен." - Write-Host "" - Write-Host " Статус: " -NoNewline -ForegroundColor Gray - if ($discSvc.Status -eq 'Running') { - Write-Host "АКТИВЕН" -ForegroundColor Green - } - else { - Write-Host "ОСТАНОВЛЕН" -ForegroundColor Yellow - } - Write-Host " Приложения: $($currentConfig.Apps)" -ForegroundColor Gray - Write-Host " Прокси: $($currentConfig.Proxy)" -ForegroundColor Gray - Write-Host "" - - $opts = [Ordered]@{ - "1" = "Изменить настройки (приложения/прокси)" - "2" = "Проверить подключение к прокси" - "3" = "Перезапустить службу" - "4" = "Остановить службу" - "5" = "Переустановить" - "b" = "Назад" - } - - $action = Show-Menu -Options $opts - - switch ($action) { - "1" { - $targetApps = Select-Apps - $proxyAddr = Get-ProxyAddress - if ($proxyAddr) { - Configure-And-Start -TargetApps $targetApps -ProxyAddr $proxyAddr - } - } - "2" { - Test-ProxyConnection -ProxyAddr $currentConfig.Proxy | Out-Null - } - "3" { - Write-Step "Перезапуск службы..." - Start-Process "$InstallPath\ProxiFyre.exe" -ArgumentList "stop" -Wait -NoNewWindow - Start-Process "$InstallPath\ProxiFyre.exe" -ArgumentList "start" -Wait -NoNewWindow - Write-Success "Перезапущено!" - } - "4" { - Start-Process "$InstallPath\ProxiFyre.exe" -ArgumentList "stop" -Wait -NoNewWindow - Write-Success "Остановлено!" - } - "5" { - $Force = $true - } - "b" { exit } - } - - if (-not $Force) { - Start-Sleep -Seconds 2 - exit - } -} - -# --- НОВАЯ УСТАНОВКА --- - -if (-not $isInstalled -or $Force) { - Ensure-VCRedist - Install-ProxiFyre -} - -$targetApps = Select-Apps -$proxyAddr = Get-ProxyAddress - -if (-not $proxyAddr) { - Write-Error "Прокси не настроен. Выход." - Start-Sleep -Seconds 2 - exit -} - -Configure-And-Start -TargetApps $targetApps -ProxyAddr $proxyAddr -Start-Sleep -Seconds 3 diff --git a/scripts/setup-singbox.ps1 b/scripts/setup-singbox.ps1 deleted file mode 100644 index 286bbf7..0000000 --- a/scripts/setup-singbox.ps1 +++ /dev/null @@ -1,417 +0,0 @@ -# ========================================== -# 📦 SING-BOX NATIVE INSTALLER -# ========================================== - -param( - [switch]$Force, - [switch]$Debug, - [string]$SubscriptionUrl = "" -) - -$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path -. "$ScriptDir\lib\Common.ps1" -. "$ScriptDir\lib\Net.ps1" -. "$ScriptDir\lib\System.ps1" - -# --- CONFIG --- -$SingboxVersion = "1.11.4" -$InstallDir = "C:\Tools\sing-box" -$LocalProxyPort = 1080 -$SingboxUrl = "https://github.com/SagerNet/sing-box/releases/download/v$SingboxVersion/sing-box-$SingboxVersion-windows-amd64.zip" -$TaskName = "SingBoxProxy" - -Ensure-Admin - -# --- LOGIC --- - -function Select-Server { - param($Config) - - $outbounds = $Config.outbounds - $servers = @() - - foreach ($outbound in $outbounds) { - if ($outbound.type -in @("vless", "vmess", "trojan", "shadowsocks", "hysteria2")) { - $servers += @{ - tag = $outbound.tag - type = $outbound.type - server = $outbound.server - server_port = $outbound.server_port - outbound = $outbound - } - } - } - - if ($servers.Count -eq 0) { - Write-Error "Серверы не найдены в подписке!" - return $null - } - - $options = [Ordered]@{} - for ($i = 0; $i -lt $servers.Count; $i++) { - $s = $servers[$i] - $options["$($i+1)"] = "$($s.tag) ($($s.server):$($s.server_port))" - } - - $choice = Show-Menu -Title "🌐 Доступные серверы" -Options $options -Prompt "👉 Выберите сервер (номер)" - $index = [int]$choice - 1 - - if ($index -lt 0 -or $index -ge $servers.Count) { - Write-Error "Неверный выбор!" - return $null - } - - return $servers[$index] -} - -function New-SingboxConfig { - param($Outbound, $Port) - - return @{ - log = @{ level = "info"; timestamp = $true; output = "$InstallDir\singbox.log" } - dns = @{ independent_cache = $true } - inbounds = @( - @{ - type = "socks" - tag = "socks-in" - listen = "0.0.0.0" - listen_port = $Port - } - ) - outbounds = @( - $Outbound, - @{ type = "direct"; tag = "direct" } - ) - route = @{ - final = $Outbound.tag - auto_detect_interface = $true - } - } -} - -function Parse-VlessUrl { - param([string]$Url) - - if (-not $Url.StartsWith("vless://")) { throw "URL должен начинаться с vless://" } - - # Remove scheme - $raw = $Url.Substring(8) - - # Split fragment - $tag = "reality" - if ($raw -match "#(.*)$") { - $tag = [System.Web.HttpUtility]::UrlDecode($matches[1]) - $raw = $raw -replace "#.*$", "" - } - - # Split query - $queryStr = "" - if ($raw -match "\?(.*)$") { - $queryStr = $matches[1] - $raw = $raw -replace "\?.*$", "" - } - - # Parse UUID@HOST:PORT - if ($raw -notmatch "([^@]+)@([^:]+):(\d+)") { throw "Неверный формат vless (ожидается uuid@host:port)" } - $uuid = $matches[1][0] - $serverHost = $matches[2][0] - $port = [int]$matches[3][0] # Fix for regex object access in PS - - if (-not $uuid) { - # Fallback if regex returns match info differently in different PS versions - $uuid = $matches[1] - $serverHost = $matches[2] - $port = [int]$matches[3] - } - - - # Parse Query - $params = @{} - if ($queryStr) { - $parts = $queryStr -split "&" - foreach ($p in $parts) { - $kv = $p -split "=" - if ($kv.Count -eq 2) { - $params[[System.Web.HttpUtility]::UrlDecode($kv[0])] = [System.Web.HttpUtility]::UrlDecode($kv[1]) - } - } - } - - # Extract - $pbk = if ($params["pbk"]) { $params["pbk"] } else { throw "Отсутствует параметр pbk (Public Key)" } - $sid = if ($params["sid"]) { $params["sid"] } else { throw "Отсутствует параметр sid (Short ID)" } - $sni = if ($params["sni"]) { $params["sni"] } else { $serverHost } - $fp = if ($params["fp"]) { $params["fp"] } else { "chrome" } - $flow = if ($params["flow"]) { $params["flow"] } else { "" } - - return @{ - uuid = $uuid - server = $serverHost - server_port = $port - tag = $tag - public_key = $pbk - short_id = $sid - server_name = $sni - fingerprint = $fp - flow = $flow - } -} - -# --- MAIN --- - -if ($Debug) { Set-DebugMode -Enabled $true } - -Write-Header "NATIVE SING-BOX (UDP ПОДДЕРЖКА)" -ClearScreen - -$taskStatus = Get-TaskStatus -Name $TaskName - -if ($taskStatus -and -not $Force) { - Write-Info "Sing-box уже установлен." - Write-Host " Статус: $taskStatus" -ForegroundColor ($taskStatus -eq "Running" ? "Green" : "Red") - Write-Host "" - - $opts = [Ordered]@{ - "1" = "Сменить сервер (из подписки)" - "2" = "Ввести новую ссылку на подписку" - "3" = "Перезапустить службу" - "4" = "Остановить службу" - "5" = "Показать конфиг" - "6" = "Переустановить" - "b" = "Назад" - } - - $act = Show-Menu -Options $opts - - switch ($act) { - "1" { - # Reload existing sub logic could be added here, currently just re-runs install flow partially - # Simplification: treat as new setup but try to load saved sub url - $Force = $true - } - "2" { $SubscriptionUrl = ""; $Force = $true } - "3" { Manage-ScheduledTask -Name $TaskName -Action "Start"; Write-Success "Запущено!"; exit } - "4" { Manage-ScheduledTask -Name $TaskName -Action "Stop"; Write-Success "Остановлено!"; exit } - "5" { Get-Content "$InstallDir\config.json"; exit } - "6" { $Force = $true } - "b" { exit } - } -} - -if ($Force -or -not $taskStatus) { - # 1. Загрузка - Write-Step "Установка Sing-box..." - if (!(Test-Path "$InstallDir\sing-box.exe")) { - New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null - $zipCtx = "$env:TEMP\sing-box.zip" - if (Download-File -Url $SingboxUrl -Destination $zipCtx) { - Expand-Archive -Path $zipCtx -DestinationPath $env:TEMP -Force - $extracted = Get-ChildItem "$env:TEMP\sing-box-*" -Directory | Select -First 1 - Copy-Item "$($extracted.FullName)\sing-box.exe" "$InstallDir\sing-box.exe" -Force - Remove-Item $zipCtx; Remove-Item $extracted.FullName -Recurse -Force - Write-Success "Sing-box скачан" - } - else { - Read-Host "Нажмите Enter для выхода..." - exit 1 - } - } - - # 2. Подписка - if ([string]::IsNullOrWhiteSpace($SubscriptionUrl)) { - # Try load saved - $savedSub = "$InstallDir\sub_info.json" - if (Test-Path $savedSub) { - try { - $json = Get-Content $savedSub -Raw | ConvertFrom-Json - if ($json.url) { - Write-Info "Найдена сохраненная подписка: $($json.url)" - if ((Read-Host "Использовать? (y/n)") -eq 'y') { $SubscriptionUrl = $json.url } - } - } - catch {} - } - } - - if ([string]::IsNullOrWhiteSpace($SubscriptionUrl)) { - $SubscriptionUrl = Read-Host "`n🔗 Введите URL подписки (VLESS)" - } - - if ([string]::IsNullOrWhiteSpace($SubscriptionUrl)) { - Write-Error "Url не указан" - Read-Host "Нажмите Enter для выхода..." - exit - } - - # --- PARSING --- - $data = @{ success = $false; config = $null; error = "" } - - if ($SubscriptionUrl.StartsWith("vless://")) { - try { - $p = Parse-VlessUrl -Url $SubscriptionUrl - $outbound = [Ordered]@{ - type = "vless" - tag = $p.tag - server = $p.server - server_port = $p.server_port - uuid = $p.uuid - flow = $p.flow - tls = @{ - enabled = $true - server_name = $p.server_name - utls = @{ enabled = $true; fingerprint = $p.fingerprint } - reality = @{ - enabled = $true - public_key = $p.public_key - short_id = $p.short_id - } - } - packet_encoding = "xudp" - } - $data.success = $true - $data.config = @{ outbounds = @($outbound) } - } - catch { - $data.error = $_.Exception.Message - } - } - else { - $data = Get-SubscriptionData -Url $SubscriptionUrl -Headers (Get-SubscriptionHeaders) - } - - - # --- PARSING LOGIC ENHANCEMENT --- - if (-not $data.success) { - # Fallback: Try to handle non-JSON body (Base64 or Plain Text) - try { - Write-Info "JSON парсинг не удался, пробую как список ссылок..." - $content = $data.rawContent - - # Base64 decode if needed - if ($content -match "^[A-Za-z0-9+/=]+$") { - try { - $bytes = [System.Convert]::FromBase64String($content) - $content = [System.Text.Encoding]::UTF8.GetString($bytes) - } - catch {} - } - - # Try to find vless:// links - $links = $content -split "[\r\n]+" | Where-Object { $_ -match "^vless://" } - - if ($links.Count -gt 0) { - Write-Success "Найдено ссылок: $($links.Count)" - - # Mock a config object with these links as "outbounds" - # Note: We can't fully parsing VLESS query params in pure PS easily without a lot of regex - # So we will try a simpler approach: Let sing-box do it? No, sing-box needs config. - - # WORKAROUND: Create a minimal outbound for each link - # Parsing `vless://UUID@HOST:PORT?security=reality&...#NAME` - $parsedOutbounds = @() - - foreach ($link in $links) { - if ($link -match "vless://([^@]+)@([^:]+):(\d+)(\?.*)?(#.*)?") { - $uuid = $matches[1] - $server = $matches[2] - $port = [int]$matches[3] - $query = $matches[4] - $hash = $matches[5] - - $tag = if ($hash) { $hash.Substring(1) } else { "${server}:${port}" } - $tag = [System.Web.HttpUtility]::UrlDecode($tag) - - # Parse Query Params - $flow = ""; $fp = ""; $pbk = ""; $sid = ""; $sni = ""; $serviceName = "" - - if ($query) { - if ($query -match "flow=([^&]+)") { $flow = $matches[1] } - if ($query -match "fp=([^&]+)") { $fp = $matches[1] } - if ($query -match "pbk=([^&]+)") { $pbk = $matches[1] } - if ($query -match "sid=([^&]+)") { $sid = $matches[1] } - if ($query -match "sni=([^&]+)") { $sni = $matches[1] } - if ($query -match "serviceName=([^&]+)") { $serviceName = $matches[1] } - } - - # Construct Sing-box outbound (REALITY based assumption for modern vless) - $out = [Ordered]@{ - type = "vless" - tag = $tag - server = $server - server_port = $port - uuid = $uuid - flow = $flow - tls = @{ - enabled = $true - server_name = $sni - utls = @{ enabled = $true; fingerprint = $fp } - reality = @{ - enabled = $true - public_key = $pbk - short_id = $sid - } - } - packet_encoding = "xudp" - } - $parsedOutbounds += $out - } - } - - if ($parsedOutbounds.Count -gt 0) { - $data.success = $true - $data.config = @{ outbounds = $parsedOutbounds } - $data.error = $null - } - else { - throw "Не удалось распарсить VLESS ссылки" - } - - } - else { - throw $data.error - } - } - catch { - Write-Error "Ошибка обработки подписки: $_" - Write-Host " Скрипт поддерживает: SIP008 (JSON) или список VLESS+Reality ссылок." -ForegroundColor Yellow - Read-Host "Нажмите Enter для выхода..." - exit - } - } - - - # Save sub info - @{ url = $SubscriptionUrl } | ConvertTo-Json | Set-Content "$InstallDir\sub_info.json" - - # 3. Выбор сервера - $server = Select-Server -Config $data.config - if (!$server) { - Read-Host "Нажмите Enter для выхода..." - exit - } - - # 4. Конфиг - $cfg = New-SingboxConfig -Outbound $server.outbound -Port $LocalProxyPort - $cfg | ConvertTo-Json -Depth 10 | Set-Content "$InstallDir\config.json" -Encoding UTF8 - - # 5. Задача - Manage-ScheduledTask -Name $TaskName -ExePath "$InstallDir\sing-box.exe" -Arguments "run -c `"$InstallDir\config.json`"" -WorkDir $InstallDir -Action "Install" - Manage-ScheduledTask -Name $TaskName -Action "Start" - - # 6. Firewall - if (Ensure-FirewallPort -Port $LocalProxyPort -Name "SingBox-Proxy-Port") { - Write-Success "Правило Firewall создано (порт $LocalProxyPort)" - } - - Write-Success "Успешно установлено и запущено!" - Write-Info "Локальный прокси: 127.0.0.1:$LocalProxyPort" - - $ips = Get-LocalIPs - if ($ips) { - Write-Info "Доступно из сети по адресам:" - foreach ($ip in $ips) { - Write-Host " ${ip}:$LocalProxyPort" -ForegroundColor Gray - } - } - - Start-Sleep -Seconds 3 -} diff --git a/scripts/uninstall-all.ps1 b/scripts/uninstall-all.ps1 deleted file mode 100644 index 72494b3..0000000 --- a/scripts/uninstall-all.ps1 +++ /dev/null @@ -1,58 +0,0 @@ -# ========================================== -# 🗑️ UNINSTALL ALL (CLEANUP) -# ========================================== - -param([switch]$Debug) - -$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path -. "$ScriptDir\lib\Common.ps1" -. "$ScriptDir\lib\System.ps1" - -if ($Debug) { Set-DebugMode -Enabled $true } - -Write-Header "ПОЛНОЕ УДАЛЕНИЕ" -ClearScreen - -Ensure-Admin - -Write-Warning "Это действие удалит весь установленный софт:" -Write-Host " - Sing-box (Служба и файлы)" -ForegroundColor Gray -Write-Host " - ProxiFyre (Служба и файлы)" -ForegroundColor Gray -Write-Host " - Драйвер WinPacketFilter" -ForegroundColor Gray -Write-Host "" - -if ((Read-Host "Вы уверены? (y/n)") -ne 'y') { exit } - -Write-Step "Удаление Sing-box..." -Manage-ScheduledTask -Name "SingBoxProxy" -Action "Stop" -Manage-ScheduledTask -Name "SingBoxProxy" -Action "Uninstall" - -if (Test-Path "C:\Tools\sing-box") { - Remove-Item "C:\Tools\sing-box" -Recurse -Force -ErrorAction SilentlyContinue - Write-Success "Файлы удалены" -} - -Write-Step "Удаление Discord Proxy (ProxiFyre)..." -$pfDir = "C:\Tools\ProxiFyre" -if (Test-Path "$pfDir\ProxiFyre.exe") { - if (Get-DebugMode) { - & "$pfDir\ProxiFyre.exe" uninstall - } - else { - & "$pfDir\ProxiFyre.exe" uninstall 2>&1 | Out-Null - } - Start-Sleep -Seconds 2 - Write-Success "Служба удалена" -} - -if (Test-Path $pfDir) { - Remove-Item $pfDir -Recurse -Force -ErrorAction SilentlyContinue - Write-Success "Файлы удалены" -} - -Write-Step "Удаление драйвера..." -# Тут сложно удалить MSI тихо без GUID, но попробуем через known path или пропустим, т.к. драйвер может быть нужен другим -Write-Info "Драйвер WinPacketFilter оставлен (он может использоваться другим ПО)." -Write-Info "Если нужно, удалите его через 'Установка и удаление программ'." - -Write-Success "Очистка завершена!" -Start-Sleep -Seconds 3 diff --git a/scripts/view-logs.ps1 b/scripts/view-logs.ps1 deleted file mode 100644 index bbde619..0000000 --- a/scripts/view-logs.ps1 +++ /dev/null @@ -1,121 +0,0 @@ -# ========================================== -# 📜 LOG VIEWER -# ========================================== -# View logs from sing-box and ProxiFyre - -param([switch]$Follow) - -$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path -. "$ScriptDir\lib\Common.ps1" - -$SingboxLog = "C:\Tools\sing-box\singbox.log" -$ProxiFyreLog = "C:\Tools\ProxiFyre" - -function Show-LogFile { - param( - [string]$Path, - [string]$Title, - [int]$Lines = 30, - [string]$Color = "Gray" - ) - - if (Test-Path $Path) { - Write-Host "`n═══ $Title ═══" -ForegroundColor Cyan - $content = Get-Content $Path -Tail $Lines -ErrorAction SilentlyContinue - if ($content) { - $content | ForEach-Object { Write-Host " $_" -ForegroundColor $Color } - } - else { - Write-Host " (Лог пустой)" -ForegroundColor DarkGray - } - } - else { - Write-Host "`n═══ $Title ═══" -ForegroundColor Cyan - Write-Host " (Файл не найден: $Path)" -ForegroundColor DarkGray - } -} - -function Tail-Logs { - Write-Host "`n📜 Режим отслеживания логов (Ctrl+C для выхода)" -ForegroundColor Yellow - Write-Host " sing-box: $SingboxLog" -ForegroundColor DarkGray - Write-Host " ProxiFyre: $ProxiFyreLog\*.log" -ForegroundColor DarkGray - Write-Host "" - - $sbPos = 0 - $pfPos = 0 - $pfLogFile = $null - - # Initial positions - if (Test-Path $SingboxLog) { $sbPos = (Get-Item $SingboxLog).Length } - $pfLogFile = Get-ChildItem "$ProxiFyreLog\*.log" -ErrorAction SilentlyContinue | Sort-Object LastWriteTime | Select-Object -Last 1 - if ($pfLogFile) { $pfPos = $pfLogFile.Length } - - try { - while ($true) { - Start-Sleep -Milliseconds 500 - - # Sing-box - if (Test-Path $SingboxLog) { - $newSize = (Get-Item $SingboxLog).Length - if ($newSize -gt $sbPos) { - $content = Get-Content $SingboxLog -Tail 20 -ErrorAction SilentlyContinue - # Show only new lines (approximate) - $content | Select-Object -Last ([math]::Max(1, [math]::Ceiling(($newSize - $sbPos) / 100))) | ForEach-Object { - Write-Host "[SB] $_" -ForegroundColor Green - } - $sbPos = $newSize - } - } - - # ProxiFyre - $pfLogFile = Get-ChildItem "$ProxiFyreLog\*.log" -ErrorAction SilentlyContinue | Sort-Object LastWriteTime | Select-Object -Last 1 - if ($pfLogFile) { - $newSize = $pfLogFile.Length - if ($newSize -gt $pfPos) { - $content = Get-Content $pfLogFile.FullName -Tail 20 -ErrorAction SilentlyContinue - $content | Select-Object -Last ([math]::Max(1, [math]::Ceiling(($newSize - $pfPos) / 100))) | ForEach-Object { - Write-Host "[PF] $_" -ForegroundColor Yellow - } - $pfPos = $newSize - } - } - } - } - catch { - Write-Host "`nОстановлено." -ForegroundColor Gray - } -} - -# --- MAIN --- - -Write-Header "ПРОСМОТР ЛОГОВ" -ClearScreen - -$opts = [Ordered]@{ - "1" = "Показать последние логи" - "2" = "Следить за логами в реальном времени (tail -f)" - "b" = "Назад" -} - -$choice = Show-Menu -Options $opts - -switch ($choice) { - "1" { - Show-LogFile -Path $SingboxLog -Title "SING-BOX (VPN)" -Lines 50 -Color "Green" - - $pfLog = Get-ChildItem "$ProxiFyreLog\*.log" -ErrorAction SilentlyContinue | Sort-Object LastWriteTime | Select-Object -Last 1 - if ($pfLog) { - Show-LogFile -Path $pfLog.FullName -Title "PROXIFYRE (Discord)" -Lines 50 -Color "Yellow" - } - else { - Write-Host "`n═══ PROXIFYRE (Discord) ═══" -ForegroundColor Cyan - Write-Host " (Логов не найдено в $ProxiFyreLog)" -ForegroundColor DarkGray - } - - Write-Host "" - Read-Host "Нажмите Enter для выхода" - } - "2" { - Tail-Logs - } - "b" { exit } -} diff --git a/src/server/config.js b/src/server/config.js new file mode 100644 index 0000000..4288876 --- /dev/null +++ b/src/server/config.js @@ -0,0 +1,21 @@ +import path from 'node:path'; + +const dataDir = process.env.DATA_DIR || path.resolve('.vpn-proxy'); + +export const settings = { + port: Number(process.env.PORT || 3456), + proxyPort: Number(process.env.PROXY_PORT || 8080), + tproxyPort: Number(process.env.TPROXY_PORT || 7895), + bindIp: process.env.PROXY_BIND_IP || '0.0.0.0', + dataDir, + distDir: process.env.DIST_DIR || '/app/dist', + configPath: process.env.SING_BOX_CONFIG || '/etc/sing-box/config.json', + cachePath: process.env.SING_BOX_CACHE || '/var/lib/sing-box/cache.db', + statePath: path.join(dataDir, 'state.json'), + customRulesPath: path.join(dataDir, 'custom-rules.json'), + subscriptionCachePath: path.join(dataDir, 'subscription-cache.json'), + hwidPath: path.join(dataDir, 'hwid'), + routingRuDirect: String(process.env.ROUTING_RU_DIRECT || 'true') !== 'false', + logLevel: process.env.LOG_LEVEL || 'info', + appName: 'VPN Proxy Gateway', +}; diff --git a/src/server/index.js b/src/server/index.js new file mode 100644 index 0000000..daa8c1a --- /dev/null +++ b/src/server/index.js @@ -0,0 +1,274 @@ +import http from 'node:http'; +import fs from 'node:fs'; +import path from 'node:path'; +import { spawn, spawnSync } from 'node:child_process'; +import { settings } from './config.js'; +import { fetchSubscription } from './subscription.js'; +import { buildGatewayConfig, writeSingboxConfig } from './singbox.js'; + +fs.mkdirSync(settings.dataDir, { recursive: true }); + +let singboxProcess = null; +let singboxStartedAt = null; + +function readJson(filePath, fallback) { + try { + if (!fs.existsSync(filePath)) return fallback; + return JSON.parse(fs.readFileSync(filePath, 'utf8')); + } catch { + return fallback; + } +} + +function writeJson(filePath, value) { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, JSON.stringify(value, null, 2), 'utf8'); +} + +function sendJson(res, statusCode, payload) { + const body = JSON.stringify(payload, null, 2); + res.writeHead(statusCode, { + 'content-type': 'application/json; charset=utf-8', + 'content-length': Buffer.byteLength(body), + }); + res.end(body); +} + +function readBody(req) { + return new Promise((resolve, reject) => { + const chunks = []; + req.on('data', (chunk) => chunks.push(chunk)); + req.on('end', () => { + if (!chunks.length) return resolve({}); + try { + resolve(JSON.parse(Buffer.concat(chunks).toString('utf8'))); + } catch { + reject(new Error('Invalid JSON body')); + } + }); + req.on('error', reject); + }); +} + +function checkSingboxConfig() { + const result = spawnSync('sing-box', ['check', '-c', settings.configPath], { + encoding: 'utf8', + }); + + if (result.status !== 0) { + throw new Error((result.stderr || result.stdout || 'sing-box check failed').trim()); + } +} + +function stopSingbox() { + return new Promise((resolve) => { + if (!singboxProcess) return resolve(); + + const current = singboxProcess; + singboxProcess = null; + + const timeout = setTimeout(() => { + current.kill('SIGKILL'); + resolve(); + }, 4000); + + current.once('exit', () => { + clearTimeout(timeout); + resolve(); + }); + + current.kill('SIGTERM'); + }); +} + +async function startSingbox() { + if (!fs.existsSync(settings.configPath)) return false; + + checkSingboxConfig(); + await stopSingbox(); + + singboxProcess = spawn('sing-box', ['run', '-c', settings.configPath], { + stdio: 'inherit', + }); + singboxStartedAt = new Date().toISOString(); + + singboxProcess.once('exit', (code, signal) => { + console.log(`[control] sing-box exited: code=${code} signal=${signal}`); + if (singboxProcess?.exitCode === code) singboxProcess = null; + }); + + return true; +} + +function publicState() { + const state = readJson(settings.statePath, {}); + const customRules = readJson(settings.customRulesPath, []); + return { + mode: 'gateway', + port: settings.port, + proxyPort: settings.proxyPort, + tproxyPort: settings.tproxyPort, + routingRuDirect: settings.routingRuDirect, + configExists: fs.existsSync(settings.configPath), + singboxRunning: Boolean(singboxProcess), + singboxStartedAt, + customRules, + ...state, + }; +} + +function normalizeList(value) { + if (Array.isArray(value)) { + return value.map((item) => String(item || '').trim()).filter(Boolean); + } + return String(value || '') + .split(/\r?\n|,/) + .map((item) => item.trim()) + .filter(Boolean); +} + +function normalizeCustomRules(input) { + const rules = Array.isArray(input) ? input : []; + return rules.map((rule, index) => ({ + id: String(rule.id || `rule-${Date.now()}-${index}`), + name: String(rule.name || `Rule ${index + 1}`).trim(), + enabled: rule.enabled !== false, + outbound: ['direct', 'vpn', 'block'].includes(rule.outbound) ? rule.outbound : 'direct', + domains: normalizeList(rule.domains), + domainSuffixes: normalizeList(rule.domainSuffixes), + domainKeywords: normalizeList(rule.domainKeywords), + ipCidrs: normalizeList(rule.ipCidrs), + ports: normalizeList(rule.ports), + networks: normalizeList(rule.networks).filter((network) => ['tcp', 'udp'].includes(network)), + })); +} + +async function handleApi(req, res) { + if (req.method === 'GET' && req.url === '/api/state') { + return sendJson(res, 200, publicState()); + } + + if (req.method === 'GET' && req.url === '/api/rules') { + return sendJson(res, 200, { + success: true, + rules: readJson(settings.customRulesPath, []), + }); + } + + if (req.method === 'PUT' && req.url === '/api/rules') { + const body = await readBody(req); + const rules = normalizeCustomRules(body.rules); + writeJson(settings.customRulesPath, rules); + return sendJson(res, 200, { success: true, rules }); + } + + if (req.method === 'POST' && req.url === '/api/subscription/fetch') { + const body = await readBody(req); + const url = String(body.url || '').trim(); + if (!url) return sendJson(res, 400, { success: false, error: 'Subscription URL is required' }); + + const parsed = await fetchSubscription(url); + writeJson(settings.subscriptionCachePath, { url, ...parsed }); + + const prevState = readJson(settings.statePath, {}); + writeJson(settings.statePath, { + ...prevState, + subscriptionUrl: url, + servers: parsed.servers, + userInfo: parsed.userInfo, + fetchedAt: parsed.fetchedAt, + }); + + return sendJson(res, 200, { success: true, ...parsed }); + } + + if (req.method === 'POST' && req.url === '/api/apply') { + const body = await readBody(req); + const selectedTag = String(body.selectedTag || '').trim(); + if (!selectedTag) return sendJson(res, 400, { success: false, error: 'selectedTag is required' }); + + const cached = readJson(settings.subscriptionCachePath, null); + if (!cached?.config) { + return sendJson(res, 400, { success: false, error: 'Fetch subscription before applying a server' }); + } + + const customRules = readJson(settings.customRulesPath, []); + const generated = buildGatewayConfig({ ...cached.config, customRules }, selectedTag); + writeSingboxConfig(generated); + await startSingbox(); + + const prevState = readJson(settings.statePath, {}); + writeJson(settings.statePath, { + ...prevState, + selectedTag, + appliedAt: new Date().toISOString(), + }); + + return sendJson(res, 200, { + success: true, + selectedTag, + configPath: settings.configPath, + singboxRunning: Boolean(singboxProcess), + }); + } + + return sendJson(res, 404, { success: false, error: 'Not found' }); +} + +const mime = { + '.html': 'text/html; charset=utf-8', + '.js': 'text/javascript; charset=utf-8', + '.css': 'text/css; charset=utf-8', + '.svg': 'image/svg+xml', + '.json': 'application/json; charset=utf-8', +}; + +function serveStatic(req, res) { + const requestPath = new URL(req.url, `http://localhost:${settings.port}`).pathname; + const cleanPath = requestPath === '/' ? '/index.html' : requestPath; + const filePath = path.resolve(settings.distDir, `.${cleanPath}`); + const distRoot = path.resolve(settings.distDir); + + if (!filePath.startsWith(distRoot)) { + res.writeHead(403); + return res.end('Forbidden'); + } + + const finalPath = fs.existsSync(filePath) && fs.statSync(filePath).isFile() + ? filePath + : path.join(settings.distDir, 'index.html'); + + const ext = path.extname(finalPath); + res.writeHead(200, { 'content-type': mime[ext] || 'application/octet-stream' }); + fs.createReadStream(finalPath).pipe(res); +} + +const server = http.createServer(async (req, res) => { + try { + if (req.url?.startsWith('/api/')) { + return await handleApi(req, res); + } + return serveStatic(req, res); + } catch (error) { + console.error('[control] request failed', error); + return sendJson(res, 500, { success: false, error: error.message || String(error) }); + } +}); + +process.on('SIGTERM', async () => { + await stopSingbox(); + process.exit(0); +}); + +process.on('SIGINT', async () => { + await stopSingbox(); + process.exit(0); +}); + +await startSingbox().catch((error) => { + console.warn(`[control] sing-box was not started: ${error.message}`); +}); + +server.listen(settings.port, '0.0.0.0', () => { + console.log(`[control] gateway UI listening on :${settings.port}`); +}); diff --git a/src/server/singbox.js b/src/server/singbox.js new file mode 100644 index 0000000..37d279c --- /dev/null +++ b/src/server/singbox.js @@ -0,0 +1,175 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { settings } from './config.js'; + +const PROXY_TYPES = new Set(['vless', 'vmess', 'trojan', 'shadowsocks', 'hysteria2']); +const CUSTOM_OUTBOUNDS = new Set(['direct', 'vpn', 'block']); + +function clone(value) { + return JSON.parse(JSON.stringify(value)); +} + +function findOutbound(subscriptionConfig, selectedTag) { + const outbounds = Array.isArray(subscriptionConfig?.outbounds) ? subscriptionConfig.outbounds : []; + return outbounds.find((outbound) => outbound.tag === selectedTag && PROXY_TYPES.has(outbound.type)); +} + +function ruleSets() { + if (!settings.routingRuDirect) return []; + + return [ + { + type: 'remote', + tag: 'geoip-ru', + format: 'binary', + url: 'https://cdn.jsdelivr.net/gh/SagerNet/sing-geoip@rule-set/geoip-ru.srs', + download_detour: 'direct', + }, + { + type: 'remote', + tag: 'geosite-category-ru', + format: 'binary', + url: 'https://cdn.jsdelivr.net/gh/SagerNet/sing-geosite@rule-set/geosite-category-ru.srs', + download_detour: 'direct', + }, + ]; +} + +function uniqueClean(values) { + return Array.from( + new Set( + (Array.isArray(values) ? values : []) + .map((value) => String(value || '').trim()) + .filter(Boolean), + ), + ); +} + +function parsePorts(values) { + return uniqueClean(values) + .map((value) => Number.parseInt(value, 10)) + .filter((value) => Number.isInteger(value) && value > 0 && value <= 65535); +} + +function toSingboxRule(customRule, vpnTag) { + if (!customRule?.enabled) return null; + if (!CUSTOM_OUTBOUNDS.has(customRule.outbound)) return null; + + const rule = {}; + const domains = uniqueClean(customRule.domains); + const domainSuffixes = uniqueClean(customRule.domainSuffixes); + const domainKeywords = uniqueClean(customRule.domainKeywords); + const ipCidrs = uniqueClean(customRule.ipCidrs); + const ports = parsePorts(customRule.ports); + const networks = uniqueClean(customRule.networks).filter((network) => ['tcp', 'udp'].includes(network)); + + if (domains.length) rule.domain = domains; + if (domainSuffixes.length) rule.domain_suffix = domainSuffixes; + if (domainKeywords.length) rule.domain_keyword = domainKeywords; + if (ipCidrs.length) rule.ip_cidr = ipCidrs; + if (ports.length) rule.port = ports; + if (networks.length) rule.network = networks; + + if ( + !rule.domain && + !rule.domain_suffix && + !rule.domain_keyword && + !rule.ip_cidr && + !rule.port && + !rule.network + ) { + return null; + } + + rule.outbound = customRule.outbound === 'vpn' ? vpnTag : customRule.outbound; + return rule; +} + +function customRouteRules(customRules, vpnTag) { + return (Array.isArray(customRules) ? customRules : []) + .map((rule) => toSingboxRule(rule, vpnTag)) + .filter(Boolean); +} + +function routeRules(customRules, vpnTag) { + const rules = [ + { + ip_is_private: true, + outbound: 'direct', + }, + ]; + + rules.push(...customRouteRules(customRules, vpnTag)); + + if (settings.routingRuDirect) { + rules.push({ + rule_set: ['geoip-ru', 'geosite-category-ru'], + outbound: 'direct', + }); + } + + return rules; +} + +export function buildGatewayConfig(subscriptionConfig, selectedTag) { + const selectedOutbound = findOutbound(subscriptionConfig, selectedTag); + if (!selectedOutbound) { + throw new Error(`Selected outbound not found: ${selectedTag}`); + } + + const vpnOutbound = clone(selectedOutbound); + if (!vpnOutbound.tag) vpnOutbound.tag = 'vpn-out'; + if (vpnOutbound.type === 'vless' && !vpnOutbound.packet_encoding) { + vpnOutbound.packet_encoding = 'xudp'; + } + + return { + log: { + level: settings.logLevel, + timestamp: true, + }, + experimental: { + cache_file: { + enabled: true, + path: settings.cachePath, + }, + }, + dns: { + independent_cache: true, + }, + inbounds: [ + { + type: 'tproxy', + tag: 'tproxy-in', + listen: '::', + listen_port: settings.tproxyPort, + sniff: true, + sniff_override_destination: true, + }, + { + type: 'mixed', + tag: 'mixed-in', + listen: settings.bindIp, + listen_port: settings.proxyPort, + sniff: true, + set_system_proxy: false, + }, + ], + outbounds: [ + vpnOutbound, + { type: 'direct', tag: 'direct' }, + { type: 'block', tag: 'block' }, + ], + route: { + rule_set: ruleSets(), + rules: routeRules(subscriptionConfig.customRules, vpnOutbound.tag), + final: vpnOutbound.tag, + auto_detect_interface: true, + }, + }; +} + +export function writeSingboxConfig(config) { + fs.mkdirSync(path.dirname(settings.configPath), { recursive: true }); + fs.writeFileSync(settings.configPath, JSON.stringify(config, null, 2), 'utf8'); +} diff --git a/src/server/subscription.js b/src/server/subscription.js new file mode 100644 index 0000000..9d27053 --- /dev/null +++ b/src/server/subscription.js @@ -0,0 +1,169 @@ +import crypto from 'node:crypto'; +import fs from 'node:fs'; +import { settings } from './config.js'; + +const PROXY_TYPES = new Set(['vless', 'vmess', 'trojan', 'shadowsocks', 'hysteria2']); + +export function getHwid() { + fs.mkdirSync(settings.dataDir, { recursive: true }); + if (fs.existsSync(settings.hwidPath)) { + return fs.readFileSync(settings.hwidPath, 'utf8').trim(); + } + const hwid = crypto.randomBytes(8).toString('hex'); + fs.writeFileSync(settings.hwidPath, hwid, 'utf8'); + return hwid; +} + +export function subscriptionHeaders() { + return { + 'user-agent': 'singbox', + 'x-hwid': getHwid(), + 'x-device-os': process.platform, + 'x-ver-os': process.version, + 'x-device-model': settings.appName, + }; +} + +export function parseUserInfo(headerValue) { + const result = {}; + if (!headerValue) return result; + + for (const part of String(headerValue).split(';')) { + const [key, value] = part.trim().split('=', 2); + if (!key || value === undefined) continue; + const parsed = Number.parseInt(value, 10); + if (!Number.isNaN(parsed)) result[key] = parsed; + } + + return result; +} + +export function parseVlessUrl(rawUrl) { + if (!rawUrl.startsWith('vless://')) { + throw new Error('VLESS URL must start with vless://'); + } + + const parsed = new URL(rawUrl); + const tag = decodeURIComponent(parsed.hash ? parsed.hash.slice(1) : 'vless-out'); + const uuid = decodeURIComponent(parsed.username || ''); + const server = parsed.hostname; + const serverPort = Number.parseInt(parsed.port || '443', 10); + const publicKey = parsed.searchParams.get('pbk') || ''; + const shortId = parsed.searchParams.get('sid') || ''; + const serverName = parsed.searchParams.get('sni') || server; + const fingerprint = parsed.searchParams.get('fp') || 'chrome'; + const flow = parsed.searchParams.get('flow') || ''; + + if (!uuid || !server || !serverPort) { + throw new Error('VLESS URL misses uuid, host or port'); + } + + if (!publicKey || !shortId) { + throw new Error('VLESS REALITY parameters pbk and sid are required'); + } + + return { + type: 'vless', + tag, + server, + server_port: serverPort, + uuid, + flow, + tls: { + enabled: true, + server_name: serverName, + utls: { + enabled: true, + fingerprint, + }, + reality: { + enabled: true, + public_key: publicKey, + short_id: shortId, + }, + }, + packet_encoding: 'xudp', + }; +} + +function maybeDecodeBase64(content) { + const compact = content.trim().replace(/\s+/g, ''); + if (!compact || !/^[A-Za-z0-9+/=]+$/.test(compact)) return content; + + try { + const decoded = Buffer.from(compact, 'base64').toString('utf8'); + if (decoded.includes('vless://') || decoded.includes('{')) return decoded; + } catch {} + + return content; +} + +export function parseSubscriptionBody(body) { + let parsedConfig = null; + + try { + parsedConfig = JSON.parse(body); + } catch { + const decoded = maybeDecodeBase64(body); + const links = decoded + .split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line.startsWith('vless://')); + + if (!links.length) { + throw new Error('Subscription does not contain JSON config or VLESS links'); + } + + parsedConfig = { + outbounds: links.map(parseVlessUrl), + }; + } + + const outbounds = Array.isArray(parsedConfig.outbounds) ? parsedConfig.outbounds : []; + const servers = outbounds + .filter((outbound) => PROXY_TYPES.has(outbound.type)) + .map((outbound) => ({ + tag: outbound.tag || `${outbound.type}-${outbound.server || 'server'}`, + type: outbound.type, + server: outbound.server || 'unknown', + server_port: outbound.server_port || 443, + })); + + if (!servers.length) { + throw new Error('No supported proxy outbounds found in subscription'); + } + + return { config: parsedConfig, servers }; +} + +export async function fetchSubscription(url) { + let parsedUrl; + try { + parsedUrl = new URL(url); + } catch { + throw new Error('Invalid subscription URL'); + } + + if (!['http:', 'https:'].includes(parsedUrl.protocol)) { + throw new Error('Subscription URL must use http or https'); + } + + const response = await fetch(parsedUrl, { + headers: subscriptionHeaders(), + redirect: 'follow', + }); + + if (!response.ok) { + throw new Error(`Subscription request failed: HTTP ${response.status}`); + } + + const body = await response.text(); + const userInfo = parseUserInfo(response.headers.get('subscription-userinfo')); + const parsed = parseSubscriptionBody(body); + + return { + ...parsed, + userInfo, + fetchedAt: new Date().toISOString(), + }; +} diff --git a/src/web/App.jsx b/src/web/App.jsx new file mode 100644 index 0000000..5cb88f4 --- /dev/null +++ b/src/web/App.jsx @@ -0,0 +1,451 @@ +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { createRoot } from 'react-dom/client'; +import './styles.css'; + +function formatBytes(value) { + if (!value) return '0 B'; + const units = ['B', 'KB', 'MB', 'GB', 'TB']; + let size = value; + let index = 0; + while (size >= 1024 && index < units.length - 1) { + size /= 1024; + index += 1; + } + return `${size.toFixed(index === 0 ? 0 : 1)} ${units[index]}`; +} + +function maskUrl(value) { + if (!value) return ''; + try { + const url = new URL(value); + return `${url.hostname}/...`; + } catch { + return value.length > 48 ? `${value.slice(0, 48)}...` : value; + } +} + +function App() { + const [state, setState] = useState(null); + const [subscriptionUrl, setSubscriptionUrl] = useState(''); + const [servers, setServers] = useState([]); + const [customRules, setCustomRules] = useState([]); + const [selectedTag, setSelectedTag] = useState(''); + const [busy, setBusy] = useState(false); + const [log, setLog] = useState([]); + const [error, setError] = useState(''); + const [rulesSaveStatus, setRulesSaveStatus] = useState('saved'); + const rulesDirtyRef = useRef(false); + const rulesSaveTimerRef = useRef(null); + const rulesRevisionRef = useRef(0); + + const userTraffic = useMemo(() => { + const info = state?.userInfo; + if (!info) return 'нет данных'; + const used = formatBytes((info.upload || 0) + (info.download || 0)); + const total = info.total ? formatBytes(info.total) : 'без лимита'; + return `${used} / ${total}`; + }, [state]); + + function addLog(message) { + const time = new Date().toLocaleTimeString('ru-RU', { hour12: false }); + setLog((items) => [{ time, message }, ...items].slice(0, 8)); + } + + async function loadState() { + const response = await fetch('/api/state'); + const data = await response.json(); + setState(data); + setServers(data.servers || []); + if (!rulesDirtyRef.current) { + setCustomRules(data.customRules || []); + } + setSelectedTag(data.selectedTag || ''); + if (data.subscriptionUrl && !subscriptionUrl) setSubscriptionUrl(data.subscriptionUrl); + } + + useEffect(() => { + loadState().catch(() => {}); + const timer = setInterval(() => loadState().catch(() => {}), 5000); + return () => clearInterval(timer); + }, []); + + useEffect(() => { + return () => { + if (rulesSaveTimerRef.current) clearTimeout(rulesSaveTimerRef.current); + }; + }, []); + + async function fetchServers() { + setBusy(true); + setError(''); + addLog(`SYNC ${maskUrl(subscriptionUrl)}`); + + try { + const response = await fetch('/api/subscription/fetch', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ url: subscriptionUrl }), + }); + const data = await response.json(); + if (!response.ok || !data.success) throw new Error(data.error || 'sync failed'); + + setServers(data.servers || []); + setSelectedTag(data.servers?.[0]?.tag || ''); + addLog(`FOUND ${data.servers.length} servers`); + await loadState(); + } catch (err) { + setError(err.message); + addLog(`ERROR ${err.message}`); + } finally { + setBusy(false); + } + } + + async function applyServer() { + setBusy(true); + setError(''); + addLog(`APPLY ${selectedTag}`); + + try { + const response = await fetch('/api/apply', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ selectedTag }), + }); + const data = await response.json(); + if (!response.ok || !data.success) throw new Error(data.error || 'apply failed'); + + addLog(`SING-BOX ${data.singboxRunning ? 'RUNNING' : 'STOPPED'}`); + await loadState(); + } catch (err) { + setError(err.message); + addLog(`ERROR ${err.message}`); + } finally { + setBusy(false); + } + } + + function emptyRule() { + return { + id: `rule-${Date.now()}`, + name: 'Новый список', + enabled: true, + outbound: 'direct', + domains: [], + domainSuffixes: [], + domainKeywords: [], + ipCidrs: [], + ports: [], + networks: [], + }; + } + + function listToText(value) { + return Array.isArray(value) ? value.join('\n') : ''; + } + + function textToList(value) { + return value + .split(/\r?\n|,/) + .map((item) => item.trim()) + .filter(Boolean); + } + + function updateRule(id, patch) { + setCustomRules((rules) => { + const nextRules = rules.map((rule) => (rule.id === id ? { ...rule, ...patch } : rule)); + queueRulesSave(nextRules); + return nextRules; + }); + } + + function queueRulesSave(nextRules) { + rulesDirtyRef.current = true; + const revision = rulesRevisionRef.current + 1; + rulesRevisionRef.current = revision; + setRulesSaveStatus('pending'); + + if (rulesSaveTimerRef.current) clearTimeout(rulesSaveTimerRef.current); + rulesSaveTimerRef.current = setTimeout(() => { + saveRules(nextRules, { silent: true, revision }); + }, 700); + } + + async function saveRules(nextRules = customRules, options = {}) { + const { silent = false, revision = rulesRevisionRef.current + 1 } = options; + if (!silent) setBusy(true); + setError(''); + if (!silent) addLog('SAVE ROUTING RULES'); + setRulesSaveStatus('saving'); + + try { + const response = await fetch('/api/rules', { + method: 'PUT', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ rules: nextRules }), + }); + const data = await response.json(); + if (!response.ok || !data.success) throw new Error(data.error || 'rules save failed'); + + if (rulesRevisionRef.current === revision) { + rulesDirtyRef.current = false; + setCustomRules(data.rules || []); + setRulesSaveStatus('saved'); + addLog(`RULES SAVED ${data.rules.length}`); + await loadState(); + } else { + setRulesSaveStatus('pending'); + } + } catch (err) { + setError(err.message); + setRulesSaveStatus('error'); + addLog(`ERROR ${err.message}`); + } finally { + if (!silent) setBusy(false); + } + } + + function saveRulesNow() { + if (rulesSaveTimerRef.current) clearTimeout(rulesSaveTimerRef.current); + rulesDirtyRef.current = true; + const revision = rulesRevisionRef.current + 1; + rulesRevisionRef.current = revision; + saveRules(customRules, { silent: false, revision }); + } + + function addRule() { + setCustomRules((rules) => { + const nextRules = [emptyRule(), ...rules]; + queueRulesSave(nextRules); + return nextRules; + }); + } + + function removeRule(id) { + setCustomRules((rules) => { + const nextRules = rules.filter((rule) => rule.id !== id); + queueRulesSave(nextRules); + return nextRules; + }); + } + + return ( +
+
+
+

VPN Proxy / Gateway Mode

+

Transparent gateway for the whole network

+

+ Вставь subscription URL, выбери outbound, и контейнер сгенерирует gateway-конфиг для sing-box: TProxy для роутера и mixed proxy для ручных клиентов. +

+
+
+ +
+ {state?.singboxRunning ? 'sing-box running' : 'sing-box standby'} + {state?.selectedTag || 'сервер не выбран'} +
+
+
+ +
+
+
+ 1 +

Subscription

+
+ + + + + +
+ 2 +

Servers

+
+ +
+ {servers.length === 0 &&
Серверы еще не загружены
} + {servers.map((server) => ( + + ))} +
+ + + + {error &&
{error}
} +
+ + +
+ +
+
+
+ 4 +

Routing lists

+
+
+ + +
+
+ +

+ Эти правила автосохраняются после изменений и вставляются после safety private-direct и до стандартного RU-direct. Для игр в gateway-режиме указывай домены, suffix, CIDR или порты: процесс на клиентском ПК gateway не видит. +

+ +
+ {customRules.length === 0 && ( +
+ Нет пользовательских списков. Добавь список, например `League direct`, и отправь его в `direct`. +
+ )} + + {customRules.map((rule) => ( +
+
+ updateRule(rule.id, { name: event.target.value })} + placeholder="Название списка" + /> + +
+ + + +
+