diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9aead67
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,35 @@
+node_modules
+dist
+
+# Local dev database
+docker-compose.dev.yml
+
+# DB dumps
+backups/
+*.dmp
+*.dump
+*.sql.gz
+
+# Env
+.env
+.env.*
+!.env.example
+!.env.test
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Editor/tool configs
+.vscode/
+.claude/
+CLAUDE.md
+.mcp.json
+
+# Dev-server stdout captures
+*-dev.log
+*.log
+
+# One-off / local scripts
+/scripts/
+backend/scripts/
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index 94658f3..0000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "workbench.browser.enableChatTools": true
-}
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2b6638e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,138 @@
+
+

+
+
The NestJS + SvelteKit + PostgreSQL codebase powering Beest, a hackathon in the Netherlands
+
+
+---
+
+# Beest
+
+The Beest codebase is what runs on https://beest.hackclub.com. That website is the You Ship We Ship platform allowing participants to sign in, create and share projects, recieve feedback and earn prizes through the shop.
+
+
+## Architecture
+
+This is a monorepo with two applications:
+
+| Layer | Stack | Role |
+|-------|-------|------|
+| **`backend/`** | NestJS 11, TypeORM, PostgreSQL | Single source of truth - all auth, business logic, and data access |
+| **`frontend/`** | SvelteKit 2, Svelte 5 | Thin proxy - renders UI, sets cookies, forwards requests to the backend |
+
+
+
+## Development Setup
+
+```bash
+git clone https://github.com/hackclub/beest
+cd beest
+
+# Start the database
+docker compose -f docker-compose.dev.yml up -d
+
+# Backend
+cd backend
+npm install
+cp .env.example .env # fill in credentials
+npm run migration:run
+npm run start:dev # runs on :3001
+
+# Frontend (in a second terminal)
+cd frontend
+npm install
+npm run dev # runs on :5173
+```
+
+## Environment Variables
+
+### Backend (`backend/.env`)
+
+```bash
+# Airtable
+AIRTABLE_API_KEY=
+AIRTABLE_BASE_ID=
+AIRTABLE_TABLE_NAME=
+
+# Hack Club Auth OAuth
+CLIENT_ID=
+CLIENT_SECRET=
+REDIRECT_URI=http://localhost:5173/oauth/callback
+
+# JWT & encryption
+JWT_SECRET=
+DB_ENCRYPTION_KEY= # 32-byte hex string for AES-256-GCM
+
+# Hackatime OAuth
+HACKATIME_CLIENT_ID=
+HACKATIME_CLIENT_SECRET=
+HACKATIME_REDIRECT_URI=http://localhost:5173/auth/hackatime/callback
+HACKATIME_BASE_URL=https://hackatime.hackclub.com
+
+# Database
+DATABASE_URL=postgresql://postgres:password@localhost:5432/postgres
+
+# Slack
+SLACK_BOT_TOKEN=
+```
+
+### Frontend (`frontend/.env`)
+
+```bash
+BACKEND_URL=http://localhost:3001
+```
+
+## Deployment
+
+### Docker Compose
+
+```bash
+docker compose up --build
+```
+
+### Dockerfile (standalone)
+
+Both `backend/` and `frontend/` have their own multi-stage Dockerfiles (Node 22 Alpine). Point `DATABASE_URL` at your PostgreSQL instance and set all backend env vars.
+
+| Service | Internal Port |
+|---------|---------------|
+| Frontend | 3000 |
+| Backend | 3001 |
+| PostgreSQL | 5432 |
+
+## API
+
+All endpoints live under the backend at `/api`. Auth-protected routes require a `Bearer` JWT in the `Authorization` header.
+
+| Endpoint | Method | Auth | Purpose |
+|----------|--------|------|---------|
+| `/api/health` | GET | — | Health check |
+| `/api/auth/start` | POST | — | Begin OAuth flow |
+| `/api/auth/handle-callback` | POST | — | Complete OAuth, issue tokens |
+| `/api/auth/refresh` | POST | — | Rotate refresh token |
+| `/api/auth/me` | GET | JWT | Current user claims |
+| `/api/auth/logout` | POST | — | Invalidate session |
+| `/api/auth/rsvp` | POST | JWT | RSVP using authenticated session |
+| `/api/auth/scope` | GET | JWT | Check user permissions |
+| `/api/rsvp` | POST | — | Submit an RSVP |
+| `/api/hackatime/start` | POST | JWT | Begin Hackatime OAuth |
+| `/api/hackatime/callback` | POST | JWT | Complete Hackatime OAuth |
+| `/api/hackatime/projects` | GET | JWT | User's Hackatime project names |
+| `/api/projects` | GET | JWT | List user's projects |
+| `/api/projects` | POST | JWT | Create a project |
+| `/api/projects/:id` | PATCH | JWT | Update a project |
+| `/api/projects/:id` | DELETE | JWT | Delete a project |
+| `/api/projects/hours` | GET | JWT | Hackatime hours breakdown |
+| `/api/leaderboard` | GET | JWT | Top 10 users by approved hours |
+| `/api/onboarding/status` | GET | JWT | Onboarding step completion |
+| `/api/onboarding/two-emails` | POST | JWT | Confirm different Slack email |
+| `/api/onboarding/sticker-link` | GET | JWT | User's unique sticker form link |
+| `/api/audit-log` | GET | JWT | User's audit log entries |
+| `/api/admin/users` | GET | Admin | List all users |
+| `/api/admin/users/:id` | GET | Admin | Get specific user |
+| `/api/admin/users/:id/ban` | POST | Admin | Ban a user |
+| `/api/admin/users/:id/perms` | PATCH | Admin | Update user permissions |
+
+---
+
+Made with <3 by [euan](https://github.com/EDRipper) , give it a ⭐
\ No newline at end of file
diff --git a/backend/.env.example b/backend/.env.example
new file mode 100644
index 0000000..0f027a6
--- /dev/null
+++ b/backend/.env.example
@@ -0,0 +1,41 @@
+AIRTABLE_API_KEY=
+AIRTABLE_BASE_ID=
+AIRTABLE_TABLE_NAME=
+PORT=3001
+
+# Hack Club Auth
+CLIENT_ID=
+CLIENT_SECRET=
+JWT_SECRET=
+REDIRECT_URI=http://localhost:5173/oauth/callback
+
+# Hackatime OAuth
+HACKATIME_CLIENT_ID=
+HACKATIME_CLIENT_SECRET=
+HACKATIME_REDIRECT_URI=http://localhost:5173/auth/hackatime/callback
+HACKATIME_BASE_URL=https://hackatime.hackclub.com
+HACKATIME_ADMIN_API_KEY=
+
+# Database (use container name in prod, public hostname in staging)
+DATABASE_URL=postgresql://postgres:password@localhost:5432/postgres
+# 32-byte hex key for AES-256-GCM column encryption (generate: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")
+DB_ENCRYPTION_KEY=
+
+# Lapse (https://github.com/hackclub/lapse) — program key for server-to-server
+# lookups so reviewers can watch a builder's timelapses inline. Leave blank to
+# disable; the feature degrades silently when unset.
+LAPSE_BASE_URL=https://lapse.hackclub.com
+LAPSE_PROGRAM_KEY=
+
+# HCB card grants (https://hcb.hackclub.com) — confidential OAuth app, scopes "read write".
+# I'm sorry you can't really recreate these unless you work at HQ. Self host hcb?
+HCB_BASE_URL=https://hcb.hackclub.com
+HCB_CLIENT_ID=
+HCB_CLIENT_SECRET=
+HCB_REDIRECT_URI=http://localhost:5173/oauth/hcb/callback
+# Public ID or slug of the single org that issues grants
+HCB_ORG_ID=
+# Suggested grant amount only (NOT a cap): the popup prefills amount as
+# pipes_spent * HCB_CENTS_PER_PIPE, but admins can override to any value.
+# Cents per pipe — 500 = $5 per pipe. Leave blank to prefill nothing.
+HCB_CENTS_PER_PIPE=
diff --git a/backend/.prettierrc b/backend/.prettierrc
new file mode 100644
index 0000000..a20502b
--- /dev/null
+++ b/backend/.prettierrc
@@ -0,0 +1,4 @@
+{
+ "singleQuote": true,
+ "trailingComma": "all"
+}
diff --git a/backend/Dockerfile b/backend/Dockerfile
new file mode 100644
index 0000000..ad7fdcb
--- /dev/null
+++ b/backend/Dockerfile
@@ -0,0 +1,17 @@
+FROM node:22-alpine AS build
+WORKDIR /app
+COPY package*.json ./
+RUN npm ci
+COPY . .
+RUN npm run build
+
+FROM node:22-alpine
+RUN apk add --no-cache curl
+WORKDIR /app
+COPY --from=build /app/dist ./dist
+COPY --from=build /app/package*.json ./
+RUN npm ci --omit=dev
+EXPOSE 3001
+HEALTHCHECK --interval=10s --timeout=3s --start-period=30s --retries=3 \
+ CMD curl -f http://localhost:3001/api/health || exit 1
+CMD ["node", "dist/main"]
diff --git a/backend/README.md b/backend/README.md
new file mode 100644
index 0000000..8f0f65f
--- /dev/null
+++ b/backend/README.md
@@ -0,0 +1,98 @@
+
+
+
+
+[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
+[circleci-url]: https://circleci.com/gh/nestjs/nest
+
+ A progressive Node.js framework for building efficient and scalable server-side applications.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Description
+
+[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
+
+## Project setup
+
+```bash
+$ npm install
+```
+
+## Compile and run the project
+
+```bash
+# development
+$ npm run start
+
+# watch mode
+$ npm run start:dev
+
+# production mode
+$ npm run start:prod
+```
+
+## Run tests
+
+```bash
+# unit tests
+$ npm run test
+
+# e2e tests
+$ npm run test:e2e
+
+# test coverage
+$ npm run test:cov
+```
+
+## Deployment
+
+When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
+
+If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
+
+```bash
+$ npm install -g @nestjs/mau
+$ mau deploy
+```
+
+With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
+
+## Resources
+
+Check out a few resources that may come in handy when working with NestJS:
+
+- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
+- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
+- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
+- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
+- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
+- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
+- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
+- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
+
+## Support
+
+Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
+
+## Stay in touch
+
+- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
+- Website - [https://nestjs.com](https://nestjs.com/)
+- Twitter - [@nestframework](https://twitter.com/nestframework)
+
+## License
+
+Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).
diff --git a/backend/eslint.config.mjs b/backend/eslint.config.mjs
new file mode 100644
index 0000000..4e9f827
--- /dev/null
+++ b/backend/eslint.config.mjs
@@ -0,0 +1,35 @@
+// @ts-check
+import eslint from '@eslint/js';
+import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
+import globals from 'globals';
+import tseslint from 'typescript-eslint';
+
+export default tseslint.config(
+ {
+ ignores: ['eslint.config.mjs'],
+ },
+ eslint.configs.recommended,
+ ...tseslint.configs.recommendedTypeChecked,
+ eslintPluginPrettierRecommended,
+ {
+ languageOptions: {
+ globals: {
+ ...globals.node,
+ ...globals.jest,
+ },
+ sourceType: 'commonjs',
+ parserOptions: {
+ projectService: true,
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+ },
+ {
+ rules: {
+ '@typescript-eslint/no-explicit-any': 'off',
+ '@typescript-eslint/no-floating-promises': 'warn',
+ '@typescript-eslint/no-unsafe-argument': 'warn',
+ "prettier/prettier": ["error", { endOfLine: "auto" }],
+ },
+ },
+);
diff --git a/backend/nest-cli.json b/backend/nest-cli.json
new file mode 100644
index 0000000..f9aa683
--- /dev/null
+++ b/backend/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/backend/package-lock.json b/backend/package-lock.json
new file mode 100644
index 0000000..50e4e1c
--- /dev/null
+++ b/backend/package-lock.json
@@ -0,0 +1,10394 @@
+{
+ "name": "backend",
+ "version": "0.0.1",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "backend",
+ "version": "0.0.1",
+ "license": "UNLICENSED",
+ "dependencies": {
+ "@nestjs/common": "^11.0.1",
+ "@nestjs/config": "^4.0.4",
+ "@nestjs/core": "^11.1.18",
+ "@nestjs/jwt": "^11.0.2",
+ "@nestjs/platform-express": "^11.1.18",
+ "@nestjs/schedule": "^6.1.3",
+ "@nestjs/throttler": "^6.5.0",
+ "@nestjs/typeorm": "^11.0.0",
+ "helmet": "^8.1.0",
+ "jsonwebtoken": "^9.0.3",
+ "pg": "^8.20.0",
+ "reflect-metadata": "^0.2.2",
+ "rxjs": "^7.8.1",
+ "typeorm": "^1.0.0"
+ },
+ "devDependencies": {
+ "@eslint/eslintrc": "^3.2.0",
+ "@eslint/js": "^9.18.0",
+ "@nestjs/cli": "^11.0.0",
+ "@nestjs/schematics": "^11.0.0",
+ "@nestjs/testing": "^11.0.1",
+ "@types/express": "^5.0.0",
+ "@types/jest": "^30.0.0",
+ "@types/jsonwebtoken": "^9.0.10",
+ "@types/node": "^22.10.7",
+ "@types/supertest": "^6.0.2",
+ "eslint": "^9.18.0",
+ "eslint-config-prettier": "^10.0.1",
+ "eslint-plugin-prettier": "^5.2.2",
+ "globals": "^16.0.0",
+ "jest": "^30.0.0",
+ "prettier": "^3.4.2",
+ "source-map-support": "^0.5.21",
+ "supertest": "^7.0.0",
+ "ts-jest": "^29.2.5",
+ "ts-loader": "^9.5.2",
+ "ts-node": "^10.9.2",
+ "tsconfig-paths": "^4.2.0",
+ "typescript": "^5.7.3",
+ "typescript-eslint": "^8.20.0"
+ }
+ },
+ "node_modules/@angular-devkit/core": {
+ "version": "19.2.24",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.24.tgz",
+ "integrity": "sha512-Kd49warf6U/EyWe5BszF/eebN3zQ3bk7tgfEljAw8q/rX95UUtriJubWvp6pgzHfzBA4jwq8f+QiNZB8eBEXPA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "8.18.0",
+ "ajv-formats": "3.0.1",
+ "jsonc-parser": "3.3.1",
+ "picomatch": "4.0.4",
+ "rxjs": "7.8.1",
+ "source-map": "0.7.4"
+ },
+ "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
+ }
+ }
+ },
+ "node_modules/@angular-devkit/core/node_modules/ajv": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
+ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/@angular-devkit/core/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/@angular-devkit/core/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/@angular-devkit/schematics": {
+ "version": "19.2.24",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.24.tgz",
+ "integrity": "sha512-lnw+ZM1Io+cJAkReC0NPDjqObL8NtKzKIkdgEEKC8CUmkhurYhedbicN8Y8NYHgG1uLd2GozW3+/QqPRZaN+Lw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@angular-devkit/core": "19.2.24",
+ "jsonc-parser": "3.3.1",
+ "magic-string": "0.30.17",
+ "ora": "5.4.1",
+ "rxjs": "7.8.1"
+ },
+ "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"
+ }
+ },
+ "node_modules/@angular-devkit/schematics-cli": {
+ "version": "19.2.24",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-19.2.24.tgz",
+ "integrity": "sha512-bsStZQG67J1HBqTmWxtIcobvgrn32L4UOdL7hGyOru5VxDWPNA8pRnDYavT3hnJeBkJYPoQIw8u7Dm0ecoQprw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@angular-devkit/core": "19.2.24",
+ "@angular-devkit/schematics": "19.2.24",
+ "@inquirer/prompts": "7.3.2",
+ "ansi-colors": "4.1.3",
+ "symbol-observable": "4.0.0",
+ "yargs-parser": "21.1.1"
+ },
+ "bin": {
+ "schematics": "bin/schematics.js"
+ },
+ "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"
+ }
+ },
+ "node_modules/@angular-devkit/schematics-cli/node_modules/@inquirer/prompts": {
+ "version": "7.3.2",
+ "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.3.2.tgz",
+ "integrity": "sha512-G1ytyOoHh5BphmEBxSwALin3n1KGNYB6yImbICcRQdzXfOGbuJ9Jske/Of5Sebk339NSGGNfUshnzK8YWkTPsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/checkbox": "^4.1.2",
+ "@inquirer/confirm": "^5.1.6",
+ "@inquirer/editor": "^4.2.7",
+ "@inquirer/expand": "^4.0.9",
+ "@inquirer/input": "^4.1.6",
+ "@inquirer/number": "^3.0.9",
+ "@inquirer/password": "^4.0.9",
+ "@inquirer/rawlist": "^4.0.9",
+ "@inquirer/search": "^3.0.9",
+ "@inquirer/select": "^4.0.9"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@angular-devkit/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/@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/@babel/compat-data": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/core/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "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/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
+ "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
+ "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-async-generators": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-bigint": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
+ "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-properties": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
+ "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.12.13"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-static-block": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
+ "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz",
+ "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz",
+ "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-private-property-in-object": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
+ "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-top-level-await": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
+ "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz",
+ "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@bcoe/v8-coverage": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
+ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/@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/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz",
+ "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.0",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz",
+ "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz",
+ "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz",
+ "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.7",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.5"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz",
+ "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.14.0",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.1",
+ "minimatch": "^3.1.5",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.39.4",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz",
+ "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@inquirer/ansi": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz",
+ "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@inquirer/checkbox": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz",
+ "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/ansi": "^1.0.2",
+ "@inquirer/core": "^10.3.2",
+ "@inquirer/figures": "^1.0.15",
+ "@inquirer/type": "^3.0.10",
+ "yoctocolors-cjs": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/confirm": {
+ "version": "5.1.21",
+ "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz",
+ "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^10.3.2",
+ "@inquirer/type": "^3.0.10"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/core": {
+ "version": "10.3.2",
+ "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz",
+ "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/ansi": "^1.0.2",
+ "@inquirer/figures": "^1.0.15",
+ "@inquirer/type": "^3.0.10",
+ "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"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/editor": {
+ "version": "4.2.23",
+ "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz",
+ "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^10.3.2",
+ "@inquirer/external-editor": "^1.0.3",
+ "@inquirer/type": "^3.0.10"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/expand": {
+ "version": "4.0.23",
+ "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz",
+ "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^10.3.2",
+ "@inquirer/type": "^3.0.10",
+ "yoctocolors-cjs": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/external-editor": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz",
+ "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chardet": "^2.1.1",
+ "iconv-lite": "^0.7.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/figures": {
+ "version": "1.0.15",
+ "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz",
+ "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@inquirer/input": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz",
+ "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^10.3.2",
+ "@inquirer/type": "^3.0.10"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/number": {
+ "version": "3.0.23",
+ "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz",
+ "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^10.3.2",
+ "@inquirer/type": "^3.0.10"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/password": {
+ "version": "4.0.23",
+ "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz",
+ "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/ansi": "^1.0.2",
+ "@inquirer/core": "^10.3.2",
+ "@inquirer/type": "^3.0.10"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/prompts": {
+ "version": "7.10.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz",
+ "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/checkbox": "^4.3.2",
+ "@inquirer/confirm": "^5.1.21",
+ "@inquirer/editor": "^4.2.23",
+ "@inquirer/expand": "^4.0.23",
+ "@inquirer/input": "^4.3.1",
+ "@inquirer/number": "^3.0.23",
+ "@inquirer/password": "^4.0.23",
+ "@inquirer/rawlist": "^4.1.11",
+ "@inquirer/search": "^3.2.2",
+ "@inquirer/select": "^4.4.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/rawlist": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz",
+ "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^10.3.2",
+ "@inquirer/type": "^3.0.10",
+ "yoctocolors-cjs": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/search": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz",
+ "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^10.3.2",
+ "@inquirer/figures": "^1.0.15",
+ "@inquirer/type": "^3.0.10",
+ "yoctocolors-cjs": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/select": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz",
+ "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/ansi": "^1.0.2",
+ "@inquirer/core": "^10.3.2",
+ "@inquirer/figures": "^1.0.15",
+ "@inquirer/type": "^3.0.10",
+ "yoctocolors-cjs": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/type": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz",
+ "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "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/@isaacs/cliui/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/@isaacs/cliui/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/@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/@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/@isaacs/cliui/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/@isaacs/cliui/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/@istanbuljs/load-nyc-config": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
+ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "camelcase": "^5.3.1",
+ "find-up": "^4.1.0",
+ "get-package-type": "^0.1.0",
+ "js-yaml": "^3.13.1",
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/schema": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@jest/console": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz",
+ "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.3.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "jest-message-util": "30.3.0",
+ "jest-util": "30.3.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/core": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz",
+ "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "30.3.0",
+ "@jest/pattern": "30.0.1",
+ "@jest/reporters": "30.3.0",
+ "@jest/test-result": "30.3.0",
+ "@jest/transform": "30.3.0",
+ "@jest/types": "30.3.0",
+ "@types/node": "*",
+ "ansi-escapes": "^4.3.2",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "exit-x": "^0.2.2",
+ "graceful-fs": "^4.2.11",
+ "jest-changed-files": "30.3.0",
+ "jest-config": "30.3.0",
+ "jest-haste-map": "30.3.0",
+ "jest-message-util": "30.3.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.3.0",
+ "jest-resolve-dependencies": "30.3.0",
+ "jest-runner": "30.3.0",
+ "jest-runtime": "30.3.0",
+ "jest-snapshot": "30.3.0",
+ "jest-util": "30.3.0",
+ "jest-validate": "30.3.0",
+ "jest-watcher": "30.3.0",
+ "pretty-format": "30.3.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/diff-sequences": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz",
+ "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/environment": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz",
+ "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/fake-timers": "30.3.0",
+ "@jest/types": "30.3.0",
+ "@types/node": "*",
+ "jest-mock": "30.3.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/expect": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz",
+ "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "expect": "30.3.0",
+ "jest-snapshot": "30.3.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/expect-utils": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz",
+ "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/fake-timers": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz",
+ "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.3.0",
+ "@sinonjs/fake-timers": "^15.0.0",
+ "@types/node": "*",
+ "jest-message-util": "30.3.0",
+ "jest-mock": "30.3.0",
+ "jest-util": "30.3.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/get-type": {
+ "version": "30.1.0",
+ "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz",
+ "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/globals": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz",
+ "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.3.0",
+ "@jest/expect": "30.3.0",
+ "@jest/types": "30.3.0",
+ "jest-mock": "30.3.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/pattern": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz",
+ "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "jest-regex-util": "30.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/reporters": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz",
+ "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@bcoe/v8-coverage": "^0.2.3",
+ "@jest/console": "30.3.0",
+ "@jest/test-result": "30.3.0",
+ "@jest/transform": "30.3.0",
+ "@jest/types": "30.3.0",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "collect-v8-coverage": "^1.0.2",
+ "exit-x": "^0.2.2",
+ "glob": "^10.5.0",
+ "graceful-fs": "^4.2.11",
+ "istanbul-lib-coverage": "^3.0.0",
+ "istanbul-lib-instrument": "^6.0.0",
+ "istanbul-lib-report": "^3.0.0",
+ "istanbul-lib-source-maps": "^5.0.0",
+ "istanbul-reports": "^3.1.3",
+ "jest-message-util": "30.3.0",
+ "jest-util": "30.3.0",
+ "jest-worker": "30.3.0",
+ "slash": "^3.0.0",
+ "string-length": "^4.0.2",
+ "v8-to-istanbul": "^9.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/reporters/node_modules/brace-expansion": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
+ "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@jest/reporters/node_modules/glob": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "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/@jest/reporters/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/@jest/reporters/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/@jest/reporters/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/@jest/schemas": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz",
+ "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.34.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/snapshot-utils": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz",
+ "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.3.0",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "natural-compare": "^1.4.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/source-map": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz",
+ "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "callsites": "^3.1.0",
+ "graceful-fs": "^4.2.11"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/test-result": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz",
+ "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "30.3.0",
+ "@jest/types": "30.3.0",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "collect-v8-coverage": "^1.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/test-sequencer": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz",
+ "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/test-result": "30.3.0",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.3.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/transform": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz",
+ "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@jest/types": "30.3.0",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "babel-plugin-istanbul": "^7.0.1",
+ "chalk": "^4.1.2",
+ "convert-source-map": "^2.0.0",
+ "fast-json-stable-stringify": "^2.1.0",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.3.0",
+ "jest-regex-util": "30.0.1",
+ "jest-util": "30.3.0",
+ "pirates": "^4.0.7",
+ "slash": "^3.0.0",
+ "write-file-atomic": "^5.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/types": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz",
+ "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/pattern": "30.0.1",
+ "@jest/schemas": "30.0.5",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "@types/istanbul-reports": "^3.0.4",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.33",
+ "chalk": "^4.1.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "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/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "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==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "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/@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==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "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/@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/@napi-rs/wasm-runtime": {
+ "version": "0.2.12",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
+ "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.4.3",
+ "@emnapi/runtime": "^1.4.3",
+ "@tybys/wasm-util": "^0.10.0"
+ }
+ },
+ "node_modules/@nestjs/cli": {
+ "version": "11.0.21",
+ "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.21.tgz",
+ "integrity": "sha512-F8mV0Sj/zVEouzR3NxBuJy08YHTUOmC5Xdcx3qIIaJWzrm8Vw86CHkhkaPBJ5ewRMHPDCShPmhsfwhpCcjts3A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@angular-devkit/core": "19.2.24",
+ "@angular-devkit/schematics": "19.2.24",
+ "@angular-devkit/schematics-cli": "19.2.24",
+ "@inquirer/prompts": "7.10.1",
+ "@nestjs/schematics": "^11.0.1",
+ "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",
+ "glob": "13.0.6",
+ "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.106.0",
+ "webpack-node-externals": "3.0.0"
+ },
+ "bin": {
+ "nest": "bin/nest.js"
+ },
+ "engines": {
+ "node": ">= 20.11"
+ },
+ "peerDependencies": {
+ "@swc/cli": "^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 || ^0.8.0",
+ "@swc/core": "^1.3.62"
+ },
+ "peerDependenciesMeta": {
+ "@swc/cli": {
+ "optional": true
+ },
+ "@swc/core": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@nestjs/common": {
+ "version": "11.1.17",
+ "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.17.tgz",
+ "integrity": "sha512-hLODw5Abp8OQgA+mUO4tHou4krKgDtUcM9j5Ihxncst9XeyxYBTt2bwZm4e4EQr5E352S4Fyy6V3iFx9ggxKAg==",
+ "license": "MIT",
+ "dependencies": {
+ "file-type": "21.3.2",
+ "iterare": "1.2.1",
+ "load-esm": "1.0.3",
+ "tslib": "2.8.1",
+ "uid": "2.0.2"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/nest"
+ },
+ "peerDependencies": {
+ "class-transformer": ">=0.4.1",
+ "class-validator": ">=0.13.2",
+ "reflect-metadata": "^0.1.12 || ^0.2.0",
+ "rxjs": "^7.1.0"
+ },
+ "peerDependenciesMeta": {
+ "class-transformer": {
+ "optional": true
+ },
+ "class-validator": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@nestjs/config": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.4.tgz",
+ "integrity": "sha512-CJPjNitr0bAufSEnRe2N+JbnVmMmDoo6hvKCPzXgZoGwJSmp/dZPk9f/RMbuD/+Q1ZJPjwsRpq0vxna++Knwow==",
+ "license": "MIT",
+ "dependencies": {
+ "dotenv": "17.4.1",
+ "dotenv-expand": "12.0.3",
+ "lodash": "4.18.1"
+ },
+ "peerDependencies": {
+ "@nestjs/common": "^10.0.0 || ^11.0.0",
+ "rxjs": "^7.1.0"
+ }
+ },
+ "node_modules/@nestjs/core": {
+ "version": "11.1.18",
+ "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.18.tgz",
+ "integrity": "sha512-wR3DtGyk/LUAiPtbXDuWJJwVkWElKBY0sqnTzf9d4uM3+X18FRZhK7WFc47czsIGOdWuRsMeLYV+1Z9dO4zDEQ==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nuxt/opencollective": "0.4.1",
+ "fast-safe-stringify": "2.1.1",
+ "iterare": "1.2.1",
+ "path-to-regexp": "8.4.2",
+ "tslib": "2.8.1",
+ "uid": "2.0.2"
+ },
+ "engines": {
+ "node": ">= 20"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/nest"
+ },
+ "peerDependencies": {
+ "@nestjs/common": "^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
+ }
+ }
+ },
+ "node_modules/@nestjs/jwt": {
+ "version": "11.0.2",
+ "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.2.tgz",
+ "integrity": "sha512-rK8aE/3/Ma45gAWfCksAXUNbOoSOUudU0Kn3rT39htPF7wsYXtKfjALKeKKJbFrIWbLjsbqfXX5bIJNvgBugGA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/jsonwebtoken": "9.0.10",
+ "jsonwebtoken": "9.0.3"
+ },
+ "peerDependencies": {
+ "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0"
+ }
+ },
+ "node_modules/@nestjs/platform-express": {
+ "version": "11.1.18",
+ "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.18.tgz",
+ "integrity": "sha512-s6GdHMTa3qx0fJewR74Xa30ysPHfBEqxIwZ7BGSTLoAEQ1vTP24urNl+b6+s49NFLEIOyeNho5fN/9/I17QlOw==",
+ "license": "MIT",
+ "dependencies": {
+ "cors": "2.8.6",
+ "express": "5.2.1",
+ "multer": "2.1.1",
+ "path-to-regexp": "8.4.2",
+ "tslib": "2.8.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/nest"
+ },
+ "peerDependencies": {
+ "@nestjs/common": "^11.0.0",
+ "@nestjs/core": "^11.0.0"
+ }
+ },
+ "node_modules/@nestjs/schedule": {
+ "version": "6.1.3",
+ "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-6.1.3.tgz",
+ "integrity": "sha512-RflMFOpR16Dwd1jAUbeB4mfGTCh65fvEdL4mSjQPJChpkRGRjIXjb+6YQcK2faQrVT60c9DmLmoVR7/ONCtuYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "cron": "4.4.0"
+ },
+ "peerDependencies": {
+ "@nestjs/common": "^10.0.0 || ^11.0.0",
+ "@nestjs/core": "^10.0.0 || ^11.0.0"
+ }
+ },
+ "node_modules/@nestjs/schematics": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.1.0.tgz",
+ "integrity": "sha512-lVxGZ46tcdItFMoXr6vyKWlnOsm1SZm/GUqAEDvy2RL4Q4O+3bkziAhrO7Y8JLssFUUvNFEGqAizI52WAxhjDw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@angular-devkit/core": "19.2.24",
+ "@angular-devkit/schematics": "19.2.24",
+ "comment-json": "5.0.0",
+ "jsonc-parser": "3.3.1",
+ "pluralize": "8.0.0"
+ },
+ "peerDependencies": {
+ "prettier": "^3.0.0",
+ "typescript": ">=4.8.2"
+ },
+ "peerDependenciesMeta": {
+ "prettier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@nestjs/testing": {
+ "version": "11.1.17",
+ "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.17.tgz",
+ "integrity": "sha512-lNffw+z+2USewmw4W0tsK+Rq94A2N4PiHbcqoRUu5y8fnqxQeIWGHhjo5BFCqj7eivqJBhT7WdRydxVq4rAHzg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "2.8.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/nest"
+ },
+ "peerDependencies": {
+ "@nestjs/common": "^11.0.0",
+ "@nestjs/core": "^11.0.0",
+ "@nestjs/microservices": "^11.0.0",
+ "@nestjs/platform-express": "^11.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@nestjs/microservices": {
+ "optional": true
+ },
+ "@nestjs/platform-express": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@nestjs/throttler": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/@nestjs/throttler/-/throttler-6.5.0.tgz",
+ "integrity": "sha512-9j0ZRfH0QE1qyrj9JjIRDz5gQLPqq9yVC2nHsrosDVAfI5HHw08/aUAWx9DZLSdQf4HDkmhTTEGLrRFHENvchQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
+ "@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
+ "reflect-metadata": "^0.1.13 || ^0.2.0"
+ }
+ },
+ "node_modules/@nestjs/typeorm": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz",
+ "integrity": "sha512-SOeUQl70Lb2OfhGkvnh4KXWlsd+zA08RuuQgT7kKbzivngxzSo1Oc7Usu5VxCxACQC9wc2l9esOHILSJeK7rJA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@nestjs/common": "^10.0.0 || ^11.0.0",
+ "@nestjs/core": "^10.0.0 || ^11.0.0",
+ "reflect-metadata": "^0.1.13 || ^0.2.0",
+ "rxjs": "^7.2.0",
+ "typeorm": "^0.3.0"
+ }
+ },
+ "node_modules/@noble/hashes": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
+ "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@nuxt/opencollective": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@nuxt/opencollective/-/opencollective-0.4.1.tgz",
+ "integrity": "sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "consola": "^3.2.3"
+ },
+ "bin": {
+ "opencollective": "bin/opencollective.js"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.10.0",
+ "npm": ">=5.10.0"
+ }
+ },
+ "node_modules/@paralleldrive/cuid2": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz",
+ "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@noble/hashes": "^1.1.5"
+ }
+ },
+ "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/@pkgr/core": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
+ "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/pkgr"
+ }
+ },
+ "node_modules/@sinclair/typebox": {
+ "version": "0.34.48",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz",
+ "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sinonjs/commons": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
+ "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/fake-timers": {
+ "version": "15.1.1",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.1.tgz",
+ "integrity": "sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.1"
+ }
+ },
+ "node_modules/@sqltools/formatter": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz",
+ "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==",
+ "license": "MIT"
+ },
+ "node_modules/@tokenizer/inflate": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz",
+ "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "token-types": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
+ }
+ },
+ "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/@tsconfig/node10": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
+ "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node12": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+ "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node14": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+ "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node16": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
+ "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/body-parser": {
+ "version": "1.19.6",
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
+ "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/connect": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/connect": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+ "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/cookiejar": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz",
+ "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/eslint": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
+ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "*",
+ "@types/json-schema": "*"
+ }
+ },
+ "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/@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/@types/express": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
+ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^5.0.0",
+ "@types/serve-static": "^2"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz",
+ "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==",
+ "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/istanbul-lib-coverage": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/istanbul-lib-report": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
+ "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/istanbul-lib-coverage": "*"
+ }
+ },
+ "node_modules/@types/istanbul-reports": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
+ "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "node_modules/@types/jest": {
+ "version": "30.0.0",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz",
+ "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "expect": "^30.0.0",
+ "pretty-format": "^30.0.0"
+ }
+ },
+ "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/@types/jsonwebtoken": {
+ "version": "9.0.10",
+ "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz",
+ "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/luxon": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.1.tgz",
+ "integrity": "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/methods": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
+ "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "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==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "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": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/stack-utils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
+ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/superagent": {
+ "version": "8.1.9",
+ "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz",
+ "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/cookiejar": "^2.1.5",
+ "@types/methods": "^1.1.4",
+ "@types/node": "*",
+ "form-data": "^4.0.0"
+ }
+ },
+ "node_modules/@types/supertest": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz",
+ "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/methods": "^1.1.4",
+ "@types/superagent": "^8.1.0"
+ }
+ },
+ "node_modules/@types/yargs": {
+ "version": "17.0.35",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
+ "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.3",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz",
+ "integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.12.2",
+ "@typescript-eslint/scope-manager": "8.57.2",
+ "@typescript-eslint/type-utils": "8.57.2",
+ "@typescript-eslint/utils": "8.57.2",
+ "@typescript-eslint/visitor-keys": "8.57.2",
+ "ignore": "^7.0.5",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.57.2",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.2.tgz",
+ "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.57.2",
+ "@typescript-eslint/types": "8.57.2",
+ "@typescript-eslint/typescript-estree": "8.57.2",
+ "@typescript-eslint/visitor-keys": "8.57.2",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz",
+ "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.57.2",
+ "@typescript-eslint/types": "^8.57.2",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz",
+ "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.57.2",
+ "@typescript-eslint/visitor-keys": "8.57.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz",
+ "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz",
+ "integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.57.2",
+ "@typescript-eslint/typescript-estree": "8.57.2",
+ "@typescript-eslint/utils": "8.57.2",
+ "debug": "^4.4.3",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz",
+ "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz",
+ "integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.57.2",
+ "@typescript-eslint/tsconfig-utils": "8.57.2",
+ "@typescript-eslint/types": "8.57.2",
+ "@typescript-eslint/visitor-keys": "8.57.2",
+ "debug": "^4.4.3",
+ "minimatch": "^10.2.2",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "10.2.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
+ "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz",
+ "integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.57.2",
+ "@typescript-eslint/types": "8.57.2",
+ "@typescript-eslint/typescript-estree": "8.57.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz",
+ "integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.57.2",
+ "eslint-visitor-keys": "^5.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@unrs/resolver-binding-android-arm-eabi": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz",
+ "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-android-arm64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz",
+ "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-arm64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz",
+ "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-x64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz",
+ "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-freebsd-x64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz",
+ "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz",
+ "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz",
+ "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz",
+ "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz",
+ "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz",
+ "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz",
+ "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz",
+ "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz",
+ "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz",
+ "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz",
+ "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-wasm32-wasi": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz",
+ "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^0.2.11"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz",
+ "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz",
+ "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-x64-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz",
+ "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "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==",
+ "devOptional": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-import-phases": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
+ "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "peerDependencies": {
+ "acorn": "^8.14.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.3.5",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz",
+ "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "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",
+ "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/ajv-formats": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
+ "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ajv-formats/node_modules/ajv": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats/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/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/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/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/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/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/ansis": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz",
+ "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "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/anymatch/node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "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/arg": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "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/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/asap": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/babel-jest": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz",
+ "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/transform": "30.3.0",
+ "@types/babel__core": "^7.20.5",
+ "babel-plugin-istanbul": "^7.0.1",
+ "babel-preset-jest": "30.3.0",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.11.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/babel-plugin-istanbul": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz",
+ "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "workspaces": [
+ "test/babel-8"
+ ],
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@istanbuljs/load-nyc-config": "^1.0.0",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-instrument": "^6.0.2",
+ "test-exclude": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/babel-plugin-jest-hoist": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz",
+ "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/babel__core": "^7.20.5"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/babel-preset-current-node-syntax": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz",
+ "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/plugin-syntax-async-generators": "^7.8.4",
+ "@babel/plugin-syntax-bigint": "^7.8.3",
+ "@babel/plugin-syntax-class-properties": "^7.12.13",
+ "@babel/plugin-syntax-class-static-block": "^7.14.5",
+ "@babel/plugin-syntax-import-attributes": "^7.24.7",
+ "@babel/plugin-syntax-import-meta": "^7.10.4",
+ "@babel/plugin-syntax-json-strings": "^7.8.3",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
+ "@babel/plugin-syntax-top-level-await": "^7.14.5"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/babel-preset-jest": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz",
+ "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "babel-plugin-jest-hoist": "30.3.0",
+ "babel-preset-current-node-syntax": "^1.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.11.0 || ^8.0.0-beta.1"
+ }
+ },
+ "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/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/baseline-browser-mapping": {
+ "version": "2.10.10",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz",
+ "integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "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/body-parser": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
+ "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.7.0",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.1",
+ "raw-body": "^3.0.1",
+ "type-is": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
+ "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "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/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",
+ "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/bs-logger": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
+ "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-json-stable-stringify": "2.x"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/bser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
+ "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "node-int64": "^0.4.0"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+ "license": "BSD-3-Clause"
+ },
+ "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/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/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/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/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/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/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001781",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz",
+ "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==",
+ "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/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/char-regex": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
+ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/chardet": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz",
+ "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "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/ci-info": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
+ "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cjs-module-lexer": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz",
+ "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/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/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/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/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/cliui/node_modules/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/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/co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">= 1.0.0",
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/collect-v8-coverage": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz",
+ "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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/comment-json": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-5.0.0.tgz",
+ "integrity": "sha512-uiqLcOiVDJtBP8WGkZHEP+FZIhTzP1dxvn59EfoYUi9gqupjrBWVQkO2atDrbnKPwLeotFYDsuNb26uBMqB+hw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-timsort": "^1.0.3",
+ "esprima": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/component-emitter": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
+ "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/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/consola": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
+ "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.18.0 || >=16.10.0"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
+ "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/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/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cookiejar": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
+ "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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/create-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/cron": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/cron/-/cron-4.4.0.tgz",
+ "integrity": "sha512-fkdfq+b+AHI4cKdhZlppHveI/mgz2qpiYxcm+t5E5TsxX7QrLS1VE0+7GENEk9z0EeGPcpSciGv6ez24duWhwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/luxon": "~3.7.0",
+ "luxon": "~3.7.0"
+ },
+ "engines": {
+ "node": ">=18.x"
+ },
+ "funding": {
+ "type": "ko-fi",
+ "url": "https://ko-fi.com/intcreator"
+ }
+ },
+ "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/dayjs": {
+ "version": "1.11.20",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz",
+ "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==",
+ "license": "MIT"
+ },
+ "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/dedent": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz",
+ "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "babel-plugin-macros": "^3.1.0"
+ },
+ "peerDependenciesMeta": {
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/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/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "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/detect-newline": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
+ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dezalgo": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
+ "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "asap": "^2.0.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/diff": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
+ "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
+ "devOptional": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "17.4.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.1.tgz",
+ "integrity": "sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dotenv-expand": {
+ "version": "12.0.3",
+ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz",
+ "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dotenv": "^16.4.5"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dotenv-expand/node_modules/dotenv": {
+ "version": "16.6.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "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/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/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "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/electron-to-chromium": {
+ "version": "1.5.325",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.325.tgz",
+ "integrity": "sha512-PwfIw7WQSt3xX7yOf5OE/unLzsK9CaN2f/FvV3WjPR1Knoc1T9vePRVV4W1EM301JzzysK51K7FNKcusCr0zYA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/emittery": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
+ "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/emittery?sponsor=1"
+ }
+ },
+ "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/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/enhanced-resolve": {
+ "version": "5.20.1",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz",
+ "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "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/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/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/es-module-lexer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
+ "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.39.4",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz",
+ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.2",
+ "@eslint/config-helpers": "^0.4.2",
+ "@eslint/core": "^0.17.0",
+ "@eslint/eslintrc": "^3.3.5",
+ "@eslint/js": "9.39.4",
+ "@eslint/plugin-kit": "^0.4.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.14.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.5",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-config-prettier": {
+ "version": "10.1.8",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
+ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-config-prettier"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-prettier": {
+ "version": "5.5.5",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz",
+ "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prettier-linter-helpers": "^1.0.1",
+ "synckit": "^0.11.12"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-plugin-prettier"
+ },
+ "peerDependencies": {
+ "@types/eslint": ">=8.0.0",
+ "eslint": ">=8.0.0",
+ "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
+ "prettier": ">=3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/eslint": {
+ "optional": true
+ },
+ "eslint-config-prettier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "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/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "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/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/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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/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/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/execa/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/exit-x": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz",
+ "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/expect": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz",
+ "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/expect-utils": "30.3.0",
+ "@jest/get-type": "30.1.0",
+ "jest-matcher-utils": "30.3.0",
+ "jest-message-util": "30.3.0",
+ "jest-mock": "30.3.0",
+ "jest-util": "30.3.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/express": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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/fast-diff": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "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/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/fast-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
+ "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/fb-watchman": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
+ "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bser": "2.1.1"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/file-type": {
+ "version": "21.3.2",
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.2.tgz",
+ "integrity": "sha512-DLkUvGwep3poOV2wpzbHCOnSKGk1LzyXTv+aHFgN2VFl96wnp8YA9YjO2qPzg5PuL8q/SW9Pdi6WTkYOIh995w==",
+ "license": "MIT",
+ "dependencies": {
+ "@tokenizer/inflate": "^0.4.1",
+ "strtok3": "^10.3.4",
+ "token-types": "^6.1.1",
+ "uint8array-extras": "^1.4.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/file-type?sponsor=1"
+ }
+ },
+ "node_modules/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/finalhandler": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "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/fork-ts-checker-webpack-plugin": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.1.0.tgz",
+ "integrity": "sha512-mpafl89VFPJmhnJ1ssH+8wmM2b50n+Rew5x42NeI2U78aRWgtkEtGmctp7iT16UjquJTjorEmIfESj3DxdW84Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.16.7",
+ "chalk": "^4.1.2",
+ "chokidar": "^4.0.1",
+ "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": ">=14.21.3"
+ },
+ "peerDependencies": {
+ "typescript": ">3.6.0",
+ "webpack": "^5.11.0"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/form-data/node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/form-data/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/formidable": {
+ "version": "3.5.4",
+ "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz",
+ "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@paralleldrive/cuid2": "^2.2.2",
+ "dezalgo": "^1.0.4",
+ "once": "^1.4.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "url": "https://ko-fi.com/tunnckoCore/commissions"
+ }
+ },
+ "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/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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/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/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "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/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/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-east-asian-width": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
+ "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/get-package-type": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
+ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "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/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob": {
+ "version": "13.0.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
+ "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "minimatch": "^10.2.2",
+ "minipass": "^7.1.3",
+ "path-scurry": "^2.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "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/glob/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/globals": {
+ "version": "16.5.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
+ "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/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/handlebars": {
+ "version": "4.7.9",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz",
+ "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.5",
+ "neo-async": "^2.6.2",
+ "source-map": "^0.6.1",
+ "wordwrap": "^1.0.0"
+ },
+ "bin": {
+ "handlebars": "bin/handlebars"
+ },
+ "engines": {
+ "node": ">=0.4.7"
+ },
+ "optionalDependencies": {
+ "uglify-js": "^3.1.4"
+ }
+ },
+ "node_modules/handlebars/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/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/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/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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/helmet": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz",
+ "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/http-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/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "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/import-local": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
+ "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pkg-dir": "^4.2.0",
+ "resolve-cwd": "^3.0.0"
+ },
+ "bin": {
+ "import-local-fixture": "fixtures/cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "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/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/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/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/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-generator-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
+ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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/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/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/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/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/istanbul-lib-coverage": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/istanbul-lib-instrument": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
+ "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/core": "^7.23.9",
+ "@babel/parser": "^7.23.9",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-report": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^4.0.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-source-maps": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
+ "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.23",
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-reports": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
+ "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/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/jest": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz",
+ "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/core": "30.3.0",
+ "@jest/types": "30.3.0",
+ "import-local": "^3.2.0",
+ "jest-cli": "30.3.0"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-changed-files": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz",
+ "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "execa": "^5.1.1",
+ "jest-util": "30.3.0",
+ "p-limit": "^3.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-circus": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz",
+ "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.3.0",
+ "@jest/expect": "30.3.0",
+ "@jest/test-result": "30.3.0",
+ "@jest/types": "30.3.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "co": "^4.6.0",
+ "dedent": "^1.6.0",
+ "is-generator-fn": "^2.1.0",
+ "jest-each": "30.3.0",
+ "jest-matcher-utils": "30.3.0",
+ "jest-message-util": "30.3.0",
+ "jest-runtime": "30.3.0",
+ "jest-snapshot": "30.3.0",
+ "jest-util": "30.3.0",
+ "p-limit": "^3.1.0",
+ "pretty-format": "30.3.0",
+ "pure-rand": "^7.0.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.6"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-cli": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz",
+ "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/core": "30.3.0",
+ "@jest/test-result": "30.3.0",
+ "@jest/types": "30.3.0",
+ "chalk": "^4.1.2",
+ "exit-x": "^0.2.2",
+ "import-local": "^3.2.0",
+ "jest-config": "30.3.0",
+ "jest-util": "30.3.0",
+ "jest-validate": "30.3.0",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-config": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz",
+ "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@jest/get-type": "30.1.0",
+ "@jest/pattern": "30.0.1",
+ "@jest/test-sequencer": "30.3.0",
+ "@jest/types": "30.3.0",
+ "babel-jest": "30.3.0",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "deepmerge": "^4.3.1",
+ "glob": "^10.5.0",
+ "graceful-fs": "^4.2.11",
+ "jest-circus": "30.3.0",
+ "jest-docblock": "30.2.0",
+ "jest-environment-node": "30.3.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.3.0",
+ "jest-runner": "30.3.0",
+ "jest-util": "30.3.0",
+ "jest-validate": "30.3.0",
+ "parse-json": "^5.2.0",
+ "pretty-format": "30.3.0",
+ "slash": "^3.0.0",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@types/node": "*",
+ "esbuild-register": ">=3.4.0",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "esbuild-register": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-config/node_modules/brace-expansion": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
+ "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/jest-config/node_modules/glob": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "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/jest-config/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/jest-config/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/jest-config/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/jest-diff": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz",
+ "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/diff-sequences": "30.3.0",
+ "@jest/get-type": "30.1.0",
+ "chalk": "^4.1.2",
+ "pretty-format": "30.3.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-docblock": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz",
+ "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-newline": "^3.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-each": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz",
+ "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "@jest/types": "30.3.0",
+ "chalk": "^4.1.2",
+ "jest-util": "30.3.0",
+ "pretty-format": "30.3.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-environment-node": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz",
+ "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.3.0",
+ "@jest/fake-timers": "30.3.0",
+ "@jest/types": "30.3.0",
+ "@types/node": "*",
+ "jest-mock": "30.3.0",
+ "jest-util": "30.3.0",
+ "jest-validate": "30.3.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-haste-map": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz",
+ "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.3.0",
+ "@types/node": "*",
+ "anymatch": "^3.1.3",
+ "fb-watchman": "^2.0.2",
+ "graceful-fs": "^4.2.11",
+ "jest-regex-util": "30.0.1",
+ "jest-util": "30.3.0",
+ "jest-worker": "30.3.0",
+ "picomatch": "^4.0.3",
+ "walker": "^1.0.8"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "^2.3.3"
+ }
+ },
+ "node_modules/jest-leak-detector": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz",
+ "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "pretty-format": "30.3.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz",
+ "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "chalk": "^4.1.2",
+ "jest-diff": "30.3.0",
+ "pretty-format": "30.3.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-message-util": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz",
+ "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@jest/types": "30.3.0",
+ "@types/stack-utils": "^2.0.3",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "picomatch": "^4.0.3",
+ "pretty-format": "30.3.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.6"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-mock": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz",
+ "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.3.0",
+ "@types/node": "*",
+ "jest-util": "30.3.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-pnp-resolver": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
+ "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "peerDependencies": {
+ "jest-resolve": "*"
+ },
+ "peerDependenciesMeta": {
+ "jest-resolve": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-regex-util": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz",
+ "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-resolve": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz",
+ "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.3.0",
+ "jest-pnp-resolver": "^1.2.3",
+ "jest-util": "30.3.0",
+ "jest-validate": "30.3.0",
+ "slash": "^3.0.0",
+ "unrs-resolver": "^1.7.11"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-resolve-dependencies": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz",
+ "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "jest-regex-util": "30.0.1",
+ "jest-snapshot": "30.3.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-runner": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz",
+ "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "30.3.0",
+ "@jest/environment": "30.3.0",
+ "@jest/test-result": "30.3.0",
+ "@jest/transform": "30.3.0",
+ "@jest/types": "30.3.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "emittery": "^0.13.1",
+ "exit-x": "^0.2.2",
+ "graceful-fs": "^4.2.11",
+ "jest-docblock": "30.2.0",
+ "jest-environment-node": "30.3.0",
+ "jest-haste-map": "30.3.0",
+ "jest-leak-detector": "30.3.0",
+ "jest-message-util": "30.3.0",
+ "jest-resolve": "30.3.0",
+ "jest-runtime": "30.3.0",
+ "jest-util": "30.3.0",
+ "jest-watcher": "30.3.0",
+ "jest-worker": "30.3.0",
+ "p-limit": "^3.1.0",
+ "source-map-support": "0.5.13"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-runner/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/jest-runner/node_modules/source-map-support": {
+ "version": "0.5.13",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
+ "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/jest-runtime": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz",
+ "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.3.0",
+ "@jest/fake-timers": "30.3.0",
+ "@jest/globals": "30.3.0",
+ "@jest/source-map": "30.0.1",
+ "@jest/test-result": "30.3.0",
+ "@jest/transform": "30.3.0",
+ "@jest/types": "30.3.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "cjs-module-lexer": "^2.1.0",
+ "collect-v8-coverage": "^1.0.2",
+ "glob": "^10.5.0",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.3.0",
+ "jest-message-util": "30.3.0",
+ "jest-mock": "30.3.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.3.0",
+ "jest-snapshot": "30.3.0",
+ "jest-util": "30.3.0",
+ "slash": "^3.0.0",
+ "strip-bom": "^4.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-runtime/node_modules/brace-expansion": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
+ "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/jest-runtime/node_modules/glob": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "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/jest-runtime/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/jest-runtime/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/jest-runtime/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/jest-snapshot": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz",
+ "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@babel/generator": "^7.27.5",
+ "@babel/plugin-syntax-jsx": "^7.27.1",
+ "@babel/plugin-syntax-typescript": "^7.27.1",
+ "@babel/types": "^7.27.3",
+ "@jest/expect-utils": "30.3.0",
+ "@jest/get-type": "30.1.0",
+ "@jest/snapshot-utils": "30.3.0",
+ "@jest/transform": "30.3.0",
+ "@jest/types": "30.3.0",
+ "babel-preset-current-node-syntax": "^1.2.0",
+ "chalk": "^4.1.2",
+ "expect": "30.3.0",
+ "graceful-fs": "^4.2.11",
+ "jest-diff": "30.3.0",
+ "jest-matcher-utils": "30.3.0",
+ "jest-message-util": "30.3.0",
+ "jest-util": "30.3.0",
+ "pretty-format": "30.3.0",
+ "semver": "^7.7.2",
+ "synckit": "^0.11.8"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-util": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz",
+ "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.3.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "graceful-fs": "^4.2.11",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-validate": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz",
+ "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "@jest/types": "30.3.0",
+ "camelcase": "^6.3.0",
+ "chalk": "^4.1.2",
+ "leven": "^3.1.0",
+ "pretty-format": "30.3.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-validate/node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jest-watcher": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz",
+ "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/test-result": "30.3.0",
+ "@jest/types": "30.3.0",
+ "@types/node": "*",
+ "ansi-escapes": "^4.3.2",
+ "chalk": "^4.1.2",
+ "emittery": "^0.13.1",
+ "jest-util": "30.3.0",
+ "string-length": "^4.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-worker": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz",
+ "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@ungap/structured-clone": "^1.3.0",
+ "jest-util": "30.3.0",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.1.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "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/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/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/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/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/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/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/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/jsonwebtoken": {
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
+ "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
+ "license": "MIT",
+ "dependencies": {
+ "jws": "^4.0.1",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
+ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/leven": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "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/load-esm": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.3.tgz",
+ "integrity": "sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
+ },
+ {
+ "type": "buymeacoffee",
+ "url": "https://buymeacoffee.com/borewit"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=13.2.0"
+ }
+ },
+ "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/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
+ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.memoize": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
+ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
+ "license": "MIT"
+ },
+ "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/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/luxon": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz",
+ "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.17",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
+ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "devOptional": true,
+ "license": "ISC"
+ },
+ "node_modules/makeerror": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
+ "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tmpl": "1.0.5"
+ }
+ },
+ "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/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/micromatch/node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/mime": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
+ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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/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/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/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/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/multer": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/multer/-/multer-2.1.1.tgz",
+ "integrity": "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==",
+ "license": "MIT",
+ "dependencies": {
+ "append-field": "^1.0.0",
+ "busboy": "^1.6.0",
+ "concat-stream": "^2.0.0",
+ "type-is": "^1.6.18"
+ },
+ "engines": {
+ "node": ">= 10.16.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/multer/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/multer/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/multer/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/multer/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/mute-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz",
+ "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^18.17.0 || >=20.5.0"
+ }
+ },
+ "node_modules/napi-postinstall": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
+ "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "napi-postinstall": "lib/cli.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/napi-postinstall"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "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/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/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/node-int64": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/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/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/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/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/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "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/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "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/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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/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/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/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/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-scurry": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
+ "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^11.0.0",
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "11.5.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz",
+ "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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/pg": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz",
+ "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==",
+ "license": "MIT",
+ "dependencies": {
+ "pg-connection-string": "^2.12.0",
+ "pg-pool": "^3.13.0",
+ "pg-protocol": "^1.13.0",
+ "pg-types": "2.2.0",
+ "pgpass": "1.0.5"
+ },
+ "engines": {
+ "node": ">= 16.0.0"
+ },
+ "optionalDependencies": {
+ "pg-cloudflare": "^1.3.0"
+ },
+ "peerDependencies": {
+ "pg-native": ">=3.0.1"
+ },
+ "peerDependenciesMeta": {
+ "pg-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pg-cloudflare": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz",
+ "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/pg-connection-string": {
+ "version": "2.12.0",
+ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz",
+ "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==",
+ "license": "MIT"
+ },
+ "node_modules/pg-int8": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/pg-pool": {
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz",
+ "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "pg": ">=8.0"
+ }
+ },
+ "node_modules/pg-protocol": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz",
+ "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==",
+ "license": "MIT"
+ },
+ "node_modules/pg-types": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+ "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+ "license": "MIT",
+ "dependencies": {
+ "pg-int8": "1.0.1",
+ "postgres-array": "~2.0.0",
+ "postgres-bytea": "~1.0.0",
+ "postgres-date": "~1.0.4",
+ "postgres-interval": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pgpass": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
+ "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+ "license": "MIT",
+ "dependencies": {
+ "split2": "^4.1.0"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/postgres-array": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+ "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postgres-bytea": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
+ "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-date": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+ "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-interval": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+ "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "xtend": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz",
+ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz",
+ "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-diff": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "30.3.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz",
+ "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.5",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "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/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/pure-rand": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz",
+ "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/qs": {
+ "version": "6.15.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
+ "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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/raw-body": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/readdirp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "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"
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve-cwd": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
+ "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-cwd/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/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/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/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "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",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "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/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/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/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "license": "MIT",
+ "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"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/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/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/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/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/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/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/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/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/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/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/split2": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">= 10.x"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/sql-highlight": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/sql-highlight/-/sql-highlight-6.1.0.tgz",
+ "integrity": "sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA==",
+ "funding": [
+ "https://github.com/scriptcoded/sql-highlight?sponsor=1",
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/scriptcoded"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/stack-utils": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/stack-utils/node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/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/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/string-length": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "char-regex": "^1.0.2",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "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/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/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/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/strip-bom": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
+ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strtok3": {
+ "version": "10.3.5",
+ "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz",
+ "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tokenizer/token": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
+ }
+ },
+ "node_modules/superagent": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz",
+ "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "component-emitter": "^1.3.1",
+ "cookiejar": "^2.1.4",
+ "debug": "^4.3.7",
+ "fast-safe-stringify": "^2.1.1",
+ "form-data": "^4.0.5",
+ "formidable": "^3.5.4",
+ "methods": "^1.1.2",
+ "mime": "2.6.0",
+ "qs": "^6.14.1"
+ },
+ "engines": {
+ "node": ">=14.18.0"
+ }
+ },
+ "node_modules/supertest": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz",
+ "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cookie-signature": "^1.2.2",
+ "methods": "^1.1.2",
+ "superagent": "^10.3.0"
+ },
+ "engines": {
+ "node": ">=14.18.0"
+ }
+ },
+ "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/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/synckit": {
+ "version": "0.11.12",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz",
+ "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@pkgr/core": "^0.2.9"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/synckit"
+ }
+ },
+ "node_modules/tapable": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz",
+ "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/terser": {
+ "version": "5.46.1",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz",
+ "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==",
+ "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/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/terser-webpack-plugin/node_modules/ajv": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
+ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/terser-webpack-plugin/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/terser-webpack-plugin/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/terser-webpack-plugin/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/terser-webpack-plugin/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/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/terser-webpack-plugin/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/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/test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/test-exclude/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "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": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tmpl": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
+ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "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/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/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/ts-api-utils": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+ "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/ts-jest": {
+ "version": "29.4.6",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz",
+ "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bs-logger": "^0.2.6",
+ "fast-json-stable-stringify": "^2.1.0",
+ "handlebars": "^4.7.8",
+ "json5": "^2.2.3",
+ "lodash.memoize": "^4.1.2",
+ "make-error": "^1.3.6",
+ "semver": "^7.7.3",
+ "type-fest": "^4.41.0",
+ "yargs-parser": "^21.1.1"
+ },
+ "bin": {
+ "ts-jest": "cli.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": ">=7.0.0-beta.0 <8",
+ "@jest/transform": "^29.0.0 || ^30.0.0",
+ "@jest/types": "^29.0.0 || ^30.0.0",
+ "babel-jest": "^29.0.0 || ^30.0.0",
+ "jest": "^29.0.0 || ^30.0.0",
+ "jest-util": "^29.0.0 || ^30.0.0",
+ "typescript": ">=4.3 <6"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "@jest/transform": {
+ "optional": true
+ },
+ "@jest/types": {
+ "optional": true
+ },
+ "babel-jest": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jest-util": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ts-jest/node_modules/type-fest": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+ "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ts-loader": {
+ "version": "9.5.4",
+ "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz",
+ "integrity": "sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "enhanced-resolve": "^5.0.0",
+ "micromatch": "^4.0.0",
+ "semver": "^7.3.4",
+ "source-map": "^0.7.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "typescript": "*",
+ "webpack": "^5.0.0"
+ }
+ },
+ "node_modules/ts-node": {
+ "version": "10.9.2",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
+ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@cspotcode/source-map-support": "^0.8.0",
+ "@tsconfig/node10": "^1.0.7",
+ "@tsconfig/node12": "^1.0.7",
+ "@tsconfig/node14": "^1.0.0",
+ "@tsconfig/node16": "^1.0.2",
+ "acorn": "^8.4.1",
+ "acorn-walk": "^8.1.1",
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "v8-compile-cache-lib": "^3.0.1",
+ "yn": "3.1.1"
+ },
+ "bin": {
+ "ts-node": "dist/bin.js",
+ "ts-node-cwd": "dist/bin-cwd.js",
+ "ts-node-esm": "dist/bin-esm.js",
+ "ts-node-script": "dist/bin-script.js",
+ "ts-node-transpile-only": "dist/bin-transpile.js",
+ "ts-script": "dist/bin-script-deprecated.js"
+ },
+ "peerDependencies": {
+ "@swc/core": ">=1.2.50",
+ "@swc/wasm": ">=1.2.50",
+ "@types/node": "*",
+ "typescript": ">=2.7"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "@swc/wasm": {
+ "optional": true
+ }
+ }
+ },
+ "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-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/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/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/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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/type-is": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "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/typeorm": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-1.0.0.tgz",
+ "integrity": "sha512-2mSKNqucP8vo+xQLP59xlHUcqLvG6qajxA7q7tnhJgeZjTrA6lK/Ar7LRyiAxdXhyXmGbIPsArPmcUB9Xg+M7w==",
+ "license": "MIT",
+ "dependencies": {
+ "@sqltools/formatter": "^1.2.5",
+ "ansis": "^4.2.0",
+ "dayjs": "^1.11.20",
+ "debug": "^4.4.3",
+ "dedent": "^1.7.2",
+ "reflect-metadata": "^0.2.2",
+ "sql-highlight": "^6.1.0",
+ "tinyglobby": "^0.2.16",
+ "tslib": "^2.8.1",
+ "yargs": "^18.0.0"
+ },
+ "bin": {
+ "typeorm": "cli.js",
+ "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js",
+ "typeorm-ts-node-esm": "cli-ts-node-esm.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24.11.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/typeorm"
+ },
+ "peerDependencies": {
+ "@google-cloud/spanner": "^8.0.0",
+ "@sap/hana-client": "^2.14.22",
+ "better-sqlite3": "^12.0.0",
+ "ioredis": "^5.0.4",
+ "mongodb": "^7.0.0",
+ "mssql": "^12.0.0",
+ "mysql2": "^3.15.3",
+ "oracledb": "^6.3.0",
+ "pg": "^8.5.1",
+ "pg-native": "^3.0.0",
+ "pg-query-stream": "^4.0.0",
+ "redis": "^5.0.0",
+ "sql.js": "^1.4.0",
+ "ts-node": "^10.9.2",
+ "typeorm-aurora-data-api-driver": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@google-cloud/spanner": {
+ "optional": true
+ },
+ "@sap/hana-client": {
+ "optional": true
+ },
+ "better-sqlite3": {
+ "optional": true
+ },
+ "ioredis": {
+ "optional": true
+ },
+ "mongodb": {
+ "optional": true
+ },
+ "mssql": {
+ "optional": true
+ },
+ "mysql2": {
+ "optional": true
+ },
+ "oracledb": {
+ "optional": true
+ },
+ "pg": {
+ "optional": true
+ },
+ "pg-native": {
+ "optional": true
+ },
+ "pg-query-stream": {
+ "optional": true
+ },
+ "redis": {
+ "optional": true
+ },
+ "sql.js": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ },
+ "typeorm-aurora-data-api-driver": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/typeorm/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/typeorm/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==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/typeorm/node_modules/cliui": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz",
+ "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^7.2.0",
+ "strip-ansi": "^7.1.0",
+ "wrap-ansi": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/typeorm/node_modules/emoji-regex": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
+ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
+ "license": "MIT"
+ },
+ "node_modules/typeorm/node_modules/string-width": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typeorm/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==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/typeorm/node_modules/wrap-ansi": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
+ "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "string-width": "^7.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/typeorm/node_modules/yargs": {
+ "version": "18.0.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz",
+ "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==",
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^9.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "string-width": "^7.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^22.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=23"
+ }
+ },
+ "node_modules/typeorm/node_modules/yargs-parser": {
+ "version": "22.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz",
+ "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==",
+ "license": "ISC",
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=23"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-eslint": {
+ "version": "8.57.2",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.2.tgz",
+ "integrity": "sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.57.2",
+ "@typescript-eslint/parser": "8.57.2",
+ "@typescript-eslint/typescript-estree": "8.57.2",
+ "@typescript-eslint/utils": "8.57.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/uglify-js": {
+ "version": "3.19.3",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
+ "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "optional": true,
+ "bin": {
+ "uglifyjs": "bin/uglifyjs"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "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/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/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "license": "MIT"
+ },
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/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/unrs-resolver": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz",
+ "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "napi-postinstall": "^0.3.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unrs-resolver"
+ },
+ "optionalDependencies": {
+ "@unrs/resolver-binding-android-arm-eabi": "1.11.1",
+ "@unrs/resolver-binding-android-arm64": "1.11.1",
+ "@unrs/resolver-binding-darwin-arm64": "1.11.1",
+ "@unrs/resolver-binding-darwin-x64": "1.11.1",
+ "@unrs/resolver-binding-freebsd-x64": "1.11.1",
+ "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1",
+ "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1",
+ "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-arm64-musl": "1.11.1",
+ "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1",
+ "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-x64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-x64-musl": "1.11.1",
+ "@unrs/resolver-binding-wasm32-wasi": "1.11.1",
+ "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1",
+ "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1",
+ "@unrs/resolver-binding-win32-x64-msvc": "1.11.1"
+ }
+ },
+ "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/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/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/v8-compile-cache-lib": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/v8-to-istanbul": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
+ "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.12",
+ "@types/istanbul-lib-coverage": "^2.0.1",
+ "convert-source-map": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
+ "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/walker": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
+ "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "makeerror": "1.0.12"
+ }
+ },
+ "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/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/webpack": {
+ "version": "5.106.0",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.0.tgz",
+ "integrity": "sha512-Pkx5joZ9RrdgO5LBkyX1L2ZAJeK/Taz3vqZ9CbcP0wS5LEMx5QkKsEwLl29QJfihZ+DKRBFldzy1O30pJ1MDpA==",
+ "dev": true,
+ "license": "MIT",
+ "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.3",
+ "browserslist": "^4.28.1",
+ "chrome-trace-event": "^1.0.2",
+ "enhanced-resolve": "^5.20.0",
+ "es-module-lexer": "^2.0.0",
+ "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.3.1",
+ "mime-types": "^2.1.27",
+ "neo-async": "^2.6.2",
+ "schema-utils": "^4.3.3",
+ "tapable": "^2.3.0",
+ "terser-webpack-plugin": "^5.3.17",
+ "watchpack": "^2.5.1",
+ "webpack-sources": "^3.3.4"
+ },
+ "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/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/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/webpack/node_modules/ajv": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
+ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/webpack/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/webpack/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/webpack/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/webpack/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/webpack/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/webpack/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/webpack/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/webpack/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/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/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wordwrap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/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/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/write-file-atomic": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
+ "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
+ "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/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "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/yn": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/yoctocolors-cjs": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz",
+ "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/backend/package.json b/backend/package.json
new file mode 100644
index 0000000..a11e2fc
--- /dev/null
+++ b/backend/package.json
@@ -0,0 +1,84 @@
+{
+ "name": "backend",
+ "version": "0.0.1",
+ "description": "",
+ "author": "",
+ "private": true,
+ "license": "UNLICENSED",
+ "scripts": {
+ "build": "nest build",
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
+ "start": "nest start",
+ "start:dev": "nest start --watch",
+ "start:debug": "nest start --debug --watch",
+ "start:prod": "node dist/main",
+ "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
+ "test": "jest",
+ "test:watch": "jest --watch",
+ "test:cov": "jest --coverage",
+ "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
+ "test:e2e": "jest --config ./test/jest-e2e.json",
+ "migration:generate": "npx typeorm-ts-node-commonjs migration:generate -d src/data-source.ts",
+ "migration:run": "npx typeorm-ts-node-commonjs migration:run -d src/data-source.ts",
+ "migration:revert": "npx typeorm-ts-node-commonjs migration:revert -d src/data-source.ts"
+ },
+ "dependencies": {
+ "@nestjs/common": "^11.0.1",
+ "@nestjs/config": "^4.0.4",
+ "@nestjs/core": "^11.1.18",
+ "@nestjs/jwt": "^11.0.2",
+ "@nestjs/platform-express": "^11.1.18",
+ "@nestjs/schedule": "^6.1.3",
+ "@nestjs/throttler": "^6.5.0",
+ "@nestjs/typeorm": "^11.0.0",
+ "helmet": "^8.1.0",
+ "jsonwebtoken": "^9.0.3",
+ "pg": "^8.20.0",
+ "reflect-metadata": "^0.2.2",
+ "rxjs": "^7.8.1",
+ "typeorm": "^1.0.0"
+ },
+ "devDependencies": {
+ "@eslint/eslintrc": "^3.2.0",
+ "@eslint/js": "^9.18.0",
+ "@nestjs/cli": "^11.0.0",
+ "@nestjs/schematics": "^11.0.0",
+ "@nestjs/testing": "^11.0.1",
+ "@types/express": "^5.0.0",
+ "@types/jest": "^30.0.0",
+ "@types/jsonwebtoken": "^9.0.10",
+ "@types/node": "^22.10.7",
+ "@types/supertest": "^6.0.2",
+ "eslint": "^9.18.0",
+ "eslint-config-prettier": "^10.0.1",
+ "eslint-plugin-prettier": "^5.2.2",
+ "globals": "^16.0.0",
+ "jest": "^30.0.0",
+ "prettier": "^3.4.2",
+ "source-map-support": "^0.5.21",
+ "supertest": "^7.0.0",
+ "ts-jest": "^29.2.5",
+ "ts-loader": "^9.5.2",
+ "ts-node": "^10.9.2",
+ "tsconfig-paths": "^4.2.0",
+ "typescript": "^5.7.3",
+ "typescript-eslint": "^8.20.0"
+ },
+ "jest": {
+ "moduleFileExtensions": [
+ "js",
+ "json",
+ "ts"
+ ],
+ "rootDir": "src",
+ "testRegex": ".*\\.spec\\.ts$",
+ "transform": {
+ "^.+\\.(t|j)s$": "ts-jest"
+ },
+ "collectCoverageFrom": [
+ "**/*.(t|j)s"
+ ],
+ "coverageDirectory": "../coverage",
+ "testEnvironment": "node"
+ }
+}
diff --git a/backend/src/admin/activity-stats.ts b/backend/src/admin/activity-stats.ts
new file mode 100644
index 0000000..e6a98cf
--- /dev/null
+++ b/backend/src/admin/activity-stats.ts
@@ -0,0 +1,324 @@
+// Activity analysis over a participant's Hackatime heartbeats — produces a
+// timeline, breakdowns by editor/category, and a `filterable` index so the
+// client can recompute active minutes when an activity type is excluded.
+
+export type Heartbeat = {
+ time: number;
+ entity: string;
+ type: string;
+ project?: string;
+ branch?: string;
+ language?: string;
+ editor?: string;
+ operating_system?: string;
+ user_agent?: string;
+ category?: string;
+ is_write?: boolean;
+ lineno?: number;
+ cursorpos?: number;
+ lines?: number;
+ line_additions?: number;
+ line_deletions?: number;
+};
+
+export type Severity = 'none' | 'low' | 'high';
+
+export type Analysis = {
+ summary: {
+ count: number;
+ spanSeconds: number;
+ activeMinutes: number;
+ firstAt: number;
+ lastAt: number;
+ coveredDays: number;
+ aiPercent: number;
+ aiCount: number;
+ pasteLikeCount: number;
+ };
+ editorBreakdown: Record;
+ categoryBreakdown: Record;
+ userAgents: Array<{ ua: string; count: number }>;
+ anomalies: {
+ autoClicker: { severity: Severity; evidence: string };
+ macroTyper: { severity: Severity; evidence: string };
+ offRepo: { count: number; sampleEntities: string[] };
+ };
+ // [times, lineNumbers, cursorPositions] for plotting.
+ points: [number[], (number | null)[], (number | null)[]];
+ // per-heartbeat (time, editor index, category index) for client-side
+ // exclusion-driven active-minute recomputation.
+ filterable: {
+ editorList: string[];
+ categoryList: string[];
+ times: number[];
+ editorIdx: number[];
+ categoryIdx: number[];
+ };
+};
+
+function stddev(values: number[]): number {
+ if (values.length < 2) return 0;
+ const m = values.reduce((a, b) => a + b, 0) / values.length;
+ const variance = values.reduce((a, b) => a + (b - m) ** 2, 0) / values.length;
+ return Math.sqrt(variance);
+}
+
+function mean(values: number[]): number {
+ if (values.length === 0) return 0;
+ return values.reduce((a, b) => a + b, 0) / values.length;
+}
+
+function basename(p: string): string {
+ const idx = Math.max(p.lastIndexOf('/'), p.lastIndexOf('\\'));
+ return idx >= 0 ? p.slice(idx + 1) : p;
+}
+
+function entityMatchesRepo(
+ entity: string,
+ repoBasenames: Set,
+ repoPaths: Set,
+): boolean {
+ if (!entity) return false;
+ if (repoBasenames.has(basename(entity))) return true;
+ const norm = entity.replace(/\\/g, '/');
+ for (const p of repoPaths) {
+ if (norm.endsWith(p)) return true;
+ }
+ return false;
+}
+
+const AI_EDITOR_RE =
+ /\b(cursor|windsurf|cline|continue|aider|copilot|trae|zed-ai|tabnine|codeium|cody|amazonq|amazon-q|claude|claude-code|claudecode|anthropic)\b/i;
+const AI_CATEGORY_RE = /\bai\b|copilot|cursor|aider|codeium|cody|claude|anthropic/i;
+const LARGE_ADDITIONS_THRESHOLD = 30;
+
+export function analyzeActivity(
+ hb: Heartbeat[],
+ repoFilePaths: Set = new Set(),
+): Analysis {
+ const sorted = [...hb].sort((a, b) => a.time - b.time);
+ const count = sorted.length;
+ const firstAt = count > 0 ? sorted[0].time : 0;
+ const lastAt = count > 0 ? sorted[count - 1].time : 0;
+ const spanSeconds = Math.max(0, lastAt - firstAt);
+
+ const repoBasenames = new Set();
+ for (const p of repoFilePaths) repoBasenames.add(basename(p));
+
+ // active minutes via 2-min idle gap
+ const minuteBuckets = new Map();
+ for (const h of sorted) {
+ const m = Math.floor(h.time / 60);
+ const arr = minuteBuckets.get(m) ?? [];
+ arr.push(h);
+ minuteBuckets.set(m, arr);
+ }
+ let activeMinutes = 0;
+ let lastT = -Infinity;
+ for (const h of sorted) {
+ if (h.time - lastT > 120) {
+ activeMinutes += 1;
+ } else {
+ const lastMinute = Math.floor(lastT / 60);
+ const thisMinute = Math.floor(h.time / 60);
+ if (thisMinute !== lastMinute) activeMinutes += 1;
+ }
+ lastT = h.time;
+ }
+
+ const coveredDayKeys = new Set();
+ for (const h of sorted) {
+ const d = new Date(h.time * 1000);
+ coveredDayKeys.add(`${d.getUTCFullYear()}-${d.getUTCMonth()}-${d.getUTCDate()}`);
+ }
+
+ const editorBreakdown: Record = {};
+ const categoryBreakdown: Record = {};
+ const uaCounts = new Map();
+ for (const h of sorted) {
+ const ed = h.editor ?? 'unknown';
+ editorBreakdown[ed] = (editorBreakdown[ed] ?? 0) + 1;
+ const cat = h.category ?? 'unknown';
+ categoryBreakdown[cat] = (categoryBreakdown[cat] ?? 0) + 1;
+ const ua = h.user_agent ?? 'unknown';
+ uaCounts.set(ua, (uaCounts.get(ua) ?? 0) + 1);
+ }
+
+ let aiCount = 0;
+ let pasteLikeCount = 0;
+ for (const h of sorted) {
+ const ed = (h.editor ?? '').toLowerCase();
+ const cat = (h.category ?? '').toLowerCase();
+ const ua = (h.user_agent ?? '').toLowerCase();
+ const additions = h.line_additions ?? 0;
+ const editorIsAi = AI_EDITOR_RE.test(ed) || AI_EDITOR_RE.test(ua);
+ const categoryIsAi = AI_CATEGORY_RE.test(cat);
+ const looksLikePaste = additions >= LARGE_ADDITIONS_THRESHOLD;
+ if (editorIsAi || categoryIsAi || looksLikePaste) {
+ aiCount += 1;
+ if (!editorIsAi && !categoryIsAi && looksLikePaste) pasteLikeCount += 1;
+ }
+ }
+ const aiPercent = count > 0 ? Math.round((aiCount / count) * 100) : 0;
+ const userAgents = [...uaCounts.entries()]
+ .map(([ua, c]) => ({ ua, count: c }))
+ .sort((a, b) => b.count - a.count);
+
+ // autoClicker: near-flat cursorpos across consecutive minutes with writes
+ let flatStreak = 0;
+ let flatStreakWithWrites = false;
+ let observedFlatWithWrites = 0;
+ const sortedBucketKeys = [...minuteBuckets.keys()].sort((a, b) => a - b);
+ for (let i = 0; i < sortedBucketKeys.length; i++) {
+ const k = sortedBucketKeys[i];
+ const bucket = minuteBuckets.get(k) ?? [];
+ const cps = bucket
+ .map((b) => b.cursorpos)
+ .filter((v): v is number => typeof v === 'number');
+ const sd = stddev(cps);
+ const hasWrites = bucket.some((b) => b.is_write === true);
+ const consecutive = i > 0 && sortedBucketKeys[i - 1] === k - 1;
+ if (sd < 5 && cps.length >= 2) {
+ flatStreak = consecutive ? flatStreak + 1 : 1;
+ if (hasWrites) flatStreakWithWrites = true;
+ } else {
+ if (flatStreak >= 3 && flatStreakWithWrites) {
+ observedFlatWithWrites = Math.max(observedFlatWithWrites, flatStreak);
+ }
+ flatStreak = 0;
+ flatStreakWithWrites = false;
+ }
+ }
+ if (flatStreak >= 3 && flatStreakWithWrites) {
+ observedFlatWithWrites = Math.max(observedFlatWithWrites, flatStreak);
+ }
+ const autoClicker: Analysis['anomalies']['autoClicker'] =
+ observedFlatWithWrites >= 5
+ ? {
+ severity: 'high',
+ evidence: `${observedFlatWithWrites} minutes of near-flat cursor with active writes`,
+ }
+ : observedFlatWithWrites >= 3
+ ? {
+ severity: 'low',
+ evidence: `${observedFlatWithWrites} minutes of near-flat cursor with active writes`,
+ }
+ : { severity: 'none', evidence: '' };
+
+ // macroTyper: sliding window of 20 inter-heartbeat intervals, near-constant
+ const intervals: number[] = [];
+ for (let i = 1; i < sorted.length; i++) {
+ intervals.push(sorted[i].time - sorted[i - 1].time);
+ }
+ let macroTyper: Analysis['anomalies']['macroTyper'] = {
+ severity: 'none',
+ evidence: '',
+ };
+ if (intervals.length >= 20) {
+ let flagged: { start: number; end: number; m: number; sd: number } | null =
+ null;
+ for (let i = 0; i + 20 <= intervals.length; i++) {
+ const win = intervals.slice(i, i + 20);
+ const m = mean(win);
+ const sd = stddev(win);
+ if (m < 10 && sd < 0.3) {
+ if (!flagged || sd < flagged.sd) {
+ flagged = { start: sorted[i].time, end: sorted[i + 20].time, m, sd };
+ }
+ }
+ }
+ if (flagged) {
+ macroTyper = {
+ severity: flagged.sd < 0.1 ? 'high' : 'low',
+ evidence: `cluster ${new Date(flagged.start * 1000).toISOString()} → ${new Date(
+ flagged.end * 1000,
+ ).toISOString()} (mean ${flagged.m.toFixed(2)}s, stddev ${flagged.sd.toFixed(3)}s)`,
+ };
+ }
+ }
+
+ // offRepo — only meaningful when we have a repo file tree (beest doesn't).
+ const offEntities = new Set();
+ let offCount = 0;
+ if (repoFilePaths.size > 0) {
+ for (const h of sorted) {
+ if (!entityMatchesRepo(h.entity, repoBasenames, repoFilePaths)) {
+ offCount += 1;
+ if (offEntities.size < 5) offEntities.add(h.entity);
+ }
+ }
+ }
+
+ // points downsample
+ const maxPoints = 50_000;
+ const stride = sorted.length > maxPoints ? Math.ceil(sorted.length / maxPoints) : 1;
+ const xs: number[] = [];
+ const lines: (number | null)[] = [];
+ const cursors: (number | null)[] = [];
+ for (let i = 0; i < sorted.length; i += stride) {
+ const h = sorted[i];
+ xs.push(h.time);
+ lines.push(typeof h.lineno === 'number' ? h.lineno : null);
+ cursors.push(typeof h.cursorpos === 'number' ? h.cursorpos : null);
+ }
+
+ // filterable per-heartbeat indices for client-side exclusion recompute
+ const editorIndexMap = new Map();
+ const categoryIndexMap = new Map();
+ const editorList: string[] = [];
+ const categoryList: string[] = [];
+ const fTimes: number[] = [];
+ const fEd: number[] = [];
+ const fCat: number[] = [];
+ const filterStride = sorted.length > 50_000 ? Math.ceil(sorted.length / 50_000) : 1;
+ for (let i = 0; i < sorted.length; i += filterStride) {
+ const h = sorted[i];
+ const ed = h.editor ?? 'unknown';
+ const cat = h.category ?? 'unknown';
+ let eIdx = editorIndexMap.get(ed);
+ if (eIdx === undefined) {
+ eIdx = editorList.length;
+ editorList.push(ed);
+ editorIndexMap.set(ed, eIdx);
+ }
+ let cIdx = categoryIndexMap.get(cat);
+ if (cIdx === undefined) {
+ cIdx = categoryList.length;
+ categoryList.push(cat);
+ categoryIndexMap.set(cat, cIdx);
+ }
+ fTimes.push(h.time);
+ fEd.push(eIdx);
+ fCat.push(cIdx);
+ }
+
+ return {
+ summary: {
+ count,
+ spanSeconds,
+ activeMinutes,
+ firstAt,
+ lastAt,
+ coveredDays: coveredDayKeys.size,
+ aiPercent,
+ aiCount,
+ pasteLikeCount,
+ },
+ editorBreakdown,
+ categoryBreakdown,
+ userAgents,
+ anomalies: {
+ autoClicker,
+ macroTyper,
+ offRepo: { count: offCount, sampleEntities: [...offEntities] },
+ },
+ points: [xs, lines, cursors],
+ filterable: {
+ editorList,
+ categoryList,
+ times: fTimes,
+ editorIdx: fEd,
+ categoryIdx: fCat,
+ },
+ };
+}
diff --git a/backend/src/admin/admin.controller.ts b/backend/src/admin/admin.controller.ts
new file mode 100644
index 0000000..0074f0c
--- /dev/null
+++ b/backend/src/admin/admin.controller.ts
@@ -0,0 +1,542 @@
+import {
+ Controller,
+ Get,
+ Post,
+ Patch,
+ Delete,
+ Param,
+ Body,
+ Req,
+ Res,
+ UseGuards,
+ BadRequestException,
+ ParseUUIDPipe,
+} from '@nestjs/common';
+import type { Request, Response } from 'express';
+import { SuperAdminGuard } from './super-admin.guard';
+import { ReviewerGuard } from './reviewer.guard';
+import { FraudReviewerGuard } from './fraud-reviewer.guard';
+import { AdminService } from './admin.service';
+import { AuditService, type AuditAction } from './audit.service';
+import { AuditLogService } from '../audit-log/audit-log.service';
+import { AuthService } from '../auth/auth.service';
+import { ShopService } from '../shop/shop.service';
+import { DevlogsService } from '../devlogs/devlogs.service';
+
+@Controller('api/admin')
+export class AdminController {
+ constructor(
+ private readonly adminService: AdminService,
+ private readonly auditService: AuditService,
+ private readonly auditLogService: AuditLogService,
+ private readonly authService: AuthService,
+ private readonly shopService: ShopService,
+ private readonly devlogsService: DevlogsService,
+ ) {}
+
+ @UseGuards(SuperAdminGuard)
+ @Get('users')
+ listUsers() {
+ return this.adminService.listUsers();
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Get('users/:id')
+ getUser(@Param('id', ParseUUIDPipe) id: string) {
+ return this.adminService.getUser(id);
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Post('users/:id/ban')
+ async banUser(
+ @Param('id', ParseUUIDPipe) id: string,
+ @Req() req: Request,
+ ) {
+ const adminId = (req as any).user?.uid;
+ await this.adminService.banUser(id, adminId);
+ return { success: true };
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Patch('users/:id/perms')
+ async updatePerms(
+ @Param('id', ParseUUIDPipe) id: string,
+ @Body() body: { perms?: string },
+ @Req() req: Request,
+ ) {
+ if (!body.perms || typeof body.perms !== 'string') {
+ throw new BadRequestException('perms is required');
+ }
+ const adminId = (req as any).user?.uid;
+ await this.adminService.updatePerms(id, body.perms, adminId);
+ return { success: true };
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Patch('users/:id/pipes')
+ async adjustPipes(
+ @Param('id', ParseUUIDPipe) id: string,
+ @Body() body: { delta?: number; reason?: string | null },
+ @Req() req: Request,
+ ) {
+ if (typeof body.delta !== 'number' || !Number.isInteger(body.delta) || body.delta === 0) {
+ throw new BadRequestException('delta must be a non-zero integer');
+ }
+ const MAX_DELTA = 100_000;
+ if (Math.abs(body.delta) > MAX_DELTA) {
+ throw new BadRequestException(`delta must be between -${MAX_DELTA} and ${MAX_DELTA}`);
+ }
+ const reason = typeof body.reason === 'string' ? body.reason.trim().slice(0, 200) : null;
+ const adminId = (req as any).user?.uid;
+ const result = await this.adminService.adjustPipes(id, body.delta, reason || null, adminId);
+ return { success: true, pipes: result.pipes };
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Post('users/:id/impersonate')
+ async impersonateUser(
+ @Param('id', ParseUUIDPipe) id: string,
+ @Req() req: Request,
+ ) {
+ const admin = (req as any).user;
+ const adminUid = admin?.uid as string;
+ const adminName = admin?.name as string ?? 'Admin';
+
+ // Look up target user's nickname for a friendlier log
+ const targetUser = await this.adminService.getUser(id);
+ const targetNick = targetUser.nickname || targetUser.name || id;
+
+ // Log impersonation on both accounts
+ await this.auditLogService.log(adminUid, 'admin_impersonate', `Started impersonating ${targetNick}`);
+ await this.auditLogService.log(id, 'admin_impersonate', `Admin ${adminName} started impersonating this account`);
+
+ return this.authService.issueImpersonationToken(id, adminUid, adminName);
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Get('stats/dau')
+ getDailyActiveUsers() {
+ return this.adminService.getDailyActiveUsers();
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Get('stats/dau/history')
+ getDauHistory() {
+ return this.adminService.getDauHistory();
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Get('stats/signups')
+ getSignupsHistory() {
+ return this.adminService.getSignupsHistory();
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Get('stats/funnel')
+ getUserFunnel() {
+ return this.adminService.getUserFunnel();
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Get('stats/unreviewed-hours')
+ getUnreviewedHours() {
+ return this.adminService.getUnreviewedHours();
+ }
+
+ // ── Projects ──
+
+ @UseGuards(ReviewerGuard)
+ @Get('projects')
+ listProjects(@Req() req: Request) {
+ const isSuperAdmin = (req as any).user?.perms === 'Super Admin';
+ return this.adminService.listAllProjects(isSuperAdmin);
+ }
+
+ @UseGuards(ReviewerGuard)
+ @Get('projects/:id/hackatime')
+ getProjectHackatime(
+ @Param('id', ParseUUIDPipe) id: string,
+ @Req() req: Request,
+ ) {
+ const isSuperAdmin = (req as any).user?.perms === 'Super Admin';
+ return this.adminService.getProjectHackatime(id, isSuperAdmin);
+ }
+
+ @UseGuards(ReviewerGuard)
+ @Post('projects/:id/review')
+ async reviewProject(
+ @Param('id', ParseUUIDPipe) id: string,
+ @Body() body: {
+ status?: string;
+ feedback?: string;
+ internalNote?: string;
+ overrideJustification?: string;
+ overrideHours?: number;
+ internalHours?: number;
+ },
+ @Req() req: Request,
+ ) {
+ const validStatuses = ['approved', 'changes_needed', 'ban'];
+ if (!body.status || !validStatuses.includes(body.status)) {
+ throw new BadRequestException(`status must be one of: ${validStatuses.join(', ')}`);
+ }
+
+ const reviewer = (req as any).user;
+ const reviewerId = reviewer?.uid;
+ const isSuperAdmin = reviewer?.perms === 'Super Admin';
+ const canBan = isSuperAdmin || reviewer?.perms === 'Fraud Reviewer';
+
+ if (body.status === 'ban' && !canBan) {
+ throw new BadRequestException('Only Super Admins and Fraud Reviewers can ban users. Flag this project in your internal note and ping Euan.');
+ }
+
+ const HOURS_CAP = 500;
+ for (const [field, value] of [
+ ['overrideHours', body.overrideHours] as const,
+ ['internalHours', body.internalHours] as const,
+ ]) {
+ if (value === undefined || value === null) continue;
+ if (!Number.isFinite(value) || value < 0 || value > HOURS_CAP) {
+ throw new BadRequestException(`${field} must be a finite number between 0 and ${HOURS_CAP}`);
+ }
+ }
+
+ // Reviewer must add their own reasoning beyond the auto-generated template
+ // (~180 chars) — require at least 250 chars on overrideJustification for an
+ // approve action so approvals aren't rubber-stamped. Rejections don't need
+ // a long justification (the feedback field carries the user-facing reason).
+ if (body.status === 'approved') {
+ const justification = (body.overrideJustification ?? '').trim();
+ const JUSTIFICATION_MIN = 250;
+ if (justification.length < JUSTIFICATION_MIN) {
+ throw new BadRequestException(
+ `Override Justification must be at least ${JUSTIFICATION_MIN} characters — please add at least 70 characters of your own reasoning beyond the auto-generated template.`,
+ );
+ }
+ }
+
+ if (body.status === 'ban') {
+ return this.adminService.banAndRejectProject(
+ id,
+ reviewerId,
+ body.feedback ?? null,
+ body.internalNote ?? null,
+ body.overrideJustification ?? null,
+ );
+ }
+
+ return this.adminService.reviewProject(
+ id,
+ reviewerId,
+ body.status,
+ body.feedback ?? null,
+ body.internalNote ?? null,
+ body.overrideJustification ?? null,
+ body.overrideHours ?? null,
+ body.internalHours ?? null,
+ );
+ }
+
+ @UseGuards(ReviewerGuard)
+ @Get('projects/:id/reviews')
+ getProjectReviews(@Param('id', ParseUUIDPipe) id: string) {
+ return this.adminService.getProjectReviews(id, true);
+ }
+
+ /** Devlog entries authored by the project owner and linked to this project. */
+ @UseGuards(ReviewerGuard)
+ @Get('projects/:id/devlogs')
+ getProjectDevlogs(@Param('id', ParseUUIDPipe) id: string) {
+ return this.devlogsService.findByProject(id);
+ }
+
+ @UseGuards(ReviewerGuard)
+ @Get('review-leaderboard')
+ getReviewLeaderboard(@Req() req: Request) {
+ const query = (req as any).query ?? {};
+ const win = (query.window as string) ?? '7d';
+ const validWindows = ['24h', '7d', '30d', 'all'];
+ if (!validWindows.includes(win)) {
+ throw new BadRequestException(`window must be one of: ${validWindows.join(', ')}`);
+ }
+ return this.adminService.getReviewLeaderboard(win as '24h' | '7d' | '30d' | 'all');
+ }
+
+ // ── Admin audit queue ──
+ // Projects parked in 'fraud_pending' after first-reviewer approval are queued
+ // here for a second-pass audit before pipes are paid out and the project syncs
+ // to Airtable. Open to Super Admin and Fraud Reviewer.
+
+ @UseGuards(FraudReviewerGuard)
+ @Get('audit/queue')
+ auditQueue() {
+ return this.auditService.listQueue();
+ }
+
+ // Super-admin-only escape hatch: when the audit queue is empty, pull up to
+ // 10 oldest unreviewed projects in as one-shot reviews (skips first-pass).
+ @UseGuards(SuperAdminGuard)
+ @Post('audit/load-unreviewed')
+ async auditLoadUnreviewed(@Req() req: Request) {
+ const superAdminId = (req as any).user?.uid;
+ return this.auditService.loadUnreviewedIntoQueue(superAdminId);
+ }
+
+ @UseGuards(FraudReviewerGuard)
+ @Post('audit/:id/decision')
+ async auditDecision(
+ @Param('id', ParseUUIDPipe) id: string,
+ @Body()
+ body: {
+ action?: string;
+ overrideHours?: number | null;
+ internalHours?: number | null;
+ justification?: string | null;
+ reviewerFeedback?: string | null;
+ userFeedback?: string | null;
+ },
+ @Req() req: Request,
+ ) {
+ const validActions = ['approve', 'rereview', 'reject', 'ban'];
+ if (!body.action || !validActions.includes(body.action)) {
+ throw new BadRequestException(
+ `action must be one of: ${validActions.join(', ')}`,
+ );
+ }
+ const reviewer = (req as any).user;
+ const auditorId = reviewer?.uid;
+ const isSuperAdmin = reviewer?.perms === 'Super Admin';
+ if (body.action === 'ban' && !isSuperAdmin) {
+ throw new BadRequestException(
+ 'Only Super Admins can ban from the audit panel.',
+ );
+ }
+ return this.auditService.decide(id, auditorId, {
+ action: body.action as AuditAction,
+ overrideHours: body.overrideHours ?? null,
+ internalHours: body.internalHours ?? null,
+ justification: body.justification ?? null,
+ reviewerFeedback: body.reviewerFeedback ?? null,
+ userFeedback: body.userFeedback ?? null,
+ isSuperAdmin,
+ });
+ }
+
+ @UseGuards(FraudReviewerGuard)
+ @Get('audit/:id/activity')
+ async auditActivity(
+ @Param('id', ParseUUIDPipe) id: string,
+ @Res() res: Response,
+ ) {
+ res.setHeader('Content-Type', 'application/x-ndjson; charset=utf-8');
+ res.setHeader('Cache-Control', 'no-cache');
+ res.setHeader('X-Accel-Buffering', 'no');
+ try {
+ for await (const evt of this.auditService.streamActivityEvents(id)) {
+ res.write(JSON.stringify(evt) + '\n');
+ // flush eagerly so the client sees per-day progress
+ (res as any).flush?.();
+ }
+ } catch {
+ res.write(JSON.stringify({ type: 'error', error: 'stream-failed' }) + '\n');
+ } finally {
+ res.end();
+ }
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Post('projects/:id/resync-airtable')
+ async resyncAirtable(
+ @Param('id', ParseUUIDPipe) id: string,
+ @Req() req: Request,
+ ) {
+ const reviewerId = (req as any).user?.uid;
+ return this.adminService.resyncProjectToAirtable(id, reviewerId);
+ }
+
+ // ── News CRUD ──
+
+ @UseGuards(SuperAdminGuard)
+ @Get('news')
+ listNews() {
+ return this.adminService.listNews();
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Post('news')
+ async createNews(@Body() body: { text?: string; displayDate?: string }) {
+ if (!body.text || !body.displayDate) {
+ throw new BadRequestException('text and displayDate are required');
+ }
+ return this.adminService.createNews(body.text, body.displayDate);
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Patch('news/:id')
+ async updateNews(
+ @Param('id', ParseUUIDPipe) id: string,
+ @Body() body: { text?: string; displayDate?: string },
+ ) {
+ return this.adminService.updateNews(id, body);
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Delete('news/:id')
+ async deleteNews(@Param('id', ParseUUIDPipe) id: string) {
+ await this.adminService.deleteNews(id);
+ return { success: true };
+ }
+
+ // ── Shop CRUD ──
+
+ @UseGuards(SuperAdminGuard)
+ @Get('shop')
+ listShopItems() {
+ return this.adminService.listShopItems();
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Post('shop')
+ async createShopItem(@Body() body: {
+ name?: string;
+ description?: string;
+ detailedDescription?: string | null;
+ imageUrl?: string;
+ priceHours?: number;
+ stock?: number | null;
+ estimatedShip?: string | null;
+ isActive?: boolean;
+ isFeatured?: boolean;
+ }) {
+ if (!body.name || !body.description || !body.imageUrl || body.priceHours == null) {
+ throw new BadRequestException('name, description, imageUrl, and priceHours are required');
+ }
+ if (!Number.isInteger(body.priceHours) || body.priceHours < 1) {
+ throw new BadRequestException('priceHours must be a positive integer');
+ }
+ if (body.stock !== undefined && body.stock !== null) {
+ if (!Number.isInteger(body.stock) || body.stock < 0) {
+ throw new BadRequestException('stock must be a non-negative integer or null');
+ }
+ }
+ return this.adminService.createShopItem({
+ name: body.name,
+ description: body.description,
+ detailedDescription: body.detailedDescription,
+ imageUrl: body.imageUrl,
+ priceHours: body.priceHours,
+ stock: body.stock,
+ estimatedShip: body.estimatedShip,
+ isActive: body.isActive,
+ isFeatured: body.isFeatured,
+ });
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Patch('shop/reorder')
+ async reorderShopItems(@Body() body: { items?: { id: string; sortOrder: number }[] }) {
+ if (!Array.isArray(body.items) || body.items.length === 0) {
+ throw new BadRequestException('items array is required');
+ }
+ for (const item of body.items) {
+ if (!item.id || typeof item.sortOrder !== 'number' || !Number.isInteger(item.sortOrder) || item.sortOrder < 0) {
+ throw new BadRequestException('each item must have a valid id and non-negative integer sortOrder');
+ }
+ }
+ await this.adminService.reorderShopItems(body.items);
+ return { success: true };
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Patch('shop/:id')
+ async updateShopItem(
+ @Param('id', ParseUUIDPipe) id: string,
+ @Body() body: {
+ name?: string;
+ description?: string;
+ detailedDescription?: string | null;
+ imageUrl?: string;
+ priceHours?: number;
+ stock?: number | null;
+ estimatedShip?: string | null;
+ isActive?: boolean;
+ isFeatured?: boolean;
+ },
+ ) {
+ if (body.priceHours !== undefined) {
+ if (!Number.isInteger(body.priceHours) || body.priceHours < 1) {
+ throw new BadRequestException('priceHours must be a positive integer');
+ }
+ }
+ if (body.stock !== undefined && body.stock !== null) {
+ if (!Number.isInteger(body.stock) || body.stock < 0) {
+ throw new BadRequestException('stock must be a non-negative integer or null');
+ }
+ }
+ return this.adminService.updateShopItem(id, body);
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Delete('shop/:id')
+ async deleteShopItem(@Param('id', ParseUUIDPipe) id: string) {
+ await this.adminService.deleteShopItem(id);
+ return { success: true };
+ }
+
+ // ── Orders / Fulfillment ──
+
+ @UseGuards(SuperAdminGuard)
+ @Get('orders')
+ listOrders(@Req() req: Request) {
+ const query = (req as any).query ?? {};
+ return this.shopService.listAllOrders({
+ shopItemId: query.shopItemId,
+ status: query.status,
+ sortBy: query.sortBy,
+ });
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Get('orders/:id/detail')
+ async getOrderDetail(@Param('id', ParseUUIDPipe) id: string) {
+ return this.adminService.getOrderDetailForFulfillment(id);
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Post('orders/:id/fulfill')
+ async fulfillOrder(@Param('id', ParseUUIDPipe) id: string) {
+ return this.shopService.fulfillOrder(id);
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Post('orders/:id/refund')
+ async refundOrder(
+ @Param('id', ParseUUIDPipe) id: string,
+ @Req() req: Request,
+ ) {
+ const adminId = (req as any).user?.uid;
+ return this.shopService.refundOrder(id, { adminId });
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Post('orders/:id/merge')
+ async mergeOrder(
+ @Param('id', ParseUUIDPipe) id: string,
+ @Req() req: Request,
+ ) {
+ const adminId = (req as any).user?.uid;
+ return this.shopService.mergeOrders(id, adminId);
+ }
+
+ @UseGuards(SuperAdminGuard)
+ @Post('orders/:id/message')
+ async sendFulfillmentMessage(
+ @Param('id', ParseUUIDPipe) id: string,
+ @Body() body: { message?: string },
+ ) {
+ if (!body.message || typeof body.message !== 'string') {
+ throw new BadRequestException('message is required');
+ }
+ return this.shopService.sendFulfillmentMessage(id, body.message);
+ }
+}
diff --git a/backend/src/admin/admin.module.ts b/backend/src/admin/admin.module.ts
new file mode 100644
index 0000000..87fc6f2
--- /dev/null
+++ b/backend/src/admin/admin.module.ts
@@ -0,0 +1,42 @@
+import { Module } from '@nestjs/common';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { AuthModule } from '../auth/auth.module';
+import { RsvpModule } from '../rsvp/rsvp.module';
+import { AuditLogModule } from '../audit-log/audit-log.module';
+import { User } from '../entities/user.entity';
+import { Session } from '../entities/session.entity';
+import { Project } from '../entities/project.entity';
+import { AuditLog } from '../entities/audit-log.entity';
+import { NewsItem } from '../entities/news-item.entity';
+import { ProjectReview } from '../entities/project-review.entity';
+import { ShopItem } from '../entities/shop-item.entity';
+import { Order } from '../entities/order.entity';
+import { Submission } from '../entities/submission.entity';
+import { ShopModule } from '../shop/shop.module';
+import { HcaModule } from '../hca/hca.module';
+import { DevlogsModule } from '../devlogs/devlogs.module';
+import { FraudReviewModule } from '../fraud-review/fraud-review.module';
+import { ProjectAirtableSyncModule } from '../projects/project-airtable-sync.module';
+import { AdminController } from './admin.controller';
+import { AdminService } from './admin.service';
+import { AuditService } from './audit.service';
+import { SuperAdminGuard } from './super-admin.guard';
+import { ReviewerGuard } from './reviewer.guard';
+import { FraudReviewerGuard } from './fraud-reviewer.guard';
+
+@Module({
+ imports: [
+ TypeOrmModule.forFeature([User, Session, Project, AuditLog, NewsItem, ProjectReview, ShopItem, Order, Submission]),
+ AuthModule,
+ RsvpModule,
+ AuditLogModule,
+ ShopModule,
+ HcaModule,
+ DevlogsModule,
+ FraudReviewModule,
+ ProjectAirtableSyncModule,
+ ],
+ controllers: [AdminController],
+ providers: [AdminService, AuditService, SuperAdminGuard, ReviewerGuard, FraudReviewerGuard],
+})
+export class AdminModule {}
diff --git a/backend/src/admin/admin.service.ts b/backend/src/admin/admin.service.ts
new file mode 100644
index 0000000..1d84797
--- /dev/null
+++ b/backend/src/admin/admin.service.ts
@@ -0,0 +1,1667 @@
+import {
+ Injectable,
+ BadRequestException,
+ Logger,
+ NotFoundException,
+} from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
+import { InjectRepository } from '@nestjs/typeorm';
+import { Repository } from 'typeorm';
+import { User } from '../entities/user.entity';
+import { Session } from '../entities/session.entity';
+import { Project } from '../entities/project.entity';
+import { AuditLog } from '../entities/audit-log.entity';
+import { NewsItem } from '../entities/news-item.entity';
+import { ProjectReview } from '../entities/project-review.entity';
+import { ShopItem } from '../entities/shop-item.entity';
+import { Order } from '../entities/order.entity';
+import { Submission } from '../entities/submission.entity';
+import { RsvpService } from '../rsvp/rsvp.service';
+import { AuditLogService } from '../audit-log/audit-log.service';
+import { HcaService } from '../hca/hca.service';
+import { fetchWithTimeout } from '../fetch.util';
+import { ProjectAirtableSyncService } from '../projects/project-airtable-sync.service';
+
+const VALID_PERMS = [
+ 'User',
+ 'Helper',
+ 'Reviewer',
+ 'Fraud Reviewer',
+ 'Super Admin',
+ 'Banned',
+] as const;
+
+@Injectable()
+export class AdminService {
+ private readonly logger = new Logger(AdminService.name);
+ private readonly hackatimeBaseUrl: string;
+ private readonly hackatimeAdminKey: string | undefined;
+
+ // DAU cache (5-minute TTL)
+ private dauCache: { count: number; timestamp: number } | null = null;
+ private readonly DAU_CACHE_TTL = 5 * 60 * 1000;
+
+ // Unreviewed-hours cache (60-second TTL) — each refresh fans out to one
+ // Hackatime /spans request per (unreviewed project × linked HT name), so
+ // back-to-back stats-page loads must not multiply that fan-out.
+ private unreviewedHoursCache: {
+ payload: {
+ totalHours: number;
+ projectCount: number;
+ approvalRate: number;
+ decisionCount: number;
+ predictedApprovedHours: number;
+ };
+ timestamp: number;
+ } | null = null;
+ private readonly UNREVIEWED_HOURS_CACHE_TTL = 60 * 1000;
+
+ // Signups history cache (10-minute TTL) — Airtable call is moderately expensive
+ private signupsCache: {
+ payload: { daily: { date: string; count: number }[]; cumulative: { date: string; count: number }[]; total: number };
+ timestamp: number;
+ } | null = null;
+ private readonly SIGNUPS_CACHE_TTL = 10 * 60 * 1000;
+
+ // DAU history: cache of finalised per-day counts (YYYY-MM-DD → count).
+ // Entries are only written for dates that are fully in the past (UTC),
+ // so they never need invalidation.
+ private readonly dauHistoryCache = new Map();
+ private dauHistoryInflight: Promise | null = null;
+ private static readonly DAU_HISTORY_START = '2026-04-03';
+ // Beest event start. Hackatime hours logged before this date should not
+ // count toward project review totals (admin /user/projects returns lifetime
+ // total_duration with no date filter, so we reconstruct from spans).
+ private static readonly HACKATIME_EVENT_START = '2026-04-02';
+
+ constructor(
+ private readonly configService: ConfigService,
+ @InjectRepository(User) private readonly userRepo: Repository,
+ @InjectRepository(Session) private readonly sessionRepo: Repository,
+ @InjectRepository(Project) private readonly projectRepo: Repository,
+ @InjectRepository(AuditLog) private readonly auditLogRepo: Repository,
+ @InjectRepository(NewsItem) private readonly newsRepo: Repository,
+ @InjectRepository(ProjectReview) private readonly reviewRepo: Repository,
+ @InjectRepository(ShopItem) private readonly shopRepo: Repository,
+ @InjectRepository(Order) private readonly orderRepo: Repository,
+ @InjectRepository(Submission) private readonly submissionRepo: Repository,
+ private readonly rsvpService: RsvpService,
+ private readonly auditLogService: AuditLogService,
+ private readonly hcaService: HcaService,
+ private readonly airtableSync: ProjectAirtableSyncService,
+ ) {
+ this.hackatimeBaseUrl = this.configService.get(
+ 'HACKATIME_BASE_URL',
+ 'https://hackatime.hackclub.com',
+ );
+ this.hackatimeAdminKey = this.configService.get('HACKATIME_ADMIN_API_KEY');
+ if (!this.hackatimeAdminKey) {
+ this.logger.warn('HACKATIME_ADMIN_API_KEY not set — admin Hackatime lookups disabled');
+ }
+ }
+
+ async listUsers(): Promise {
+ const [users, permsMap] = await Promise.all([
+ this.userRepo.find({ order: { createdAt: 'DESC' } }),
+ this.rsvpService.getAllPerms(),
+ ]);
+ return users.map((u) => ({
+ id: u.id,
+ hcaSub: u.hcaSub,
+ name: u.name,
+ nickname: u.nickname,
+ slackId: u.slackId,
+ email: u.email,
+ hackatimeConnected: !!u.hackatimeToken,
+ perms: (u.email ? permsMap.get(u.email.toLowerCase()) : null) ?? null,
+ createdAt: u.createdAt,
+ }));
+ }
+
+ async getUser(userId: string) {
+ const user = await this.userRepo.findOne({
+ where: { id: userId },
+ });
+ if (!user) throw new NotFoundException('User not found');
+
+ const projects = await this.projectRepo.find({
+ where: { userId },
+ order: { createdAt: 'DESC' },
+ select: ['id', 'name', 'status', 'projectType', 'createdAt'],
+ });
+
+ const orders = await this.orderRepo.find({
+ where: { userId },
+ order: { createdAt: 'DESC' },
+ select: ['id', 'itemName', 'quantity', 'pipesSpent', 'status', 'createdAt'],
+ });
+
+ const sessions = await this.sessionRepo.count({ where: { userId } });
+
+ const auditLogs = await this.auditLogRepo.find({
+ where: { userId },
+ order: { createdAt: 'DESC' },
+ take: 50,
+ select: ['id', 'action', 'label', 'createdAt'],
+ });
+
+ let perms: string | null = null;
+ try {
+ if (user.email) {
+ perms = await this.rsvpService.getPerms(user.email);
+ }
+ } catch {
+ // Airtable lookup failed — don't block the response
+ }
+
+ return {
+ id: user.id,
+ hcaSub: user.hcaSub,
+ name: user.name,
+ nickname: user.nickname,
+ slackId: user.slackId,
+ email: user.email,
+ hackatimeConnected: !!user.hackatimeToken,
+ twoEmails: user.twoEmails,
+ createdAt: user.createdAt,
+ updatedAt: user.updatedAt,
+ pipes: user.pipes ?? 0,
+ perms,
+ projects,
+ orders,
+ activeSessions: sessions,
+ auditLogs,
+ };
+ }
+
+ async banUser(userId: string, adminId?: string): Promise {
+ const user = await this.userRepo.findOne({ where: { id: userId } });
+ if (!user) throw new NotFoundException('User not found');
+
+ // 1. Update Airtable perms to Banned
+ await this.rsvpService.updatePerms(user.email, 'Banned');
+
+ // 2. Revoke all sessions for this user
+ await this.sessionRepo.delete({ userId });
+
+ // 3. Audit log on the banned user's record
+ const identifier = user.name || user.slackId || user.hcaSub;
+ await this.auditLogService.log(userId, 'admin_ban', `Banned user ${identifier}`);
+
+ // 4. Audit log on the admin's record
+ if (adminId) {
+ await this.auditLogService.log(adminId, 'admin_ban', `Banned user ${identifier}`);
+ }
+ }
+
+ async banAndRejectProject(
+ projectId: string,
+ reviewerId: string,
+ feedback: string | null,
+ internalNote: string | null,
+ overrideJustification: string | null,
+ ) {
+ const project = await this.projectRepo.findOne({
+ where: { id: projectId },
+ relations: ['user'],
+ });
+ if (!project) throw new NotFoundException('Project not found');
+ if (project.userId === reviewerId) {
+ throw new BadRequestException('You cannot review your own project');
+ }
+
+ // 1. Reject the project
+ project.status = 'changes_needed';
+ await this.projectRepo.save(project);
+
+ // 2. Save review record
+ const review = this.reviewRepo.create({
+ projectId,
+ reviewerId,
+ status: 'ban',
+ feedback: feedback || null,
+ internalNote: internalNote || null,
+ overrideJustification: overrideJustification || null,
+ });
+ await this.reviewRepo.save(review);
+
+ // 3. Ban the user
+ await this.banUser(project.userId);
+
+ // 4. Audit logs
+ await this.auditLogService.log(project.userId, 'project_reviewed', `Project "${project.name}" was rejected`);
+ await this.auditLogService.log(project.userId, 'admin_ban', `Banned via project review of "${project.name}"`);
+
+ return { success: true };
+ }
+
+ async adjustPipes(
+ userId: string,
+ delta: number,
+ reason: string | null,
+ adminId?: string,
+ ): Promise<{ pipes: number }> {
+ if (!Number.isInteger(delta) || delta === 0) {
+ throw new BadRequestException('delta must be a non-zero integer');
+ }
+
+ const user = await this.userRepo.findOne({ where: { id: userId } });
+ if (!user) throw new NotFoundException('User not found');
+
+ const current = user.pipes ?? 0;
+ const next = current + delta;
+ if (next < 0) {
+ throw new BadRequestException(
+ `Cannot revoke ${-delta} pipes — user only has ${current}`,
+ );
+ }
+
+ if (delta > 0) {
+ await this.userRepo.increment({ id: userId }, 'pipes', delta);
+ } else {
+ await this.userRepo.decrement({ id: userId }, 'pipes', -delta);
+ }
+
+ const identifier = user.name || user.slackId || user.hcaSub;
+ const verb = delta > 0 ? 'Granted' : 'Revoked';
+ const reasonSuffix = reason ? ` — ${reason}` : '';
+ const label = `${verb} ${Math.abs(delta)} pipes (${identifier}, ${current} → ${next})${reasonSuffix}`;
+ await this.auditLogService.log(userId, 'admin_pipes_adjust', label);
+ if (adminId) {
+ await this.auditLogService.log(adminId, 'admin_pipes_adjust', label);
+ }
+
+ return { pipes: next };
+ }
+
+ async updatePerms(userId: string, perms: string, adminId?: string): Promise {
+ if (!VALID_PERMS.includes(perms as any)) {
+ throw new BadRequestException(
+ `Invalid perms value. Must be one of: ${VALID_PERMS.join(', ')}`,
+ );
+ }
+
+ const user = await this.userRepo.findOne({ where: { id: userId } });
+ if (!user) throw new NotFoundException('User not found');
+
+ await this.rsvpService.updatePerms(user.email, perms);
+
+ const identifier = user.name || user.slackId || user.hcaSub;
+ await this.auditLogService.log(userId, 'admin_perms_change', `Changed ${identifier} perms to ${perms}`);
+
+ if (adminId) {
+ await this.auditLogService.log(adminId, 'admin_perms_change', `Changed ${identifier} perms to ${perms}`);
+ }
+ }
+
+ // ── Projects ──
+
+ async listAllProjects(isSuperAdmin: boolean) {
+ const projects = await this.projectRepo.find({
+ order: { createdAt: 'DESC' },
+ relations: ['user'],
+ });
+
+ const statusCounts = {
+ unshipped: 0,
+ unreviewed: 0,
+ fraud_pending: 0,
+ changes_needed: 0,
+ approved: 0,
+ };
+
+ // Fetch latest submission for each project in a single query
+ const latestSubmissions = await this.submissionRepo
+ .createQueryBuilder('s')
+ .distinctOn(['s.project_id'])
+ .orderBy('s.project_id')
+ .addOrderBy('s.created_at', 'DESC')
+ .getMany();
+ const submissionMap = new Map(latestSubmissions.map((s) => [s.projectId, s]));
+
+ const mapped = projects
+ .filter((p) => isSuperAdmin || p.status !== 'unshipped')
+ .map((p) => {
+ if (p.status in statusCounts) {
+ statusCounts[p.status as keyof typeof statusCounts]++;
+ }
+ const latestSub = submissionMap.get(p.id);
+ return {
+ id: p.id,
+ name: p.name,
+ description: p.description,
+ projectType: p.projectType,
+ status: p.status,
+ codeUrl: p.codeUrl,
+ demoUrl: p.demoUrl,
+ readmeUrl: p.readmeUrl,
+ screenshot1Url: p.screenshot1Url,
+ screenshot2Url: p.screenshot2Url,
+ hackatimeProjectName: p.hackatimeProjectName,
+ isUpdate: p.isUpdate,
+ otherHcProgram: p.otherHcProgram,
+ aiUse: p.aiUse,
+ createdAt: p.createdAt,
+ updatedAt: p.updatedAt,
+ user: {
+ id: p.user?.id,
+ name: isSuperAdmin ? p.user?.name : null,
+ slackId: p.user?.slackId,
+ },
+ latestSubmission: latestSub ? {
+ id: latestSub.id,
+ changeDescription: latestSub.changeDescription,
+ minHoursConfirmed: latestSub.minHoursConfirmed,
+ reviewerNote: latestSub.reviewerNote,
+ status: latestSub.status,
+ createdAt: latestSub.createdAt,
+ } : null,
+ };
+ });
+
+ return { statusCounts, projects: mapped };
+ }
+
+ async reviewProject(
+ projectId: string,
+ reviewerId: string,
+ status: string,
+ feedback: string | null,
+ internalNote: string | null,
+ overrideJustification: string | null,
+ overrideHours: number | null,
+ internalHours: number | null,
+ ) {
+ const project = await this.projectRepo.findOne({
+ where: { id: projectId },
+ relations: ['user'],
+ });
+ if (!project) throw new NotFoundException('Project not found');
+ if (project.userId === reviewerId) {
+ throw new BadRequestException('You cannot review your own project');
+ }
+
+ const previousStatus = project.status;
+ const previousOverrideHours = project.overrideHours;
+
+ // Block re-reviewing an already-approved project. Reviewers must send to
+ // "changes needed" first (which claws back pipes and zeroes hours) before
+ // re-approving. This prevents:
+ // - duplicate Airtable rows on accidental double-approve clicks
+ // - inflate-on-re-approve where a reviewer raises overrideHours on a
+ // project that's already paid out, granting extra pipes
+ if (previousStatus === 'approved' && status === 'approved') {
+ throw new BadRequestException(
+ 'Project is already approved. Send to "changes needed" first if you need to re-review.',
+ );
+ }
+ // Block re-approving while still waiting on the fraud-review verdict.
+ if (previousStatus === 'fraud_pending' && status === 'approved') {
+ throw new BadRequestException(
+ 'Project is already awaiting fraud review. Wait for the verdict before re-reviewing.',
+ );
+ }
+
+ // Find the latest unreviewed submission for this project
+ const submission = await this.submissionRepo.findOne({
+ where: { projectId, status: 'unreviewed' },
+ order: { createdAt: 'DESC' },
+ });
+
+ // 1. Update project status and hours.
+ //
+ // On approval, the reviewer's submitted overrideHours/internalHours are the
+ // DELTA for THIS submission — new work since the last approval — and are
+ // ADDED on top of the project's existing approved hours, never overwriting.
+ // For initial ships project.overrideHours is 0, so the delta becomes the
+ // cumulative; for reships the delta accumulates on top of prior approvals.
+ // After approved → changes_needed (which wipes hours/claws back pipes), the
+ // project is back at 0, so a follow-up approval starts fresh from the delta.
+ //
+ // When the reviewer approves, the project does NOT go straight to 'approved'.
+ // It moves to 'fraud_pending' first — the joe.fraud first-pass review must
+ // clear before pipes are granted and the project syncs to Airtable. The
+ // background poller in FraudReviewService observes the verdict and either
+ // finalises the approval or marks the project changes_needed with a
+ // generic user-facing message.
+ project.status = status === 'approved' ? 'fraud_pending' : status;
+ if (status === 'approved') {
+ if (overrideHours !== null && overrideHours !== undefined) {
+ const delta = Math.round(overrideHours * 10) / 10;
+ project.overrideHours = Math.round(((project.overrideHours ?? 0) + delta) * 10) / 10;
+ }
+ if (internalHours !== null && internalHours !== undefined) {
+ const internalDelta = Math.round(internalHours * 10) / 10;
+ project.internalHours = Math.round(((project.internalHours ?? 0) + internalDelta) * 10) / 10;
+ }
+ }
+
+ // Hackatime cap: reviewer cannot approve more new hours than the user has
+ // actually logged in Hackatime since the last approval (with a 0.5h buffer
+ // for rounding). This refetches Hackatime server-side at approval time so a
+ // tampered request body can't bypass it.
+ if (
+ status === 'approved' &&
+ overrideHours !== null &&
+ overrideHours !== undefined &&
+ overrideHours > 0
+ ) {
+ try {
+ const ht = await this.getProjectHackatime(projectId, false);
+ const currentHackatime = ht?.totalHours ?? 0;
+ const previousProjectHours = previousOverrideHours ?? 0;
+ const allowedDelta = currentHackatime - previousProjectHours + 0.5;
+ const submittedDelta = Math.round(overrideHours * 10) / 10;
+ if (submittedDelta > allowedDelta) {
+ const hackatimeDelta = Math.round((currentHackatime - previousProjectHours) * 10) / 10;
+ throw new BadRequestException(
+ `Cannot approve ${submittedDelta}h of new work — Hackatime shows only ${hackatimeDelta}h of new time since last approval. Reduce the approved hours, or send to "changes needed" if the hours look wrong.`,
+ );
+ }
+ } catch (e) {
+ if (e instanceof BadRequestException) throw e;
+ // Hackatime fetch failed for an unrelated reason — log and proceed,
+ // rather than blocking reviews on Hackatime outages.
+ this.logger.warn(`Hackatime cap check failed for project ${projectId}: ${e}`);
+ }
+ }
+
+ // project.overrideHours is the CUMULATIVE total approved hours for this
+ // project (sum of submission deltas across all approved ships). Validate:
+ // - status=approved + finalHours <= 0 silently zeroes pipes_granted and,
+ // because the bar suppresses overflow on approved projects, makes the
+ // user's hours appear to vanish (moaz, 2026-05-05).
+ // - status=approved + finalHours < pipes_granted desyncs the bar from the
+ // pipes already paid out (sadrita, 2026-04-29).
+ // To genuinely reduce a project's hours, route through changes_needed first
+ // (which claws back pipes), then re-approve.
+ if (status === 'approved') {
+ const finalHours = project.overrideHours ?? 0;
+ if (finalHours <= 0) {
+ throw new BadRequestException(
+ 'Cannot approve a project at 0 hours. Enter a positive delta of new hours, or use "changes needed" to reject without granting pipes.',
+ );
+ }
+ if (finalHours < (project.pipesGranted ?? 0)) {
+ throw new BadRequestException(
+ `Cannot reduce approved hours to ${finalHours} — ${project.pipesGranted} pipes have already been granted on this project. Send to "changes needed" first to claw back pipes.`,
+ );
+ }
+ }
+
+ await this.projectRepo.save(project);
+
+ // 2a. Handle rejection.
+ // - Direct approved → changes_needed: wipe overrideHours and claw back pipes (the approval is being revoked).
+ // - unreviewed → changes_needed with prior approval (pipesGranted > 0): preserve the prior approval's
+ // hours and pipes — only the new resubmission is being rejected, the original approval still stands.
+ // - Any other path into changes_needed (never approved): clear overrideHours so a reviewer-set value
+ // on the rejection doesn't bleed into the approved bucket on the next resubmission.
+ if (status === 'changes_needed') {
+ if (previousStatus === 'approved') {
+ project.overrideHours = 0;
+ if ((project.pipesGranted ?? 0) > 0) {
+ const clawback = project.pipesGranted!;
+ await this.userRepo.decrement({ id: project.userId }, 'pipes', clawback);
+ project.pipesGranted = 0;
+ this.logger.warn(`Clawed back ${clawback} pipes from user ${project.userId} for project ${project.id}`);
+ }
+ await this.projectRepo.save(project);
+ } else if (previousStatus === 'unreviewed' && (project.pipesGranted ?? 0) > 0) {
+ project.overrideHours = previousOverrideHours;
+ await this.projectRepo.save(project);
+ } else {
+ project.overrideHours = 0;
+ await this.projectRepo.save(project);
+ }
+ }
+
+ // 2b. Pipe granting on the approved path is DEFERRED to the fraud-review
+ // poller (FraudReviewService.completeApproval). Clawback on the
+ // changes_needed path was already handled in section 2a above.
+
+ // 3. Update the submission status and hours.
+ // On the approved path the submission stays at 'unreviewed' until the
+ // fraud poller flips it to 'approved' (or to 'changes_needed' on
+ // fraud-rejection). Only update here for the non-approved paths.
+ if (submission) {
+ if (overrideHours !== null && overrideHours !== undefined) {
+ submission.overrideHours = Math.round(overrideHours * 10) / 10;
+ }
+ if (internalHours !== null && internalHours !== undefined) {
+ submission.internalHours = Math.round(internalHours * 10) / 10;
+ }
+ if (status !== 'approved') {
+ submission.status = status;
+ }
+ await this.submissionRepo.save(submission);
+ }
+
+ // 4. Save the review record (linked to submission if one exists)
+ const review = this.reviewRepo.create({
+ projectId,
+ reviewerId,
+ submissionId: submission?.id ?? null,
+ status,
+ feedback: feedback || null,
+ internalNote: internalNote || null,
+ overrideJustification: overrideJustification || null,
+ });
+ await this.reviewRepo.save(review);
+
+ // 5. Audit log to the project owner (not the reviewer)
+ const label =
+ status === 'approved'
+ ? `Project "${project.name}" was approved by reviewer`
+ : `Project "${project.name}" received feedback`;
+ await this.auditLogService.log(project.userId, 'project_reviewed', label);
+
+ return { success: true };
+ }
+
+ async resyncProjectToAirtable(projectId: string, reviewerId: string) {
+ const project = await this.projectRepo.findOne({
+ where: { id: projectId },
+ relations: ['user'],
+ });
+ if (!project) throw new NotFoundException('Project not found');
+ if (project.status !== 'approved') {
+ throw new BadRequestException('Only approved projects can be re-pushed to Airtable');
+ }
+
+ // Find the latest review and latest approved submission for this project to
+ // include override justification and per-ship internal hours
+ const latestReview = await this.reviewRepo.findOne({
+ where: { projectId },
+ order: { createdAt: 'DESC' },
+ });
+ const latestApprovedSub = await this.submissionRepo.findOne({
+ where: { projectId, status: 'approved' },
+ order: { createdAt: 'DESC' },
+ });
+
+ // Re-sync the funnel date fields
+ if (project.user?.email) {
+ this.rsvpService.updateDateField(project.user.email, 'Loops - beestApprovedProject');
+ }
+
+ // Re-push the full project record to Airtable Projects table
+ try {
+ await this.airtableSync.syncApprovedProject(
+ project,
+ latestReview?.overrideJustification ?? null,
+ latestApprovedSub ?? null,
+ );
+ } catch (err) {
+ this.logger.error(`Airtable resync failed for project ${projectId}: ${err}`);
+ throw new BadRequestException('Failed to push project to Airtable — check server logs');
+ }
+
+ await this.auditLogService.log(
+ reviewerId,
+ 'admin_resync_airtable',
+ `Re-pushed project "${project.name}" to Airtable`,
+ );
+
+ return { success: true };
+ }
+
+ async getReviewLeaderboard(window: '24h' | '7d' | '30d' | 'all') {
+ const windowMs: Record = {
+ '24h': 24 * 60 * 60 * 1000,
+ '7d': 7 * 24 * 60 * 60 * 1000,
+ '30d': 30 * 24 * 60 * 60 * 1000,
+ 'all': null,
+ };
+ const ms = windowMs[window];
+
+ const qb = this.reviewRepo
+ .createQueryBuilder('r')
+ .leftJoin('r.reviewer', 'u')
+ .select('r.reviewer_id', 'reviewerId')
+ .addSelect('u.name', 'reviewerName')
+ .addSelect('u.slack_id', 'reviewerSlackId')
+ .addSelect('COUNT(*)::int', 'total')
+ .addSelect("COUNT(*) FILTER (WHERE r.status = 'approved')::int", 'approved')
+ .addSelect("COUNT(*) FILTER (WHERE r.status = 'changes_needed')::int", 'changesNeeded')
+ .addSelect("COUNT(*) FILTER (WHERE r.status = 'ban')::int", 'banned')
+ .groupBy('r.reviewer_id')
+ .addGroupBy('u.name')
+ .addGroupBy('u.slack_id')
+ .orderBy('total', 'DESC');
+
+ if (ms !== null) {
+ qb.where('r.created_at > :cutoff', { cutoff: new Date(Date.now() - ms) });
+ }
+
+ const rows = await qb.getRawMany();
+ return rows.map((r) => {
+ const total = Number(r.total);
+ const approved = Number(r.approved);
+ return {
+ reviewerId: r.reviewerId,
+ reviewerName: r.reviewerName,
+ reviewerSlackId: r.reviewerSlackId,
+ total,
+ approved,
+ changesNeeded: Number(r.changesNeeded),
+ banned: Number(r.banned),
+ approvalPercent: total > 0 ? Math.round((approved / total) * 100) : 0,
+ };
+ });
+ }
+
+ async getProjectReviews(projectId: string, includeInternal: boolean) {
+ const reviews = await this.reviewRepo.find({
+ where: { projectId },
+ order: { createdAt: 'DESC' },
+ relations: ['reviewer'],
+ });
+
+ return reviews.map((r) => ({
+ id: r.id,
+ status: r.status,
+ feedback: r.feedback,
+ ...(includeInternal ? { internalNote: r.internalNote } : {}),
+ overrideJustification: r.overrideJustification,
+ reviewerName: r.reviewer?.name ?? null,
+ createdAt: r.createdAt,
+ }));
+ }
+
+ // ── Unified Airtable duplicate check ──
+
+ /**
+ * Checks the Unified Airtable "Approved Projects" table for a matching Code URL.
+ *
+ * Security constraints:
+ * - This method is private — only callable within AdminService
+ * - Only called from getProjectHackatime, which is behind SuperAdminGuard
+ * - Only accepts HTTPS URLs (rejects anything else)
+ * - The codeUrl is taken from the project's DB record, never from user input
+ * - Returns only boolean match/error — no Airtable record data is ever exposed
+ */
+ private async checkUnifiedDuplicate(
+ codeUrl: string,
+ ): Promise<{ duplicate: boolean; error: boolean }> {
+ if (!codeUrl) {
+ return { duplicate: false, error: true };
+ }
+
+ // Only allow https:// URLs — reject anything that could be a formula injection
+ try {
+ const parsed = new URL(codeUrl);
+ if (parsed.protocol !== 'https:') {
+ return { duplicate: false, error: true };
+ }
+ } catch {
+ return { duplicate: false, error: true };
+ }
+
+ // Escape for Airtable formula: double any backslashes, then escape single quotes
+ const escaped = codeUrl.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
+ const formula = `{Code URL} = '${escaped}'`;
+
+ try {
+ const params = new URLSearchParams({
+ select: JSON.stringify({
+ filterByFormula: formula,
+ maxRecords: 1,
+ fields: ['Code URL'],
+ }),
+ });
+ const url = `https://api2.hackclub.com/v0.1/Unified%20YSWS%20Projects%20DB/Approved%20Projects?${params.toString()}`;
+ this.logger.log(`Unified check: formula=${formula}`);
+ const res = await fetchWithTimeout(url);
+ if (!res.ok) {
+ const body = await res.text();
+ this.logger.warn(`Unified check failed (${res.status}): ${body}`);
+ return { duplicate: false, error: true };
+ }
+ const records: any[] = await res.json() ?? [];
+ this.logger.log(`Unified check: ${records.length} records found`);
+ // Only expose match/no-match — never leak record contents
+ return { duplicate: records.length > 0, error: false };
+ } catch {
+ return { duplicate: false, error: true };
+ }
+ }
+
+ // ── Hackatime admin lookup ──
+
+ private emptyHackatimeResult(
+ projectId: string,
+ user: User | null,
+ isSuperAdmin: boolean,
+ project?: Project | null,
+ ) {
+ return {
+ projectId,
+ hackatimeProjects: [],
+ categories: [],
+ totalHours: 0,
+ earliestHeartbeat: null,
+ previousApprovedHours: project?.overrideHours ?? 0,
+ previousInternalHours: project?.internalHours ?? 0,
+ trustLevel: null,
+ linkedBanned: false,
+ linkedEmail: null,
+ linkedSlackUid: null,
+ beestEmail: isSuperAdmin ? (user?.email ?? null) : null,
+ beestSlackId: user?.slackId ?? null,
+ emailMismatch: false,
+ unifiedDuplicate: false,
+ unifiedError: true,
+ };
+ }
+
+ private async hackatimeGet(path: string): Promise {
+ return fetchWithTimeout(`${this.hackatimeBaseUrl}${path}`, {
+ headers: { Authorization: `Bearer ${this.hackatimeAdminKey}` },
+ });
+ }
+
+ private async hackatimePost(path: string, body: object): Promise {
+ return fetchWithTimeout(`${this.hackatimeBaseUrl}${path}`, {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${this.hackatimeAdminKey}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(body),
+ });
+ }
+
+ async getProjectHackatime(projectId: string, isSuperAdmin: boolean) {
+ if (!this.hackatimeAdminKey) {
+ throw new BadRequestException('Hackatime admin API key not configured');
+ }
+
+ const project = await this.projectRepo.findOne({
+ where: { id: projectId },
+ relations: ['user'],
+ });
+ if (!project) throw new NotFoundException('Project not found');
+
+ const hackatimeNames: string[] = project.hackatimeProjectName ?? [];
+ const user = project.user;
+ if (!user) {
+ return this.emptyHackatimeResult(projectId, user, isSuperAdmin, project);
+ }
+
+ try {
+ // 1. Resolve Hackatime user ID — prefer stored ID, fall back to email lookup, then OAuth token
+ let hackatimeUserId: string | number | null = user.hackatimeUserId ?? null;
+ if (!hackatimeUserId && user.email) {
+ try {
+ const emailRes = await this.hackatimePost(
+ '/api/admin/v1/user/get_user_by_email',
+ { email: user.email },
+ );
+ if (emailRes.ok) {
+ const emailData = await emailRes.json();
+ hackatimeUserId = emailData.user_id ?? emailData?.data?.user_id ?? null;
+ }
+ } catch (err) {
+ this.logger.warn(`Hackatime email lookup failed for project ${projectId}: ${err}`);
+ }
+ }
+ // Last resort: use the user's own Hackatime OAuth token to resolve their ID
+ if (!hackatimeUserId && user.hackatimeToken) {
+ try {
+ const meRes = await fetchWithTimeout(
+ `${this.hackatimeBaseUrl}/api/v1/authenticated/me`,
+ { headers: { Authorization: `Bearer ${user.hackatimeToken}` } },
+ );
+ if (meRes.ok) {
+ const meData = await meRes.json();
+ const d = meData?.data ?? meData;
+ hackatimeUserId = d?.id?.toString() ?? d?.user_id?.toString() ?? null;
+ // Persist for future lookups
+ if (hackatimeUserId) {
+ user.hackatimeUserId = String(hackatimeUserId);
+ await this.userRepo.save(user);
+ }
+ }
+ } catch (err) {
+ this.logger.warn(`Hackatime OAuth /me fallback failed for project ${projectId}: ${err}`);
+ }
+ }
+ if (!hackatimeUserId) {
+ return this.emptyHackatimeResult(projectId, user, isSuperAdmin, project);
+ }
+
+ // 2. Get user info (trust level), projects, and Unified duplicate check in parallel
+ const [infoRes, projectsRes, unifiedResult] = await Promise.all([
+ this.hackatimeGet(`/api/admin/v1/user/info?user_id=${hackatimeUserId}`),
+ this.hackatimeGet(`/api/admin/v1/user/projects?user_id=${hackatimeUserId}`),
+ this.checkUnifiedDuplicate(project.codeUrl ?? ''),
+ ]);
+
+ const debug: { infoStatus: number; projectsStatus: number; totalProjectsReturned: number; linkedNames: string[]; availableNames: string[] } = {
+ infoStatus: infoRes.status,
+ projectsStatus: projectsRes.status,
+ totalProjectsReturned: 0,
+ linkedNames: hackatimeNames,
+ availableNames: [],
+ };
+
+ let trustLevel: string | null = null;
+ let linkedBanned = false;
+ let linkedEmail: string | null = null;
+ let linkedSlackUid: string | null = null;
+ let emailMismatch = false;
+ if (infoRes.ok) {
+ const infoData = await infoRes.json();
+ const u = infoData?.user ?? infoData?.data ?? infoData ?? {};
+ trustLevel = u?.trust_level ?? u?.trust_factor?.trust_level ?? null;
+ linkedBanned = u?.banned === true;
+ linkedSlackUid = typeof u?.slack_uid === 'string' ? u.slack_uid : null;
+ const rawEmails = u?.email_addresses ?? u?.emails ?? [];
+ if (Array.isArray(rawEmails) && rawEmails.length > 0) {
+ const emails = rawEmails.filter(
+ (e): e is string => typeof e === 'string',
+ );
+ linkedEmail = emails[0] ?? null;
+ if (user.email) {
+ const own = user.email.toLowerCase();
+ emailMismatch = !emails.some((e) => e.toLowerCase() === own);
+ }
+ }
+ }
+
+ let matched: { name: string; hours: number; languages: string[]; firstHeartbeat: number | null }[] = [];
+ let categories: { name: string; totalSeconds: number; percent: number }[] = [];
+ if (projectsRes.ok) {
+ const projData = await projectsRes.json();
+ const allProjects: {
+ name: string;
+ total_duration: number;
+ languages: string[];
+ first_heartbeat?: number | string | null;
+ }[] = projData?.projects ?? projData?.data ?? [];
+
+ debug.totalProjectsReturned = allProjects.length;
+ debug.availableNames = allProjects.map((p) => p.name);
+
+ if (hackatimeNames.length > 0) {
+ const nameSet = new Set(hackatimeNames);
+ const matchedRaw = allProjects.filter((p) => nameSet.has(p.name));
+
+ // Fetch event-window hours from spans per project: the admin
+ // /user/projects total_duration is lifetime, which would surface
+ // pre-event hours to reviewers. end_date is padded by one day so
+ // today's spans are fully included even with timezone edges.
+ const endDatePadded = AdminService.ymdUtc(
+ new Date(Date.now() + 86400_000),
+ );
+ const spansResults = await Promise.allSettled(
+ matchedRaw.map((p) =>
+ this.hackatimeGet(
+ `/api/v1/users/${encodeURIComponent(String(hackatimeUserId))}/heartbeats/spans` +
+ `?start_date=${AdminService.HACKATIME_EVENT_START}&end_date=${endDatePadded}` +
+ `&project=${encodeURIComponent(p.name)}`,
+ ).then(async (r) =>
+ r.ok
+ ? ((await r.json()) as {
+ spans?: { start_time?: number; end_time?: number; duration?: number }[];
+ })
+ : null,
+ ),
+ ),
+ );
+
+ // Pull the Wakatime-compatible categories summary so reviewers can see
+ // how much of the time was "AI Coding" vs regular coding. One stats
+ // call covers all linked projects via filter_by_project.
+ try {
+ const statsRes = await this.hackatimeGet(
+ `/api/v1/users/${encodeURIComponent(String(hackatimeUserId))}/stats` +
+ `?start_date=${AdminService.HACKATIME_EVENT_START}&end_date=${endDatePadded}` +
+ `&filter_by_project=${encodeURIComponent(matchedRaw.map((p) => p.name).join(','))}`,
+ );
+ if (statsRes.ok) {
+ const statsBody = await statsRes.json();
+ const rawCats = statsBody?.data?.categories ?? statsBody?.categories ?? [];
+ if (Array.isArray(rawCats)) {
+ const parsed = rawCats
+ .map((c: { name?: unknown; total_seconds?: unknown }) => {
+ const secs = typeof c?.total_seconds === 'number' ? c.total_seconds : Number(c?.total_seconds);
+ return {
+ name: typeof c?.name === 'string' ? c.name : '',
+ totalSeconds: Number.isFinite(secs) && secs > 0 ? secs : 0,
+ };
+ })
+ .filter((c) => c.name && c.totalSeconds > 0);
+ const sum = parsed.reduce((s, c) => s + c.totalSeconds, 0);
+ if (sum > 0) {
+ categories = parsed
+ .map((c) => ({
+ name: c.name,
+ totalSeconds: c.totalSeconds,
+ percent: Math.round((c.totalSeconds / sum) * 1000) / 10,
+ }))
+ .sort((a, b) => b.percent - a.percent);
+ }
+ }
+ }
+ } catch (err) {
+ this.logger.warn(`Hackatime category stats failed for project ${projectId}: ${err}`);
+ }
+
+ matched = matchedRaw.map((p, i) => {
+ const fhRaw = p.first_heartbeat ?? null;
+ let firstHeartbeat: number | null = null;
+ if (fhRaw !== null && fhRaw !== undefined) {
+ const n = typeof fhRaw === 'string' ? Number(fhRaw) : fhRaw;
+ if (Number.isFinite(n) && n > 0) {
+ firstHeartbeat = n > 1e12 ? Math.floor(n / 1000) : Math.floor(n);
+ }
+ }
+
+ let seconds = 0;
+ const sr = spansResults[i];
+ if (sr.status === 'fulfilled' && sr.value?.spans) {
+ for (const span of sr.value.spans) {
+ if (
+ typeof span.duration === 'number' &&
+ Number.isFinite(span.duration) &&
+ span.duration > 0
+ ) {
+ seconds += span.duration;
+ continue;
+ }
+ if (
+ typeof span.start_time === 'number' &&
+ typeof span.end_time === 'number' &&
+ Number.isFinite(span.start_time) &&
+ Number.isFinite(span.end_time) &&
+ span.end_time > span.start_time
+ ) {
+ // start/end may arrive as seconds or milliseconds.
+ const diff = span.end_time - span.start_time;
+ seconds += diff > 1e9 ? diff / 1000 : diff;
+ }
+ }
+ }
+
+ return {
+ name: p.name,
+ hours: Math.round((seconds / 3600) * 10) / 10,
+ languages: p.languages ?? [],
+ firstHeartbeat,
+ };
+ });
+ }
+ }
+
+ const totalHours = Math.round(
+ matched.reduce((sum, p) => sum + p.hours, 0) * 10,
+ ) / 10;
+
+ const heartbeatTimes = matched
+ .map((p) => p.firstHeartbeat)
+ .filter((t): t is number => t !== null);
+ const earliestHeartbeat = heartbeatTimes.length > 0 ? Math.min(...heartbeatTimes) : null;
+
+ // Currently-applied approved hours on the project (the additive base for
+ // delta-mode review UI). When a project was approved → changes_needed,
+ // these are 0 even if a historical approved submission exists, signaling
+ // the FE to switch the input back to cumulative-mode for the next review.
+ const previousApprovedHours = project.overrideHours ?? 0;
+ const previousInternalHours = project.internalHours ?? 0;
+
+ return {
+ projectId,
+ hackatimeProjects: matched,
+ categories,
+ totalHours,
+ earliestHeartbeat,
+ previousApprovedHours,
+ previousInternalHours,
+ trustLevel,
+ linkedBanned,
+ linkedEmail: isSuperAdmin ? linkedEmail : null,
+ linkedSlackUid,
+ beestEmail: isSuperAdmin ? (user.email ?? null) : null,
+ beestSlackId: user.slackId ?? null,
+ emailMismatch,
+ unifiedDuplicate: unifiedResult.duplicate,
+ unifiedError: unifiedResult.error,
+ debug,
+ };
+ } catch (err) {
+ this.logger.error(`Hackatime admin lookup error for project ${projectId}: ${err}`);
+ return {
+ projectId,
+ hackatimeProjects: [],
+ categories: [],
+ totalHours: 0,
+ earliestHeartbeat: null,
+ previousApprovedHours: project.overrideHours ?? 0,
+ previousInternalHours: project.internalHours ?? 0,
+ trustLevel: null,
+ linkedBanned: false,
+ linkedEmail: null,
+ linkedSlackUid: null,
+ beestEmail: null,
+ beestSlackId: null,
+ emailMismatch: false,
+ unifiedDuplicate: false,
+ unifiedError: true,
+ };
+ }
+ }
+
+ // ── Unreviewed hours ──
+
+ /**
+ * Sum of new Hackatime hours awaiting review across every project currently
+ * in 'unreviewed' status, plus the historical per-decision approval rate and
+ * a naive prediction of how many of those pending hours will end up approved.
+ *
+ * - Hours: for resubmissions the project's previously-approved `overrideHours`
+ * is subtracted, and event-window /spans are used (not lifetime totals) so
+ * pre-event Hackatime time is excluded — matches the per-project review UI.
+ * - Approval rate: across every entry in project_reviews, treats both
+ * 'changes_needed' and 'ban' as not-approved.
+ * - Predicted approved hours: totalHours * approvalRate. This is intentionally
+ * simple — it ignores the fact that 'changes_needed' projects often come
+ * back and get approved on a later pass, so it's a lower-bound estimate.
+ */
+ async getUnreviewedHours(): Promise<{
+ totalHours: number;
+ projectCount: number;
+ approvalRate: number;
+ decisionCount: number;
+ predictedApprovedHours: number;
+ }> {
+ if (
+ this.unreviewedHoursCache &&
+ Date.now() - this.unreviewedHoursCache.timestamp < this.UNREVIEWED_HOURS_CACHE_TTL
+ ) {
+ return this.unreviewedHoursCache.payload;
+ }
+
+ // Historical approval rate from project_reviews. Cheap query — single scan
+ // over a small table — so it shares the unreviewed-hours cache TTL.
+ const reviewCounts = await this.reviewRepo
+ .createQueryBuilder('r')
+ .select("COUNT(*) FILTER (WHERE r.status = 'approved')::int", 'approved')
+ .addSelect("COUNT(*) FILTER (WHERE r.status = 'changes_needed')::int", 'changesNeeded')
+ .addSelect("COUNT(*) FILTER (WHERE r.status = 'ban')::int", 'banned')
+ .getRawOne<{ approved: string | number; changesNeeded: string | number; banned: string | number }>();
+ const approved = Number(reviewCounts?.approved ?? 0);
+ const changesNeeded = Number(reviewCounts?.changesNeeded ?? 0);
+ const banned = Number(reviewCounts?.banned ?? 0);
+ const decisionCount = approved + changesNeeded + banned;
+ const approvalRate = decisionCount > 0 ? approved / decisionCount : 0;
+
+ const projects = await this.projectRepo.find({
+ where: { status: 'unreviewed' },
+ relations: ['user'],
+ });
+ const projectCount = projects.length;
+
+ if (!this.hackatimeAdminKey || projects.length === 0) {
+ const payload = {
+ totalHours: 0,
+ projectCount,
+ approvalRate,
+ decisionCount,
+ predictedApprovedHours: 0,
+ };
+ this.unreviewedHoursCache = { payload, timestamp: Date.now() };
+ return payload;
+ }
+
+ // Flatten to (project, linked-name) pairs — /spans takes one project name
+ // per request. Skip projects whose owner has no Hackatime linkage; their
+ // contribution is 0 either way.
+ const rows: { hackatimeUserId: string; projectName: string; projectId: string }[] = [];
+ for (const p of projects) {
+ if (!p.user?.hackatimeUserId) continue;
+ if (!p.hackatimeProjectName || p.hackatimeProjectName.length === 0) continue;
+ for (const name of p.hackatimeProjectName) {
+ rows.push({
+ hackatimeUserId: p.user.hackatimeUserId,
+ projectName: name,
+ projectId: p.id,
+ });
+ }
+ }
+
+ const endDatePadded = AdminService.ymdUtc(new Date(Date.now() + 86400_000));
+ const secondsByProject = new Map();
+
+ const batchSize = 10;
+ for (let i = 0; i < rows.length; i += batchSize) {
+ const batch = rows.slice(i, i + batchSize);
+ await Promise.allSettled(
+ batch.map(async (row) => {
+ try {
+ const res = await this.hackatimeGet(
+ `/api/v1/users/${encodeURIComponent(row.hackatimeUserId)}/heartbeats/spans` +
+ `?start_date=${AdminService.HACKATIME_EVENT_START}&end_date=${endDatePadded}` +
+ `&project=${encodeURIComponent(row.projectName)}`,
+ );
+ if (!res.ok) return;
+ const data = (await res.json()) as {
+ spans?: { start_time?: number; end_time?: number; duration?: number }[];
+ };
+ let seconds = 0;
+ for (const span of data.spans ?? []) {
+ if (
+ typeof span.duration === 'number' &&
+ Number.isFinite(span.duration) &&
+ span.duration > 0
+ ) {
+ seconds += span.duration;
+ continue;
+ }
+ if (
+ typeof span.start_time === 'number' &&
+ typeof span.end_time === 'number' &&
+ Number.isFinite(span.start_time) &&
+ Number.isFinite(span.end_time) &&
+ span.end_time > span.start_time
+ ) {
+ const diff = span.end_time - span.start_time;
+ seconds += diff > 1e9 ? diff / 1000 : diff;
+ }
+ }
+ secondsByProject.set(
+ row.projectId,
+ (secondsByProject.get(row.projectId) ?? 0) + seconds,
+ );
+ } catch (err) {
+ this.logger.warn(
+ `Unreviewed-hours span fetch failed for project ${row.projectId} (${row.projectName}): ${err}`,
+ );
+ }
+ }),
+ );
+ }
+
+ let totalHours = 0;
+ for (const p of projects) {
+ const hackatimeHours = (secondsByProject.get(p.id) ?? 0) / 3600;
+ const newHours = Math.max(0, hackatimeHours - (p.overrideHours ?? 0));
+ totalHours += newHours;
+ }
+ totalHours = Math.round(totalHours * 10) / 10;
+ const predictedApprovedHours = Math.round(totalHours * approvalRate * 10) / 10;
+
+ const payload = {
+ totalHours,
+ projectCount,
+ approvalRate,
+ decisionCount,
+ predictedApprovedHours,
+ };
+ this.unreviewedHoursCache = { payload, timestamp: Date.now() };
+ return payload;
+ }
+
+ // ── Daily Active Users ──
+
+ async getDailyActiveUsers(): Promise<{ count: number }> {
+ // Return cached value if fresh
+ if (this.dauCache && Date.now() - this.dauCache.timestamp < this.DAU_CACHE_TTL) {
+ return { count: this.dauCache.count };
+ }
+
+ if (!this.hackatimeAdminKey) {
+ return { count: 0 };
+ }
+
+ // Find all beest projects that are linked to a hackatime project, along with their owner.
+ // Uses getMany so the hackatimeProjectName JSON transformer runs and decodes the array.
+ const linkedProjects = await this.projectRepo
+ .createQueryBuilder('p')
+ .innerJoinAndSelect('p.user', 'u')
+ .where('u.hackatime_user_id IS NOT NULL')
+ .andWhere('p.hackatime_project_name IS NOT NULL')
+ .select(['p.id', 'p.hackatimeProjectName', 'u.id', 'u.hackatimeUserId'])
+ .getMany();
+
+ // Group linked hackatime project names per user
+ const perUser = new Map<
+ string,
+ { hackatimeUserId: string; linkedNames: Set }
+ >();
+ for (const p of linkedProjects) {
+ if (!p.user?.hackatimeUserId) continue;
+ if (!p.hackatimeProjectName || p.hackatimeProjectName.length === 0) continue;
+ let entry = perUser.get(p.user.id);
+ if (!entry) {
+ entry = { hackatimeUserId: p.user.hackatimeUserId, linkedNames: new Set() };
+ perUser.set(p.user.id, entry);
+ }
+ for (const n of p.hackatimeProjectName) entry.linkedNames.add(n);
+ }
+
+ const users = Array.from(perUser.values());
+ const oneDayAgo = Math.floor(Date.now() / 1000) - 86400;
+ let activeCount = 0;
+
+ // Process in batches of 10 to avoid overwhelming the API
+ const batchSize = 10;
+ for (let i = 0; i < users.length; i += batchSize) {
+ const batch = users.slice(i, i + batchSize);
+ const results = await Promise.allSettled(
+ batch.map(async (user) => {
+ const res = await this.hackatimeGet(
+ `/api/admin/v1/user/projects?user_id=${user.hackatimeUserId}`,
+ );
+ if (!res.ok) return false;
+ const data = await res.json();
+ const projects: { name?: string; last_heartbeat?: number | string | null }[] =
+ data?.projects ?? data?.data ?? [];
+ return projects.some((p) => {
+ if (!p.name || !user.linkedNames.has(p.name)) return false;
+ const lh = p.last_heartbeat;
+ if (lh == null) return false;
+ const ts = typeof lh === 'string' ? Number(lh) : lh;
+ if (!Number.isFinite(ts) || ts <= 0) return false;
+ const normalized = ts > 1e12 ? Math.floor(ts / 1000) : Math.floor(ts);
+ return normalized >= oneDayAgo;
+ });
+ }),
+ );
+ for (const r of results) {
+ if (r.status === 'fulfilled' && r.value) activeCount++;
+ }
+ }
+
+ this.dauCache = { count: activeCount, timestamp: Date.now() };
+ return { count: activeCount };
+ }
+
+ /**
+ * Historical DAU per UTC day, plus the rolling-24h "today" value.
+ *
+ * Each past day's count is the number of distinct users whose linked beest
+ * Hackatime projects produced at least one span with a start in that day's
+ * UTC window. Finalised days (anything before today UTC) are memoised in
+ * `dauHistoryCache`, so a typical request only fetches spans for dates not
+ * yet cached.
+ */
+ async getDauHistory(): Promise<{
+ history: { date: string; count: number }[];
+ today: { count: number; timestamp: number };
+ }> {
+ const todayUtc = AdminService.ymdUtc(new Date());
+ const allDates = AdminService.enumerateDaysUtc(AdminService.DAU_HISTORY_START, todayUtc);
+ const pastDates = allDates.slice(0, -1); // exclude today — it's the rolling 24h
+
+ if (this.hackatimeAdminKey && pastDates.some((d) => !this.dauHistoryCache.has(d))) {
+ // Collapse concurrent callers onto a single backfill.
+ if (!this.dauHistoryInflight) {
+ this.dauHistoryInflight = this.backfillDauHistory(pastDates).finally(() => {
+ this.dauHistoryInflight = null;
+ });
+ }
+ await this.dauHistoryInflight;
+ }
+
+ const history = pastDates.map((date) => ({
+ date,
+ count: this.dauHistoryCache.get(date) ?? 0,
+ }));
+
+ const { count: todayCount } = await this.getDailyActiveUsers();
+ return { history, today: { count: todayCount, timestamp: Date.now() } };
+ }
+
+ private async backfillDauHistory(dates: string[]): Promise {
+ const missing = dates.filter((d) => !this.dauHistoryCache.has(d));
+ if (missing.length === 0) return;
+
+ // Same filter as getDailyActiveUsers: users with a linked Hackatime ID
+ // and at least one project linked to a Hackatime project name.
+ const linkedProjects = await this.projectRepo
+ .createQueryBuilder('p')
+ .innerJoinAndSelect('p.user', 'u')
+ .where('u.hackatime_user_id IS NOT NULL')
+ .andWhere('p.hackatime_project_name IS NOT NULL')
+ .select(['p.id', 'p.hackatimeProjectName', 'u.id', 'u.hackatimeUserId'])
+ .getMany();
+
+ const perUser = new Map }>();
+ for (const p of linkedProjects) {
+ if (!p.user?.hackatimeUserId) continue;
+ if (!p.hackatimeProjectName || p.hackatimeProjectName.length === 0) continue;
+ let entry = perUser.get(p.user.id);
+ if (!entry) {
+ entry = { hackatimeUserId: p.user.hackatimeUserId, linkedNames: new Set() };
+ perUser.set(p.user.id, entry);
+ }
+ for (const n of p.hackatimeProjectName) entry.linkedNames.add(n);
+ }
+
+ const startDate = missing[0];
+ const endDate = missing[missing.length - 1];
+ // end_date on Hackatime is inclusive of the day — pad by one so the last
+ // missing day is fully covered even with timezone edge cases.
+ const endDatePadded = AdminService.ymdUtc(
+ new Date(Date.parse(endDate + 'T00:00:00Z') + 86400_000),
+ );
+
+ // Per-day sets of active user IDs.
+ const activeByDay = new Map>();
+ for (const d of missing) activeByDay.set(d, new Set());
+
+ const users = Array.from(perUser.entries());
+ const batchSize = 10;
+ for (let i = 0; i < users.length; i += batchSize) {
+ const batch = users.slice(i, i + batchSize);
+ await Promise.allSettled(
+ batch.map(async ([userId, user]) => {
+ // One request per linked project name — the spans endpoint filters
+ // by a single project at a time.
+ const projectNames = Array.from(user.linkedNames);
+ const responses = await Promise.allSettled(
+ projectNames.map((name) =>
+ this.hackatimeGet(
+ `/api/v1/users/${encodeURIComponent(user.hackatimeUserId)}/heartbeats/spans` +
+ `?start_date=${startDate}&end_date=${endDatePadded}` +
+ `&project=${encodeURIComponent(name)}`,
+ ).then(async (r) => (r.ok ? ((await r.json()) as { spans?: { start_time?: number }[] }) : null)),
+ ),
+ );
+ for (const r of responses) {
+ if (r.status !== 'fulfilled' || !r.value?.spans) continue;
+ for (const span of r.value.spans) {
+ const t = span.start_time;
+ if (typeof t !== 'number' || !Number.isFinite(t) || t <= 0) continue;
+ const ms = t > 1e12 ? t : t * 1000;
+ const day = AdminService.ymdUtc(new Date(ms));
+ const set = activeByDay.get(day);
+ if (set) set.add(userId);
+ }
+ }
+ }),
+ );
+ }
+
+ const todayUtc = AdminService.ymdUtc(new Date());
+ for (const [day, set] of activeByDay) {
+ // Only persist finalised days — today's window is still open.
+ if (day < todayUtc) this.dauHistoryCache.set(day, set.size);
+ }
+ }
+
+ private static ymdUtc(d: Date): string {
+ const y = d.getUTCFullYear();
+ const m = String(d.getUTCMonth() + 1).padStart(2, '0');
+ const day = String(d.getUTCDate()).padStart(2, '0');
+ return `${y}-${m}-${day}`;
+ }
+
+ private static enumerateDaysUtc(startYmd: string, endYmd: string): string[] {
+ const out: string[] = [];
+ let cursor = Date.parse(startYmd + 'T00:00:00Z');
+ const end = Date.parse(endYmd + 'T00:00:00Z');
+ while (cursor <= end) {
+ out.push(AdminService.ymdUtc(new Date(cursor)));
+ cursor += 86400_000;
+ }
+ return out;
+ }
+
+ // ── Signups + funnel ──
+
+ async getSignupsHistory(): Promise<{
+ daily: { date: string; count: number }[];
+ cumulative: { date: string; count: number }[];
+ total: number;
+ }> {
+ if (this.signupsCache && Date.now() - this.signupsCache.timestamp < this.SIGNUPS_CACHE_TTL) {
+ return this.signupsCache.payload;
+ }
+
+ const timestamps = await this.rsvpService.getAllSignupTimestamps();
+
+ const dailyMap = new Map();
+ for (const ts of timestamps) {
+ const day = AdminService.ymdUtc(new Date(ts));
+ dailyMap.set(day, (dailyMap.get(day) ?? 0) + 1);
+ }
+
+ const daily = Array.from(dailyMap.entries())
+ .sort(([a], [b]) => a.localeCompare(b))
+ .map(([date, count]) => ({ date, count }));
+
+ const cumulative: { date: string; count: number }[] = [];
+ let running = 0;
+ for (const d of daily) {
+ running += d.count;
+ cumulative.push({ date: d.date, count: running });
+ }
+
+ const payload = { daily, cumulative, total: timestamps.length };
+ this.signupsCache = { payload, timestamp: Date.now() };
+ return payload;
+ }
+
+ async getUserFunnel(): Promise<{
+ signedUp: number;
+ loggedIn: number;
+ linkedHackatime: number;
+ submittedProject: number;
+ approvedProject: number;
+ madeOrder: number;
+ }> {
+ const signupsHistory = await this.getSignupsHistory().catch(() => null);
+
+ // "approvedProject" counts users with a durable approval event in the
+ // submissions table rather than users whose CURRENT project is in the
+ // 'approved' state. Project status drifts: a reviewer approval first moves
+ // the project to 'fraud_pending' (and only the fraud poller flips it to
+ // 'approved'), and an approved → changes_needed transition wipes the
+ // approved status entirely. Submissions, by contrast, are immutable history
+ // — so counting distinct users with at least one approved submission
+ // captures everyone who's ever been approved.
+ const [loggedIn, linkedHackatime, submittedRaw, approvedRaw, orderRaw] = await Promise.all([
+ this.userRepo.count(),
+ this.userRepo
+ .createQueryBuilder('u')
+ .where('u.hackatime_user_id IS NOT NULL')
+ .getCount(),
+ this.projectRepo
+ .createQueryBuilder('p')
+ .select('COUNT(DISTINCT p.user_id)', 'c')
+ .getRawOne<{ c: string }>(),
+ this.submissionRepo
+ .createQueryBuilder('s')
+ .select('COUNT(DISTINCT s.user_id)', 'c')
+ .where('s.status = :status', { status: 'approved' })
+ .getRawOne<{ c: string }>(),
+ this.orderRepo
+ .createQueryBuilder('o')
+ .select('COUNT(DISTINCT o.user_id)', 'c')
+ .getRawOne<{ c: string }>(),
+ ]);
+
+ return {
+ signedUp: signupsHistory?.total ?? 0,
+ loggedIn,
+ linkedHackatime,
+ submittedProject: Number(submittedRaw?.c ?? 0),
+ approvedProject: Number(approvedRaw?.c ?? 0),
+ madeOrder: Number(orderRaw?.c ?? 0),
+ };
+ }
+
+ // ── News CRUD ──
+
+ async listNews(): Promise {
+ return this.newsRepo.find({ order: { displayDate: 'DESC', createdAt: 'DESC' } });
+ }
+
+ async createNews(text: string, displayDate: string): Promise {
+ const item = this.newsRepo.create({ text, displayDate });
+ return this.newsRepo.save(item);
+ }
+
+ async updateNews(id: string, data: { text?: string; displayDate?: string }): Promise {
+ const item = await this.newsRepo.findOne({ where: { id } });
+ if (!item) throw new NotFoundException('News item not found');
+ if (data.text !== undefined) item.text = data.text;
+ if (data.displayDate !== undefined) item.displayDate = data.displayDate;
+ return this.newsRepo.save(item);
+ }
+
+ async deleteNews(id: string): Promise {
+ const item = await this.newsRepo.findOne({ where: { id } });
+ if (!item) throw new NotFoundException('News item not found');
+ await this.newsRepo.remove(item);
+ }
+
+ // ── Shop CRUD ──
+
+ async listShopItems(): Promise {
+ return this.shopRepo.find({ order: { sortOrder: 'ASC' } });
+ }
+
+ async createShopItem(data: {
+ name: string;
+ description: string;
+ detailedDescription?: string | null;
+ imageUrl: string;
+ priceHours: number;
+ stock?: number | null;
+ estimatedShip?: string | null;
+ isActive?: boolean;
+ isFeatured?: boolean;
+ }): Promise {
+ const maxOrder = await this.shopRepo
+ .createQueryBuilder('s')
+ .select('MAX(s.sortOrder)', 'max')
+ .getRawOne();
+ const sortOrder = (maxOrder?.max ?? -1) + 1;
+
+ const item = this.shopRepo.create({
+ name: data.name,
+ description: data.description,
+ detailedDescription: data.detailedDescription ?? null,
+ imageUrl: data.imageUrl,
+ priceHours: data.priceHours,
+ stock: data.stock ?? null,
+ estimatedShip: data.estimatedShip ?? null,
+ isActive: data.isActive ?? true,
+ isFeatured: data.isFeatured ?? false,
+ sortOrder,
+ });
+ return this.shopRepo.save(item);
+ }
+
+ async updateShopItem(id: string, data: {
+ name?: string;
+ description?: string;
+ detailedDescription?: string | null;
+ imageUrl?: string;
+ priceHours?: number;
+ stock?: number | null;
+ estimatedShip?: string | null;
+ isActive?: boolean;
+ isFeatured?: boolean;
+ }): Promise {
+ const item = await this.shopRepo.findOne({ where: { id } });
+ if (!item) throw new NotFoundException('Shop item not found');
+ if (data.name !== undefined) item.name = data.name;
+ if (data.description !== undefined) item.description = data.description;
+ if (data.detailedDescription !== undefined) item.detailedDescription = data.detailedDescription;
+ if (data.imageUrl !== undefined) item.imageUrl = data.imageUrl;
+ if (data.priceHours !== undefined) item.priceHours = data.priceHours;
+ if (data.stock !== undefined) item.stock = data.stock;
+ if (data.estimatedShip !== undefined) item.estimatedShip = data.estimatedShip;
+ if (data.isActive !== undefined) item.isActive = data.isActive;
+ if (data.isFeatured !== undefined) item.isFeatured = data.isFeatured;
+ return this.shopRepo.save(item);
+ }
+
+ async deleteShopItem(id: string): Promise {
+ const item = await this.shopRepo.findOne({ where: { id } });
+ if (!item) throw new NotFoundException('Shop item not found');
+ await this.shopRepo.remove(item);
+ }
+
+ async reorderShopItems(items: { id: string; sortOrder: number }[]): Promise {
+ await Promise.all(
+ items.map((i) => this.shopRepo.update(i.id, { sortOrder: i.sortOrder })),
+ );
+ }
+
+ /**
+ * Super-admin-only order detail for fulfillment: returns the buyer's address
+ * (fetched live from HCA — never persisted in beest) plus their approved
+ * projects so fulfillment staff can verify what to ship.
+ */
+ async getOrderDetailForFulfillment(orderId: string): Promise<{
+ address: {
+ streetAddress: string | null;
+ locality: string | null;
+ region: string | null;
+ postalCode: string | null;
+ country: string | null;
+ } | null;
+ addressMissing: boolean;
+ projects: {
+ id: string;
+ name: string;
+ projectType: string | null;
+ hours: number | null;
+ approvedAt: string;
+ }[];
+ }> {
+ const order = await this.orderRepo.findOne({
+ where: { id: orderId },
+ relations: ['user'],
+ });
+ if (!order) throw new NotFoundException('Order not found');
+
+ const user = order.user;
+
+ const [identity, projects] = await Promise.all([
+ user?.hcaSub ? this.hcaService.getIdentity(user.hcaSub) : Promise.resolve(null),
+ this.projectRepo
+ .createQueryBuilder('project')
+ .where('project.userId = :uid', { uid: order.userId })
+ .andWhere('project.status = :status', { status: 'approved' })
+ .select([
+ 'project.id',
+ 'project.name',
+ 'project.projectType',
+ 'project.overrideHours',
+ 'project.updatedAt',
+ ])
+ .orderBy('project.updatedAt', 'DESC')
+ .getMany(),
+ ]);
+
+ const addr = identity?.address ?? null;
+ const address = addr
+ ? {
+ streetAddress: addr.street_address ?? null,
+ locality: addr.locality ?? null,
+ region: addr.region ?? null,
+ postalCode: addr.postal_code ?? null,
+ country: addr.country ?? null,
+ }
+ : null;
+
+ return {
+ address,
+ addressMissing: !address,
+ projects: projects.map((p) => ({
+ id: p.id,
+ name: p.name,
+ projectType: p.projectType ?? null,
+ hours: p.overrideHours ?? null,
+ approvedAt: p.updatedAt.toISOString(),
+ })),
+ };
+ }
+}
diff --git a/backend/src/admin/audit.service.ts b/backend/src/admin/audit.service.ts
new file mode 100644
index 0000000..245e036
--- /dev/null
+++ b/backend/src/admin/audit.service.ts
@@ -0,0 +1,775 @@
+import {
+ Injectable,
+ Logger,
+ BadRequestException,
+ NotFoundException,
+} from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
+import { InjectRepository } from '@nestjs/typeorm';
+import { In, Repository } from 'typeorm';
+import { Project } from '../entities/project.entity';
+import { Submission } from '../entities/submission.entity';
+import { ProjectReview } from '../entities/project-review.entity';
+import { User } from '../entities/user.entity';
+import { AuditLogService } from '../audit-log/audit-log.service';
+import { RsvpService } from '../rsvp/rsvp.service';
+import { ProjectAirtableSyncService } from '../projects/project-airtable-sync.service';
+import { fetchWithTimeout } from '../fetch.util';
+import { analyzeActivity, type Heartbeat } from './activity-stats';
+import { AdminService } from './admin.service';
+import { Inject, forwardRef } from '@nestjs/common';
+
+// Beest event start — Hackatime time before this date is ignored, matching the
+// rest of the admin Hackatime tooling.
+const HACKATIME_EVENT_START = '2026-04-02';
+const MAX_LOOKBACK_DAYS = 70;
+
+export type AuditAction = 'approve' | 'rereview' | 'reject' | 'ban';
+
+export interface AuditDecisionDto {
+ action: AuditAction;
+ // approve
+ overrideHours?: number | null;
+ internalHours?: number | null;
+ justification?: string | null;
+ // rereview (feedback to the first reviewer, internal)
+ reviewerFeedback?: string | null;
+ // reject + ban (feedback to the user)
+ userFeedback?: string | null;
+ // ban only: caller must be Super Admin — the controller passes this through
+ // from the resolved request perms so the service can refuse non-SA bans.
+ isSuperAdmin?: boolean;
+}
+
+function parseHackatimeNames(raw: string | string[] | null | undefined): string[] {
+ if (!raw) return [];
+ // The Project entity transforms this column to a string[] already, but guard
+ // against a raw string (JSON or comma-separated) just in case.
+ if (Array.isArray(raw)) {
+ return raw.map((s) => String(s).trim()).filter(Boolean);
+ }
+ const trimmed = raw.trim();
+ if (!trimmed) return [];
+ try {
+ const parsed = JSON.parse(trimmed);
+ if (Array.isArray(parsed)) {
+ return parsed.map((s) => String(s).trim()).filter(Boolean);
+ }
+ if (typeof parsed === 'string') return [parsed.trim()].filter(Boolean);
+ } catch {
+ // not JSON — fall back to comma-separated
+ }
+ return trimmed
+ .split(',')
+ .map((s) => s.trim())
+ .filter(Boolean);
+}
+
+@Injectable()
+export class AuditService {
+ private readonly logger = new Logger(AuditService.name);
+ private readonly hackatimeBaseUrl: string;
+
+ constructor(
+ private readonly config: ConfigService,
+ @InjectRepository(Project) private readonly projectRepo: Repository,
+ @InjectRepository(Submission)
+ private readonly submissionRepo: Repository,
+ @InjectRepository(ProjectReview)
+ private readonly reviewRepo: Repository,
+ @InjectRepository(User) private readonly userRepo: Repository,
+ private readonly auditLogService: AuditLogService,
+ private readonly rsvpService: RsvpService,
+ private readonly airtableSync: ProjectAirtableSyncService,
+ @Inject(forwardRef(() => AdminService))
+ private readonly adminService: AdminService,
+ ) {
+ this.hackatimeBaseUrl = this.config.get(
+ 'HACKATIME_BASE_URL',
+ 'https://hackatime.hackclub.com',
+ );
+ }
+
+ // ── Queue ──────────────────────────────────────────────────────────────────
+
+ /**
+ * Projects awaiting a super-admin second pass. These are first-reviewer
+ * approved projects parked in `fraud_pending` (the old fraud-review holding
+ * state, now repurposed as the second-pass queue). Oldest first.
+ */
+ async listQueue(): Promise {
+ const projects = await this.projectRepo.find({
+ where: { status: 'fraud_pending' },
+ relations: ['user'],
+ order: { createdAt: 'ASC' },
+ });
+
+ return Promise.all(projects.map((p) => this.serializeQueueItem(p)));
+ }
+
+ /**
+ * Pull up to N oldest unreviewed projects into the audit queue so a super
+ * admin can clear them as one-shot reviews (skipping the first-pass stage).
+ *
+ * Constraint: only allowed when the queue is empty — this is meant to bridge
+ * a temporary first-reviewer shortage, not run as a parallel review stream.
+ * The "one-shot" property is inferred at decide-time by checking that no
+ * prior `ProjectReview` with status='approved' exists for the project; we
+ * don't tag the row, we just rely on the absence of a first-pass approval.
+ */
+ async loadUnreviewedIntoQueue(superAdminId: string, limit = 10): Promise<{ loaded: number }> {
+ const pending = await this.projectRepo.count({ where: { status: 'fraud_pending' } });
+ if (pending > 0) {
+ throw new BadRequestException(
+ `Cannot load unreviewed projects — audit queue is not empty (${pending} project${pending === 1 ? '' : 's'} still pending).`,
+ );
+ }
+
+ const safeLimit = Math.max(1, Math.min(limit, 25));
+ const candidates = await this.projectRepo.find({
+ where: { status: 'unreviewed' },
+ order: { createdAt: 'ASC' },
+ take: safeLimit,
+ });
+ if (candidates.length === 0) {
+ return { loaded: 0 };
+ }
+
+ for (const p of candidates) {
+ p.status = 'fraud_pending';
+ }
+ await this.projectRepo.save(candidates);
+
+ await this.auditLogService.log(
+ superAdminId,
+ 'project_reviewed',
+ `Loaded ${candidates.length} unreviewed project${candidates.length === 1 ? '' : 's'} into the audit queue for one-shot review`,
+ );
+ this.logger.log(
+ `Super admin ${superAdminId} loaded ${candidates.length} unreviewed projects into the audit queue`,
+ );
+ return { loaded: candidates.length };
+ }
+
+ private async serializeQueueItem(project: Project) {
+ // Pull every submission for this project so we can show the SA the full
+ // history (approved hours + reasons + reviewer) for resubmissions. The
+ // current submission is the newest one; everything older goes into
+ // priorSubmissions for the UI to render as a timeline.
+ const allSubmissions = await this.submissionRepo.find({
+ where: { projectId: project.id },
+ order: { createdAt: 'DESC' },
+ });
+ const submission = allSubmissions[0] ?? null;
+ const olderSubmissions = allSubmissions.slice(1);
+
+ const submissionIds = allSubmissions.map((s) => s.id);
+ const allReviews = submissionIds.length
+ ? await this.reviewRepo.find({
+ where: { submissionId: In(submissionIds) },
+ order: { createdAt: 'DESC' },
+ })
+ : [];
+
+ const reviewsBySubmission = new Map();
+ for (const r of allReviews) {
+ if (!r.submissionId) continue;
+ const list = reviewsBySubmission.get(r.submissionId) ?? [];
+ list.push(r);
+ reviewsBySubmission.set(r.submissionId, list);
+ }
+
+ // Submission-scoped one-shot detection: did the first-pass approve THIS
+ // submission? A re-ship whose new submission has never been approved
+ // correctly registers as one-shot when loaded.
+ const currentReviews = submission
+ ? reviewsBySubmission.get(submission.id) ?? []
+ : [];
+ const originalApproval =
+ currentReviews.find((r) => r.status === 'approved') ?? null;
+
+ const reviewerIds = Array.from(
+ new Set(
+ allReviews
+ .map((r) => r.reviewerId)
+ .filter((id): id is string => !!id),
+ ),
+ );
+ const reviewers = reviewerIds.length
+ ? await this.userRepo.find({ where: { id: In(reviewerIds) } })
+ : [];
+ const reviewerById = new Map(reviewers.map((u) => [u.id, u]));
+ const nameOf = (reviewerId: string | null | undefined): string | null => {
+ if (!reviewerId) return null;
+ const u = reviewerById.get(reviewerId);
+ return u?.nickname || u?.name || null;
+ };
+
+ const reviewerName = nameOf(originalApproval?.reviewerId);
+
+ const priorSubmissions = olderSubmissions.map((sub) => {
+ const subReviews = reviewsBySubmission.get(sub.id) ?? [];
+ // Latest review (already DESC sorted) — the one that decided this submission
+ const latest = subReviews[0] ?? null;
+ return {
+ id: sub.id,
+ status: sub.status,
+ overrideHours: sub.overrideHours,
+ internalHours: sub.internalHours,
+ pipesGranted: sub.pipesGranted,
+ changeDescription: sub.changeDescription,
+ createdAt: sub.createdAt,
+ review: latest
+ ? {
+ status: latest.status,
+ overrideJustification: latest.overrideJustification,
+ feedback: latest.feedback,
+ internalNote: latest.internalNote,
+ reviewerName: nameOf(latest.reviewerId),
+ createdAt: latest.createdAt,
+ }
+ : null,
+ };
+ });
+
+ const user = project.user;
+ return {
+ id: project.id,
+ name: project.name,
+ description: project.description,
+ projectType: project.projectType,
+ codeUrl: project.codeUrl,
+ readmeUrl: project.readmeUrl,
+ demoUrl: project.demoUrl,
+ screenshot1Url: project.screenshot1Url,
+ screenshot2Url: project.screenshot2Url,
+ hackatimeProjectNames: parseHackatimeNames(project.hackatimeProjectName),
+ aiUse: project.aiUse,
+ isUpdate: project.isUpdate,
+ otherHcProgram: project.otherHcProgram,
+ overrideHours: project.overrideHours ?? 0,
+ internalHours: project.internalHours ?? 0,
+ pipesGranted: project.pipesGranted ?? 0,
+ createdAt: project.createdAt,
+ owner: user
+ ? {
+ id: user.id,
+ name: user.name,
+ nickname: user.nickname,
+ slackId: user.slackId,
+ email: user.email,
+ hackatimeConnected: !!user.hackatimeToken,
+ }
+ : null,
+ originalApproval: originalApproval
+ ? {
+ reviewerId: originalApproval.reviewerId,
+ reviewerName,
+ overrideJustification: originalApproval.overrideJustification,
+ feedback: originalApproval.feedback,
+ internalNote: originalApproval.internalNote,
+ createdAt: originalApproval.createdAt,
+ }
+ : null,
+ // One-shot = pulled into the queue via the SA escape hatch (no prior
+ // first-pass approval). The decide endpoint enforces stricter checks in
+ // this mode; the UI surfaces it so the SA knows they're the only review.
+ isOneShot: !originalApproval,
+ submission: submission
+ ? {
+ id: submission.id,
+ changeDescription: submission.changeDescription,
+ overrideHours: submission.overrideHours,
+ createdAt: submission.createdAt,
+ }
+ : null,
+ // Older submissions on the same project, newest first, each with its
+ // latest review (so the SA can see history for resubmissions). Empty
+ // array for first ships.
+ priorSubmissions,
+ };
+ }
+
+ // ── Decision ─────────────────────────────────────────────────────────────--
+
+ async decide(
+ projectId: string,
+ superAdminId: string,
+ dto: AuditDecisionDto,
+ ): Promise<{ success: true }> {
+ const project = await this.projectRepo.findOne({
+ where: { id: projectId },
+ relations: ['user'],
+ });
+ if (!project) throw new NotFoundException('Project not found');
+ if (project.status !== 'fraud_pending') {
+ throw new BadRequestException(
+ `Project is not awaiting second review (status: ${project.status}).`,
+ );
+ }
+
+ const submission = await this.submissionRepo.findOne({
+ where: { projectId },
+ order: { createdAt: 'DESC' },
+ });
+
+ switch (dto.action) {
+ case 'approve':
+ return this.approve(project, submission, superAdminId, dto);
+ case 'rereview':
+ return this.returnForReReview(project, submission, superAdminId, dto);
+ case 'reject':
+ return this.reject(project, submission, superAdminId, dto);
+ case 'ban':
+ return this.banFromAudit(project, superAdminId, dto);
+ default:
+ throw new BadRequestException('Unknown action');
+ }
+ }
+
+ /** Ban-and-reject — Super-Admin only. Delegates to AdminService so the ban
+ * path stays identical to the first-pass ban. */
+ private async banFromAudit(
+ project: Project,
+ superAdminId: string,
+ dto: AuditDecisionDto,
+ ): Promise<{ success: true }> {
+ if (!dto.isSuperAdmin) {
+ throw new BadRequestException(
+ 'Only Super Admins can ban from the audit panel.',
+ );
+ }
+ const feedback = (dto.userFeedback ?? '').trim();
+ if (feedback.length < 10) {
+ throw new BadRequestException(
+ 'Ban feedback for the user must be at least 10 characters.',
+ );
+ }
+ await this.adminService.banAndRejectProject(
+ project.id,
+ superAdminId,
+ feedback,
+ 'Banned via audit panel',
+ null,
+ );
+ return { success: true };
+ }
+
+ /** Final approval: grant pipes + push to Airtable (relocated from the old
+ * fraud-review poller's completeApproval). */
+ private async approve(
+ project: Project,
+ submission: Submission | null,
+ superAdminId: string,
+ dto: AuditDecisionDto,
+ ): Promise<{ success: true }> {
+ // One-shot mode: this *submission* has no first-pass approval, so this is
+ // the only review it will get. Tighten the justification floor and apply
+ // the same Hackatime cap the first-pass would have enforced. Submission-
+ // scoped so re-ships of previously-approved projects still register.
+ const priorApproval = submission
+ ? await this.reviewRepo.findOne({
+ where: { submissionId: submission.id, status: 'approved' },
+ })
+ : null;
+ const isOneShot = !priorApproval;
+
+ const justification = (dto.justification ?? '').trim();
+ const minJustification = isOneShot ? 250 : 50;
+ if (justification.length < minJustification) {
+ throw new BadRequestException(
+ `Approval justification must be at least ${minJustification} characters.`,
+ );
+ }
+
+ // The SA may set overrideHours (user-facing, drives pipes) and internalHours
+ // (Airtable's "Override Hours Spent") independently. Both are treated as
+ // FINAL cumulative values, overwriting the first reviewer's numbers. If
+ // internalHours isn't supplied the existing project value is preserved,
+ // EXCEPT in one-shot mode where it defaults to the override value (no
+ // first-pass left an internalHours behind).
+ if (dto.overrideHours !== null && dto.overrideHours !== undefined) {
+ const finalOverride = Math.round(dto.overrideHours * 10) / 10;
+ if (!Number.isFinite(finalOverride) || finalOverride <= 0) {
+ throw new BadRequestException('overrideHours must be a positive number.');
+ }
+ if (finalOverride < (project.pipesGranted ?? 0)) {
+ throw new BadRequestException(
+ `Cannot reduce hours to ${finalOverride} — ${project.pipesGranted} pipes already granted.`,
+ );
+ }
+ // One-shot: enforce the same Hackatime cap the first-pass would apply.
+ // Cap = currentHackatime + 0.5h rounding buffer (previousProjectHours is
+ // 0 since first-pass never ran). Hackatime outage is logged and skipped
+ // rather than blocking the review.
+ if (isOneShot) {
+ try {
+ const ht = await this.adminService.getProjectHackatime(project.id, false);
+ const currentHackatime = ht?.totalHours ?? 0;
+ const allowedDelta = currentHackatime + 0.5;
+ if (finalOverride > allowedDelta) {
+ const hackatimeHours = Math.round(currentHackatime * 10) / 10;
+ throw new BadRequestException(
+ `Cannot approve ${finalOverride}h — Hackatime shows only ${hackatimeHours}h. Reduce the approved hours, or reject instead.`,
+ );
+ }
+ } catch (e) {
+ if (e instanceof BadRequestException) throw e;
+ this.logger.warn(
+ `One-shot Hackatime cap check failed for project ${project.id}: ${e}`,
+ );
+ }
+ }
+ project.overrideHours = finalOverride;
+ if (submission) submission.overrideHours = finalOverride;
+
+ let finalInternal: number;
+ if (dto.internalHours !== null && dto.internalHours !== undefined) {
+ finalInternal = Math.round(dto.internalHours * 10) / 10;
+ if (!Number.isFinite(finalInternal) || finalInternal < 0) {
+ throw new BadRequestException('internalHours must be a non-negative number.');
+ }
+ } else {
+ finalInternal = isOneShot ? finalOverride : (project.internalHours ?? finalOverride);
+ }
+ project.internalHours = finalInternal;
+ if (submission) submission.internalHours = finalInternal;
+ } else if (isOneShot) {
+ // One-shot requires the SA to explicitly set the approved hours — the
+ // project has no first-pass-set value to inherit from.
+ throw new BadRequestException(
+ 'One-shot approval requires overrideHours to be set.',
+ );
+ }
+
+ project.status = 'approved';
+ await this.projectRepo.save(project);
+
+ if (submission && submission.status !== 'approved') {
+ submission.status = 'approved';
+ await this.submissionRepo.save(submission);
+ }
+
+ // Grant pipes — delta logic identical to the previous fraud poller path.
+ if ((project.overrideHours ?? 0) > 0) {
+ const totals = await this.projectRepo
+ .createQueryBuilder('p')
+ .select('COALESCE(SUM(p.override_hours), 0)', 'earnedHours')
+ .addSelect('COALESCE(SUM(p.pipes_granted), 0)', 'granted')
+ .where('p.user_id = :uid', { uid: project.userId })
+ .andWhere(
+ `(p.status = 'approved' OR (p.status <> 'approved' AND p.pipes_granted > 0))`,
+ )
+ .getRawOne<{ earnedHours: string; granted: string }>();
+ const target = Math.floor(Number(totals?.earnedHours ?? 0));
+ const previouslyGranted = Number(totals?.granted ?? 0);
+ const delta = target - previouslyGranted;
+ if (delta > 0) {
+ await this.userRepo.increment({ id: project.userId }, 'pipes', delta);
+ project.pipesGranted = (project.pipesGranted ?? 0) + delta;
+ await this.projectRepo.save(project);
+ if (submission) {
+ submission.pipesGranted = delta;
+ await this.submissionRepo.save(submission);
+ }
+ }
+ }
+
+ // Record the second-pass approval. One-shot approvals can carry a
+ // user-facing feedback string (the only review the user will see, so the
+ // SA may want to leave a note); regular second-pass approvals don't.
+ const approveUserFeedback = isOneShot
+ ? ((dto.userFeedback ?? '').trim() || null)
+ : null;
+ const review = this.reviewRepo.create({
+ projectId: project.id,
+ reviewerId: superAdminId,
+ submissionId: submission?.id ?? null,
+ status: 'approved',
+ feedback: approveUserFeedback,
+ internalNote: isOneShot ? 'One-shot approval' : 'Second-pass (super admin) approval',
+ overrideJustification: justification,
+ });
+ await this.reviewRepo.save(review);
+
+ // Loops + Airtable Projects push — now gated to this stage only.
+ if (project.user?.email) {
+ this.rsvpService.updateDateField(
+ project.user.email,
+ 'Loops - beestApprovedProject',
+ );
+ }
+ try {
+ await this.airtableSync.syncApprovedProject(
+ project,
+ justification,
+ submission ?? null,
+ );
+ } catch (err) {
+ this.logger.error(
+ `Airtable sync failed for second-pass-approved project ${project.id}: ${err}`,
+ );
+ }
+
+ await this.auditLogService.log(
+ project.userId,
+ 'project_reviewed',
+ `Project "${project.name}" was approved`,
+ );
+ this.logger.log(`Second-pass approved project ${project.id}`);
+ return { success: true };
+ }
+
+ /** Send back to the first-review queue with feedback for the first reviewer
+ * (internal — the user is not notified). */
+ private async returnForReReview(
+ project: Project,
+ submission: Submission | null,
+ superAdminId: string,
+ dto: AuditDecisionDto,
+ ): Promise<{ success: true }> {
+ const feedback = (dto.reviewerFeedback ?? '').trim();
+ if (feedback.length < 10) {
+ throw new BadRequestException(
+ 'Re-review feedback must be at least 10 characters.',
+ );
+ }
+
+ // Revert the pending approval delta this submission contributed, so a
+ // re-approval re-adds it cleanly rather than double-counting.
+ const subOverride = submission?.overrideHours ?? 0;
+ const subInternal = submission?.internalHours ?? 0;
+ if (subOverride > 0) {
+ project.overrideHours = Math.max(
+ 0,
+ Math.round(((project.overrideHours ?? 0) - subOverride) * 10) / 10,
+ );
+ }
+ if (subInternal > 0) {
+ project.internalHours = Math.max(
+ 0,
+ Math.round(((project.internalHours ?? 0) - subInternal) * 10) / 10,
+ );
+ }
+ project.status = 'unreviewed';
+ await this.projectRepo.save(project);
+
+ if (submission) {
+ submission.status = 'unreviewed';
+ submission.reviewerNote = `[Returned by second-pass review] ${feedback}`;
+ await this.submissionRepo.save(submission);
+ }
+
+ // Internal trace (logged against the super admin, not the user).
+ await this.auditLogService.log(
+ superAdminId,
+ 'project_reviewed',
+ `Returned "${project.name}" to the first-review queue for re-review`,
+ );
+ this.logger.log(`Second-pass returned project ${project.id} for re-review`);
+ return { success: true };
+ }
+
+ /** Regular rejection — user-facing changes-needed, no pipes (none granted). */
+ private async reject(
+ project: Project,
+ submission: Submission | null,
+ superAdminId: string,
+ dto: AuditDecisionDto,
+ ): Promise<{ success: true }> {
+ const feedback = (dto.userFeedback ?? '').trim();
+ if (feedback.length < 10) {
+ throw new BadRequestException(
+ 'Rejection feedback for the user must be at least 10 characters.',
+ );
+ }
+
+ project.status = 'changes_needed';
+ project.overrideHours = 0;
+ project.internalHours = 0;
+ await this.projectRepo.save(project);
+
+ if (submission && submission.status !== 'changes_needed') {
+ submission.status = 'changes_needed';
+ submission.overrideHours = 0;
+ submission.internalHours = 0;
+ await this.submissionRepo.save(submission);
+ }
+
+ const review = this.reviewRepo.create({
+ projectId: project.id,
+ reviewerId: superAdminId,
+ submissionId: submission?.id ?? null,
+ status: 'changes_needed',
+ feedback,
+ internalNote: 'Rejected at second-pass review',
+ overrideJustification: null,
+ });
+ await this.reviewRepo.save(review);
+
+ await this.auditLogService.log(
+ project.userId,
+ 'project_reviewed',
+ `Project "${project.name}" received feedback`,
+ );
+ this.logger.log(`Second-pass rejected project ${project.id}`);
+ return { success: true };
+ }
+
+ // ── Heartbeats (anomaly + activity analysis) ───────────────────────────────
+
+ /**
+ * Streams NDJSON events ({type: meta|day|complete|error}) for the heartbeat
+ * timeline + anomaly analysis. Fetches the owner's raw Hackatime heartbeats
+ * day-by-day using their stored OAuth token, filters to the project's linked
+ * Hackatime project names, then analyzes.
+ */
+ async *streamActivityEvents(
+ projectId: string,
+ ): AsyncGenerator> {
+ const project = await this.projectRepo.findOne({
+ where: { id: projectId },
+ relations: ['user'],
+ });
+ if (!project) {
+ yield { type: 'error', error: 'project-not-found' };
+ return;
+ }
+ const user = project.user;
+ if (!user) {
+ yield { type: 'error', error: 'owner-not-found' };
+ return;
+ }
+ const names = parseHackatimeNames(project.hackatimeProjectName);
+ if (names.length === 0) {
+ yield { type: 'error', error: 'no-hackatime-project' };
+ return;
+ }
+ if (!user.hackatimeToken) {
+ yield { type: 'error', error: 'owner-not-linked' };
+ return;
+ }
+
+ let apiKey: string;
+ try {
+ apiKey = await this.getApiKey(user.hackatimeToken);
+ } catch (err) {
+ this.logger.warn(`Hackatime api_key fetch failed for ${projectId}: ${err}`);
+ yield { type: 'error', error: 'hackatime-auth-failed' };
+ return;
+ }
+
+ const to = new Date();
+ let fromMs = Date.parse(`${HACKATIME_EVENT_START}T00:00:00Z`);
+ const ceilingMs = to.getTime() - MAX_LOOKBACK_DAYS * 86_400_000;
+ if (Number.isNaN(fromMs)) fromMs = ceilingMs;
+ fromMs = Math.max(fromMs, ceilingMs);
+ const from = new Date(fromMs);
+ const days = eachDayUTC(from, to);
+
+ yield {
+ type: 'meta',
+ hackatimeProjects: names,
+ fromIso: from.toISOString(),
+ toIso: to.toISOString(),
+ daysQueried: days.length,
+ };
+
+ const projectSet = new Set(names);
+ const matched: Heartbeat[] = [];
+ const seen = new Set();
+ let rawCount = 0;
+ let daysSeen = 0;
+ const concurrency = 6;
+
+ for (let i = 0; i < days.length; i += concurrency) {
+ const batch = days.slice(i, i + concurrency);
+ const results = await Promise.allSettled(
+ batch.map((d) => this.fetchHeartbeatsForDay(apiKey, d)),
+ );
+ for (let j = 0; j < batch.length; j++) {
+ daysSeen += 1;
+ const r = results[j];
+ const hbs = r.status === 'fulfilled' ? r.value : [];
+ for (const hb of hbs) {
+ const key = `${hb.time}|${hb.entity}`;
+ if (seen.has(key)) continue;
+ seen.add(key);
+ rawCount += 1;
+ if (projectSet.has((hb.project ?? '').trim())) matched.push(hb);
+ }
+ yield {
+ type: 'day',
+ day: batch[j].toISOString().slice(0, 10),
+ daysSeen,
+ daysTotal: days.length,
+ matchedCountSoFar: matched.length,
+ rawCountSoFar: rawCount,
+ };
+ }
+ }
+
+ const analysis = analyzeActivity(matched);
+ yield {
+ type: 'complete',
+ analysis,
+ hackatimeProjects: names,
+ diagnostics: {
+ rawCount,
+ matchedCount: matched.length,
+ daysQueried: days.length,
+ },
+ };
+ }
+
+ private async getApiKey(ownerToken: string): Promise {
+ const res = await fetchWithTimeout(
+ `${this.hackatimeBaseUrl}/api/v1/authenticated/api_keys`,
+ { headers: { Authorization: `Bearer ${ownerToken}` } },
+ );
+ if (!res.ok) {
+ throw new Error(`api_keys ${res.status}`);
+ }
+ const body = (await res.json()) as { token?: string };
+ if (!body.token) throw new Error('api_keys response missing token');
+ return body.token;
+ }
+
+ private async fetchHeartbeatsForDay(
+ apiKey: string,
+ day: Date,
+ ): Promise {
+ const start = new Date(
+ Date.UTC(day.getUTCFullYear(), day.getUTCMonth(), day.getUTCDate(), 0, 0, 0),
+ );
+ const end = new Date(
+ Date.UTC(day.getUTCFullYear(), day.getUTCMonth(), day.getUTCDate(), 23, 59, 59),
+ );
+ const params = new URLSearchParams({
+ start_time: start.toISOString(),
+ end_time: end.toISOString(),
+ });
+ const res = await fetchWithTimeout(
+ `${this.hackatimeBaseUrl}/api/v1/my/heartbeats?${params.toString()}`,
+ { headers: { Authorization: `Bearer ${apiKey}` } },
+ );
+ if (!res.ok) return [];
+ const body = (await res.json()) as { heartbeats?: Heartbeat[] };
+ return Array.isArray(body.heartbeats) ? body.heartbeats : [];
+ }
+}
+
+function eachDayUTC(from: Date, to: Date): Date[] {
+ const days: Date[] = [];
+ const cursor = new Date(
+ Date.UTC(from.getUTCFullYear(), from.getUTCMonth(), from.getUTCDate()),
+ );
+ const last = new Date(
+ Date.UTC(to.getUTCFullYear(), to.getUTCMonth(), to.getUTCDate()),
+ );
+ while (cursor.getTime() <= last.getTime()) {
+ days.push(new Date(cursor.getTime()));
+ cursor.setUTCDate(cursor.getUTCDate() + 1);
+ }
+ return days;
+}
diff --git a/backend/src/admin/fraud-reviewer.guard.ts b/backend/src/admin/fraud-reviewer.guard.ts
new file mode 100644
index 0000000..c2951a1
--- /dev/null
+++ b/backend/src/admin/fraud-reviewer.guard.ts
@@ -0,0 +1,52 @@
+import {
+ CanActivate,
+ ExecutionContext,
+ Injectable,
+ ForbiddenException,
+ UnauthorizedException,
+} from '@nestjs/common';
+import { AuthService } from '../auth/auth.service';
+import { RsvpService } from '../rsvp/rsvp.service';
+
+const ALLOWED_ROLES = ['Super Admin', 'Fraud Reviewer'];
+
+/**
+ * Guard for routes that gate audit-panel actions and bans.
+ * Allows Super Admin and Fraud Reviewer. Checks Airtable on every request —
+ * no caching, so revocations are instant.
+ */
+@Injectable()
+export class FraudReviewerGuard implements CanActivate {
+ constructor(
+ private readonly authService: AuthService,
+ private readonly rsvpService: RsvpService,
+ ) {}
+
+ async canActivate(context: ExecutionContext): Promise {
+ const request = context.switchToHttp().getRequest();
+ const authHeader = request.headers.authorization;
+
+ if (!authHeader?.startsWith('Bearer ')) {
+ throw new UnauthorizedException();
+ }
+
+ let user: Record;
+ try {
+ const token = authHeader.split(' ')[1];
+ user = this.authService.verifyToken(token);
+ } catch {
+ throw new UnauthorizedException();
+ }
+
+ const email = user?.email as string;
+ if (!email) throw new ForbiddenException();
+
+ const perms = await this.rsvpService.getPerms(email);
+ if (!perms || !ALLOWED_ROLES.includes(perms)) {
+ throw new ForbiddenException();
+ }
+
+ request.user = { ...user, perms };
+ return true;
+ }
+}
diff --git a/backend/src/admin/reviewer.guard.ts b/backend/src/admin/reviewer.guard.ts
new file mode 100644
index 0000000..9dd4613
--- /dev/null
+++ b/backend/src/admin/reviewer.guard.ts
@@ -0,0 +1,51 @@
+import {
+ CanActivate,
+ ExecutionContext,
+ Injectable,
+ ForbiddenException,
+ UnauthorizedException,
+} from '@nestjs/common';
+import { AuthService } from '../auth/auth.service';
+import { RsvpService } from '../rsvp/rsvp.service';
+
+const REVIEWER_ROLES = ['Super Admin', 'Reviewer', 'Fraud Reviewer'];
+
+/**
+ * Guard that requires a valid JWT AND Reviewer-level+ Perms in Airtable.
+ * Allows Super Admin, Reviewer, and Fraud Reviewer.
+ */
+@Injectable()
+export class ReviewerGuard implements CanActivate {
+ constructor(
+ private readonly authService: AuthService,
+ private readonly rsvpService: RsvpService,
+ ) {}
+
+ async canActivate(context: ExecutionContext): Promise {
+ const request = context.switchToHttp().getRequest();
+ const authHeader = request.headers.authorization;
+
+ if (!authHeader?.startsWith('Bearer ')) {
+ throw new UnauthorizedException();
+ }
+
+ let user: Record;
+ try {
+ const token = authHeader.split(' ')[1];
+ user = this.authService.verifyToken(token);
+ } catch {
+ throw new UnauthorizedException();
+ }
+
+ const email = user?.email as string;
+ if (!email) throw new ForbiddenException();
+
+ const perms = await this.rsvpService.getPerms(email);
+ if (!perms || !REVIEWER_ROLES.includes(perms)) {
+ throw new ForbiddenException();
+ }
+
+ request.user = { ...user, perms };
+ return true;
+ }
+}
diff --git a/backend/src/admin/super-admin.guard.ts b/backend/src/admin/super-admin.guard.ts
new file mode 100644
index 0000000..89998f6
--- /dev/null
+++ b/backend/src/admin/super-admin.guard.ts
@@ -0,0 +1,49 @@
+import {
+ CanActivate,
+ ExecutionContext,
+ Injectable,
+ ForbiddenException,
+ UnauthorizedException,
+} from '@nestjs/common';
+import { AuthService } from '../auth/auth.service';
+import { RsvpService } from '../rsvp/rsvp.service';
+
+/**
+ * Guard that requires a valid JWT AND Super Admin Perms in Airtable.
+ * Checks Airtable on every request — no caching, so revocations are instant.
+ */
+@Injectable()
+export class SuperAdminGuard implements CanActivate {
+ constructor(
+ private readonly authService: AuthService,
+ private readonly rsvpService: RsvpService,
+ ) {}
+
+ async canActivate(context: ExecutionContext): Promise {
+ const request = context.switchToHttp().getRequest();
+ const authHeader = request.headers.authorization;
+
+ if (!authHeader?.startsWith('Bearer ')) {
+ throw new UnauthorizedException();
+ }
+
+ let user: Record;
+ try {
+ const token = authHeader.split(' ')[1];
+ user = this.authService.verifyToken(token);
+ } catch {
+ throw new UnauthorizedException();
+ }
+
+ const email = user?.email as string;
+ if (!email) throw new ForbiddenException();
+
+ const perms = await this.rsvpService.getPerms(email);
+ if (perms !== 'Super Admin') {
+ throw new ForbiddenException();
+ }
+
+ request.user = user;
+ return true;
+ }
+}
diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts
new file mode 100644
index 0000000..1e0b512
--- /dev/null
+++ b/backend/src/app.module.ts
@@ -0,0 +1,68 @@
+import { Module } from '@nestjs/common';
+import { ConfigModule, ConfigService } from '@nestjs/config';
+import { ScheduleModule } from '@nestjs/schedule';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { RsvpModule } from './rsvp/rsvp.module';
+import { AuthModule } from './auth/auth.module';
+import { HackatimeModule } from './hackatime/hackatime.module';
+import { OnboardingModule } from './onboarding/onboarding.module';
+import { ProjectsModule } from './projects/projects.module';
+import { AuditLogModule } from './audit-log/audit-log.module';
+import { AdminModule } from './admin/admin.module';
+import { NewsModule } from './news/news.module';
+import { ShopModule } from './shop/shop.module';
+import { DevlogsModule } from './devlogs/devlogs.module';
+import { FraudReviewModule } from './fraud-review/fraud-review.module';
+import { LapseModule } from './lapse/lapse.module';
+import { HcbModule } from './hcb/hcb.module';
+import { User } from './entities/user.entity';
+import { Session } from './entities/session.entity';
+import { Project } from './entities/project.entity';
+import { AuditLog } from './entities/audit-log.entity';
+import { NewsItem } from './entities/news-item.entity';
+import { ProjectReview } from './entities/project-review.entity';
+import { Comment } from './entities/comment.entity';
+import { ShopItem } from './entities/shop-item.entity';
+import { Order } from './entities/order.entity';
+import { FulfillmentUpdate } from './entities/fulfillment-update.entity';
+import { Submission } from './entities/submission.entity';
+import { ShopSuggestion } from './entities/shop-suggestion.entity';
+import { ShopSuggestionVote } from './entities/shop-suggestion-vote.entity';
+import { Devlog } from './entities/devlog.entity';
+import { FraudReview } from './entities/fraud-review.entity';
+import { HcbCredential } from './entities/hcb-credential.entity';
+import { HealthController } from './health.controller';
+
+@Module({
+ controllers: [HealthController],
+ imports: [
+ ConfigModule.forRoot({ isGlobal: true }),
+ ScheduleModule.forRoot(),
+ TypeOrmModule.forRootAsync({
+ imports: [ConfigModule],
+ inject: [ConfigService],
+ useFactory: (config: ConfigService) => ({
+ type: 'postgres',
+ url: config.getOrThrow('DATABASE_URL'),
+ entities: [User, Session, Project, AuditLog, NewsItem, ProjectReview, Comment, ShopItem, Order, FulfillmentUpdate, Submission, ShopSuggestion, ShopSuggestionVote, Devlog, FraudReview, HcbCredential],
+ migrations: [__dirname + '/migrations/*{.ts,.js}'],
+ migrationsRun: true,
+ synchronize: false,
+ }),
+ }),
+ RsvpModule,
+ AuthModule,
+ HackatimeModule,
+ OnboardingModule,
+ ProjectsModule,
+ AuditLogModule,
+ AdminModule,
+ NewsModule,
+ ShopModule,
+ DevlogsModule,
+ FraudReviewModule,
+ LapseModule,
+ HcbModule,
+ ],
+})
+export class AppModule {}
diff --git a/backend/src/audit-log/audit-log.controller.ts b/backend/src/audit-log/audit-log.controller.ts
new file mode 100644
index 0000000..e813623
--- /dev/null
+++ b/backend/src/audit-log/audit-log.controller.ts
@@ -0,0 +1,26 @@
+import {
+ Controller,
+ Get,
+ Req,
+ UseGuards,
+ UnauthorizedException,
+} from '@nestjs/common';
+import { Throttle } from '@nestjs/throttler';
+import type { Request } from 'express';
+import { JwtAuthGuard } from '../auth/jwt-auth.guard';
+import { AuditLogService } from './audit-log.service';
+
+@Controller('api/audit-log')
+export class AuditLogController {
+ constructor(private readonly auditLogService: AuditLogService) {}
+
+ @Throttle({ default: { limit: 30, ttl: 60000 } })
+ @UseGuards(JwtAuthGuard)
+ @Get()
+ async list(@Req() req: Request) {
+ const userId = (req as any).user?.uid;
+ if (!userId) throw new UnauthorizedException('No user identity');
+
+ return this.auditLogService.getForUser(userId);
+ }
+}
diff --git a/backend/src/audit-log/audit-log.module.ts b/backend/src/audit-log/audit-log.module.ts
new file mode 100644
index 0000000..84e6da4
--- /dev/null
+++ b/backend/src/audit-log/audit-log.module.ts
@@ -0,0 +1,14 @@
+import { Module } from '@nestjs/common';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { AuthModule } from '../auth/auth.module';
+import { AuditLog } from '../entities/audit-log.entity';
+import { AuditLogController } from './audit-log.controller';
+import { AuditLogService } from './audit-log.service';
+
+@Module({
+ imports: [AuthModule, TypeOrmModule.forFeature([AuditLog])],
+ controllers: [AuditLogController],
+ providers: [AuditLogService],
+ exports: [AuditLogService],
+})
+export class AuditLogModule {}
diff --git a/backend/src/audit-log/audit-log.service.ts b/backend/src/audit-log/audit-log.service.ts
new file mode 100644
index 0000000..2fdcb9d
--- /dev/null
+++ b/backend/src/audit-log/audit-log.service.ts
@@ -0,0 +1,51 @@
+import { Injectable } from '@nestjs/common';
+import { InjectRepository } from '@nestjs/typeorm';
+import { Repository } from 'typeorm';
+import { AuditLog, AuditAction } from '../entities/audit-log.entity';
+
+@Injectable()
+export class AuditLogService {
+ constructor(
+ @InjectRepository(AuditLog)
+ private auditLogRepo: Repository,
+ ) {}
+
+ async log(userId: string, action: AuditAction, label: string, impersonatorName?: string): Promise {
+ const prefix = impersonatorName ? `[${impersonatorName} performed an action on your behalf] ` : '';
+ const entry = this.auditLogRepo.create({
+ userId,
+ action,
+ label: (prefix + label).replace(/[<>"'`&\\]/g, '').replace(/\0/g, '').trim().slice(0, 255),
+ });
+ await this.auditLogRepo.save(entry);
+ }
+
+ async getForUser(userId: string, limit = 50) {
+ return this.auditLogRepo.find({
+ where: { userId },
+ order: { createdAt: 'DESC' },
+ take: limit,
+ select: ['id', 'action', 'label', 'createdAt'],
+ });
+ }
+
+ /**
+ * Returns distinct user IDs with at least one `hackatime_ownership_failed`
+ * audit log whose label contains any of `labelSubstrings`. Used by the
+ * daily Hackatime-recovery cron to find candidates whose linked account
+ * was originally flagged as banned/red-trust.
+ */
+ async findUsersWithOwnershipFailLabels(labelSubstrings: string[]): Promise {
+ if (labelSubstrings.length === 0) return [];
+ const qb = this.auditLogRepo
+ .createQueryBuilder('a')
+ .select('DISTINCT a.user_id', 'user_id')
+ .where("a.action = 'hackatime_ownership_failed'");
+ qb.andWhere(
+ `(${labelSubstrings.map((_, i) => `a.label LIKE :s${i}`).join(' OR ')})`,
+ Object.fromEntries(labelSubstrings.map((s, i) => [`s${i}`, `%${s}%`])),
+ );
+ const rows = await qb.getRawMany<{ user_id: string }>();
+ return rows.map((r) => r.user_id);
+ }
+}
diff --git a/backend/src/auth/auth.controller.ts b/backend/src/auth/auth.controller.ts
new file mode 100644
index 0000000..b7c2f06
--- /dev/null
+++ b/backend/src/auth/auth.controller.ts
@@ -0,0 +1,228 @@
+import {
+ Controller,
+ Get,
+ Post,
+ Patch,
+ Body,
+ Req,
+ Query,
+ UseGuards,
+ UnauthorizedException,
+ BadRequestException,
+ ForbiddenException,
+} from '@nestjs/common';
+import { Throttle } from '@nestjs/throttler';
+import type { Request } from 'express';
+import { AuthService, ALLOWED_GENDERS, type Gender } from './auth.service';
+import { JwtAuthGuard } from './jwt-auth.guard';
+import { RsvpService } from '../rsvp/rsvp.service';
+import { IdentityService } from '../identity/identity.service';
+
+@Controller('api/auth')
+export class AuthController {
+ constructor(
+ private readonly authService: AuthService,
+ private readonly rsvpService: RsvpService,
+ private readonly identityService: IdentityService,
+ ) {}
+
+ @Throttle({ default: { limit: 10, ttl: 60000 } })
+ @Post('start')
+ start(@Body() body: { email?: string }) {
+ return this.authService.startAuth(body.email);
+ }
+
+ @Throttle({ default: { limit: 10, ttl: 60000 } })
+ @Post('handle-callback')
+ async handleCallback(
+ @Body()
+ body: {
+ code: string;
+ state: string;
+ storedState: string;
+ attribution?: {
+ utm_source?: string | null;
+ utm_medium?: string | null;
+ utm_campaign?: string | null;
+ referrer?: string | null;
+ landing_path?: string | null;
+ };
+ },
+ ) {
+ if (!body.code) {
+ throw new BadRequestException('Authorization code is required');
+ }
+ if (!body.state || !body.storedState) {
+ throw new BadRequestException('State parameters are required');
+ }
+ try {
+ return await this.authService.handleCallback(
+ body.code,
+ body.state,
+ body.storedState,
+ body.attribution,
+ );
+ } catch {
+ throw new UnauthorizedException('Authentication failed');
+ }
+ }
+
+ /**
+ * Exchange a refresh token for a new JWT + rotated refresh token.
+ */
+ @Throttle({ default: { limit: 10, ttl: 60000 } })
+ @Post('refresh')
+ async refresh(@Body() body: { refreshToken: string }) {
+ if (!body.refreshToken) {
+ throw new BadRequestException('Refresh token is required');
+ }
+ try {
+ return await this.authService.refreshAuth(body.refreshToken);
+ } catch {
+ throw new UnauthorizedException('Invalid refresh token');
+ }
+ }
+
+ @UseGuards(JwtAuthGuard)
+ @Get('me')
+ async me(@Req() req: Request) {
+ const user = (req as any).user;
+ // Check if user has been banned since the JWT was issued
+ try {
+ const perms = await this.rsvpService.getPerms(user.email);
+ if (perms === 'Banned') {
+ throw new UnauthorizedException('Account banned');
+ }
+ } catch (err) {
+ if (err instanceof UnauthorizedException) throw err;
+ // Airtable lookup failed — don't block the response
+ }
+ // Include impersonation context if present so the frontend can show it
+ const result: Record = { ...user };
+ if (user.impersonator_uid) {
+ result.impersonator_uid = user.impersonator_uid;
+ result.impersonator_name = user.impersonator_name;
+ }
+ return result;
+ }
+
+ @UseGuards(JwtAuthGuard)
+ @Get('shipping-eligibility')
+ async shippingEligibility(@Req() req: Request) {
+ const user = (req as any).user;
+ const hasAddress = !!user.has_address;
+ const hasBirthdate = !!user.has_birthdate;
+ const identityVerified = await this.identityService.isVerified({
+ slackId: user.slack_id,
+ email: user.email,
+ });
+ return {
+ hasAddress,
+ hasBirthdate,
+ identityVerified,
+ eligible: hasAddress && hasBirthdate && identityVerified,
+ addressPortalUrl: 'https://auth.hackclub.com/portal/address',
+ identityPortalUrl: 'https://auth.hackclub.com/verifications/document',
+ };
+ }
+
+ @UseGuards(JwtAuthGuard)
+ @Post('rsvp')
+ async rsvpFromSession(@Req() req: Request) {
+ const email = (req as any).user?.email;
+ if (!email) {
+ throw new BadRequestException('No email in token');
+ }
+ return this.rsvpService.createRsvp(email);
+ }
+
+ @UseGuards(JwtAuthGuard)
+ @Get('scope')
+ async checkScope(
+ @Req() req: Request,
+ @Query('scope') scope: string,
+ ) {
+ const email = (req as any).user?.email;
+ if (!email) throw new ForbiddenException();
+
+ const perms = await this.rsvpService.getPerms(email);
+
+ const scopeRequirements: Record = {
+ admin: ['Super Admin'],
+ reviewer: ['Super Admin', 'Reviewer', 'Fraud Reviewer'],
+ audit: ['Super Admin', 'Fraud Reviewer'],
+ };
+
+ const allowed = scopeRequirements[scope];
+ if (!allowed || !perms || !allowed.includes(perms)) {
+ throw new ForbiddenException();
+ }
+
+ return { allowed: true, perms };
+ }
+
+ @Throttle({ default: { limit: 10, ttl: 60000 } })
+ @UseGuards(JwtAuthGuard)
+ @Patch('nickname')
+ async updateNickname(
+ @Req() req: Request,
+ @Body() body: { nickname?: string },
+ ) {
+ const uid = (req as any).user?.uid;
+ if (!uid) throw new UnauthorizedException();
+ const nickname = (body.nickname ?? '').trim();
+ if (!nickname || nickname.length > 50) {
+ throw new BadRequestException('Nickname must be 1–50 characters');
+ }
+ return this.authService.updateNickname(uid, nickname);
+ }
+
+ @Throttle({ default: { limit: 10, ttl: 60000 } })
+ @UseGuards(JwtAuthGuard)
+ @Patch('gender')
+ async updateGender(
+ @Req() req: Request,
+ @Body() body: { gender?: string },
+ ) {
+ const uid = (req as any).user?.uid;
+ if (!uid) throw new UnauthorizedException();
+ const gender = body.gender;
+ if (!gender || !ALLOWED_GENDERS.includes(gender as Gender)) {
+ throw new BadRequestException('Invalid gender value');
+ }
+ return this.authService.updateGender(uid, gender as Gender);
+ }
+
+ @UseGuards(JwtAuthGuard)
+ @Get('intent')
+ async getIntent(@Req() req: Request) {
+ const uid = (req as any).user?.uid;
+ if (!uid) throw new UnauthorizedException();
+ return this.authService.getIntentStatus(uid);
+ }
+
+ @Throttle({ default: { limit: 10, ttl: 60000 } })
+ @UseGuards(JwtAuthGuard)
+ @Post('intent')
+ async setIntent(@Req() req: Request, @Body() body: { choice?: string }) {
+ const uid = (req as any).user?.uid;
+ if (!uid) throw new UnauthorizedException();
+ const choice = body.choice;
+ const allowed = ['Hackathon', 'Shop', 'Browsing', 'Both'];
+ if (!choice || !allowed.includes(choice)) {
+ throw new BadRequestException(`choice must be one of: ${allowed.join(', ')}`);
+ }
+ return this.authService.setIntent(uid, choice);
+ }
+
+ /**
+ * Invalidates the session's refresh token. The proxy clears cookies.
+ */
+ @Post('logout')
+ async logout(@Body() body: { refreshToken?: string }) {
+ if (body.refreshToken) {
+ await this.authService.invalidateSession(body.refreshToken);
+ }
+ return { success: true };
+ }
+}
diff --git a/backend/src/auth/auth.module.ts b/backend/src/auth/auth.module.ts
new file mode 100644
index 0000000..13474f9
--- /dev/null
+++ b/backend/src/auth/auth.module.ts
@@ -0,0 +1,48 @@
+import { Module } from '@nestjs/common';
+import { JwtModule } from '@nestjs/jwt';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
+import { APP_GUARD } from '@nestjs/core';
+import { ConfigModule, ConfigService } from '@nestjs/config';
+import { RsvpModule } from '../rsvp/rsvp.module';
+import { IdentityModule } from '../identity/identity.module';
+import { User } from '../entities/user.entity';
+import { Session } from '../entities/session.entity';
+import { AuthService } from './auth.service';
+import { AuthController } from './auth.controller';
+import { JwtAuthGuard } from './jwt-auth.guard';
+
+@Module({
+ imports: [
+ TypeOrmModule.forFeature([User, Session]),
+ JwtModule.registerAsync({
+ imports: [ConfigModule],
+ inject: [ConfigService],
+ useFactory: (configService: ConfigService) => ({
+ secret: configService.getOrThrow('JWT_SECRET'),
+ signOptions: {
+ expiresIn: '1h',
+ issuer: 'beest',
+ audience: 'beest',
+ },
+ verifyOptions: {
+ issuer: 'beest',
+ audience: 'beest',
+ },
+ }),
+ }),
+ ThrottlerModule.forRoot({
+ throttlers: [{ ttl: 60000, limit: 30 }],
+ }),
+ RsvpModule,
+ IdentityModule,
+ ],
+ controllers: [AuthController],
+ providers: [
+ AuthService,
+ JwtAuthGuard,
+ { provide: APP_GUARD, useClass: ThrottlerGuard },
+ ],
+ exports: [AuthService, JwtAuthGuard],
+})
+export class AuthModule {}
diff --git a/backend/src/auth/auth.service.ts b/backend/src/auth/auth.service.ts
new file mode 100644
index 0000000..4e39933
--- /dev/null
+++ b/backend/src/auth/auth.service.ts
@@ -0,0 +1,489 @@
+import { Injectable, Logger } from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
+import { JwtService } from '@nestjs/jwt';
+import { InjectRepository } from '@nestjs/typeorm';
+import { Repository } from 'typeorm';
+import { createHash, createHmac, randomBytes, timingSafeEqual } from 'crypto';
+import { fetchWithTimeout } from '../fetch.util';
+import { RsvpService } from '../rsvp/rsvp.service';
+import { User } from '../entities/user.entity';
+import { Session } from '../entities/session.entity';
+
+const ALLOWED_REDIRECTS = new Set(['/home', '/tutorial']);
+
+const EMAIL_RE =
+ /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
+
+const REFRESH_TOKEN_EXPIRY_MS = 90 * 24 * 60 * 60 * 1000; // 90 days
+
+export const ALLOWED_GENDERS = [
+ 'male',
+ 'female',
+ 'non_binary_other',
+ 'not_sure',
+ 'prefer_not_to_say',
+] as const;
+export type Gender = (typeof ALLOWED_GENDERS)[number];
+
+@Injectable()
+export class AuthService {
+ private readonly logger = new Logger(AuthService.name);
+ private readonly clientId: string;
+ private readonly clientSecret: string;
+ private readonly redirectUri: string;
+ private readonly jwtSecret: string;
+
+ private readonly authorizeUrl =
+ 'https://auth.hackclub.com/oauth/authorize';
+ private readonly tokenUrl = 'https://auth.hackclub.com/oauth/token';
+ private readonly userinfoUrl = 'https://auth.hackclub.com/oauth/userinfo';
+
+ private readonly scopes = [
+ 'openid',
+ 'email',
+ 'name',
+ 'profile',
+ 'birthdate',
+ 'address',
+ 'verification_status',
+ 'slack_id',
+ 'basic_info',
+ ].join(' ');
+
+ constructor(
+ private configService: ConfigService,
+ private jwtService: JwtService,
+ private rsvpService: RsvpService,
+ @InjectRepository(User)
+ private userRepo: Repository,
+ @InjectRepository(Session)
+ private sessionRepo: Repository,
+ ) {
+ this.clientId = this.configService.getOrThrow('CLIENT_ID');
+ this.clientSecret = this.configService.getOrThrow('CLIENT_SECRET');
+ this.redirectUri = this.configService.get(
+ 'REDIRECT_URI',
+ 'http://localhost:5173/oauth/callback',
+ );
+ this.jwtSecret = this.configService.getOrThrow('JWT_SECRET');
+ }
+
+ private signState(state: string): string {
+ return createHmac('sha256', this.jwtSecret)
+ .update(`hca:${state}`)
+ .digest('hex');
+ }
+
+ startAuth(email?: string): { url: string; state: string } {
+ const state = crypto.randomUUID();
+ const signature = this.signState(state);
+ const signedState = `${state}.${signature}`;
+
+ const sanitizedEmail =
+ email && EMAIL_RE.test(email.trim()) ? email.trim() : undefined;
+
+ const params = new URLSearchParams({
+ client_id: this.clientId,
+ redirect_uri: this.redirectUri,
+ response_type: 'code',
+ scope: this.scopes,
+ state: signedState,
+ return_to: '/join/beest',
+ });
+
+ if (sanitizedEmail) {
+ params.set('login_hint', sanitizedEmail);
+ }
+
+ return {
+ url: `${this.authorizeUrl}?${params.toString()}`,
+ state,
+ };
+ }
+
+ async handleCallback(
+ code: string,
+ returnedSignedState: string,
+ cookieState: string,
+ attribution?: {
+ utm_source?: string | null;
+ utm_medium?: string | null;
+ utm_campaign?: string | null;
+ referrer?: string | null;
+ landing_path?: string | null;
+ },
+ ): Promise<{ token: string; refreshToken: string; redirectTo: string }> {
+ // 1. Verify state
+ const dotIndex = returnedSignedState.lastIndexOf('.');
+ if (dotIndex === -1) {
+ throw new Error('Malformed state parameter');
+ }
+
+ const stateValue = returnedSignedState.substring(0, dotIndex);
+ const signature = returnedSignedState.substring(dotIndex + 1);
+
+ const stateBuffer = Buffer.from(stateValue);
+ const cookieBuffer = Buffer.from(cookieState);
+ if (
+ stateBuffer.length !== cookieBuffer.length ||
+ !timingSafeEqual(stateBuffer, cookieBuffer)
+ ) {
+ throw new Error('State mismatch');
+ }
+
+ const expectedSignature = this.signState(stateValue);
+ const sigBuffer = Buffer.from(signature);
+ const expectedBuffer = Buffer.from(expectedSignature);
+ if (
+ sigBuffer.length !== expectedBuffer.length ||
+ !timingSafeEqual(sigBuffer, expectedBuffer)
+ ) {
+ throw new Error('Invalid state signature');
+ }
+
+ // 2. Exchange code for tokens
+ const tokenResponse = await fetchWithTimeout(this.tokenUrl, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+ body: new URLSearchParams({
+ grant_type: 'authorization_code',
+ code,
+ redirect_uri: this.redirectUri,
+ client_id: this.clientId,
+ client_secret: this.clientSecret,
+ }),
+ });
+
+ if (!tokenResponse.ok) {
+ this.logger.error(`Token exchange failed: ${tokenResponse.status}`);
+ throw new Error('Token exchange failed');
+ }
+
+ const tokens = await tokenResponse.json().catch(() => null);
+ if (!tokens?.access_token) {
+ throw new Error('Invalid token response');
+ }
+
+ // 3. Fetch user info
+ const userinfoResponse = await fetchWithTimeout(this.userinfoUrl, {
+ headers: { Authorization: `Bearer ${tokens.access_token}` },
+ });
+
+ if (!userinfoResponse.ok) {
+ this.logger.error('Failed to fetch user info');
+ throw new Error('Failed to fetch user info');
+ }
+
+ const userinfo = await userinfoResponse.json().catch(() => null);
+ if (!userinfo?.sub) {
+ throw new Error('Invalid userinfo response');
+ }
+
+ // 4. Upsert user in DB (pass HCA tokens so they're persisted encrypted)
+ const user = await this.upsertUser(
+ userinfo,
+ tokens.access_token,
+ tokens.refresh_token,
+ attribution,
+ );
+
+ // 4b. Check if user is banned
+ try {
+ const perms = await this.rsvpService.getPerms(userinfo.email);
+ if (perms === 'Banned') {
+ return {
+ token: '',
+ refreshToken: '',
+ redirectTo: 'https://fraud.hackclub.com/',
+ };
+ }
+ } catch (err) {
+ this.logger.error(`Perms check failed for ${userinfo.sub}: ${err}`);
+ }
+
+ // 5. Submit RSVP
+ let redirectTo = '/home';
+ try {
+ const rsvpResult = await this.rsvpService.createRsvp(userinfo.email);
+ redirectTo = rsvpResult.existing ? '/home' : '/tutorial';
+ } catch (err) {
+ this.logger.error(
+ `RSVP submission failed for user ${userinfo.sub}: ${err}`,
+ );
+ }
+
+ if (!ALLOWED_REDIRECTS.has(redirectTo)) {
+ redirectTo = '/home';
+ }
+
+ // 6. Create session with refresh token
+ const refreshToken = await this.createSession(user.id);
+
+ // 7. Sign JWT — no PII beyond what's needed for display + auth checks
+ const token = this.jwtService.sign({
+ sub: userinfo.sub,
+ uid: user.id,
+ email: userinfo.email,
+ name: userinfo.name,
+ nickname: userinfo.nickname,
+ slack_id: userinfo.slack_id,
+ has_address: user.hasAddress,
+ has_birthdate: user.hasBirthdate,
+ gender: user.gender,
+ });
+
+ return { token, refreshToken, redirectTo };
+ }
+
+ /**
+ * Validates a refresh token, rotates it, and issues a new JWT.
+ */
+ async refreshAuth(
+ refreshToken: string,
+ ): Promise<{ token: string; refreshToken: string }> {
+ const hash = createHash('sha256').update(refreshToken).digest('hex');
+ const session = await this.sessionRepo.findOne({
+ where: { refreshTokenHash: hash },
+ relations: ['user'],
+ });
+
+ if (!session || session.expiresAt < new Date()) {
+ if (session) await this.sessionRepo.remove(session);
+ throw new Error('Invalid or expired refresh token');
+ }
+
+ const user = session.user;
+
+ // Rotate: delete old session, create new one
+ await this.sessionRepo.remove(session);
+ const newRefreshToken = await this.createSession(user.id);
+
+ const token = this.jwtService.sign({
+ sub: user.hcaSub,
+ uid: user.id,
+ email: user.email,
+ name: user.name,
+ nickname: user.nickname,
+ slack_id: user.slackId,
+ has_address: user.hasAddress,
+ has_birthdate: user.hasBirthdate,
+ gender: user.gender,
+ });
+
+ return { token, refreshToken: newRefreshToken };
+ }
+
+ /**
+ * Invalidates a refresh token (logout).
+ */
+ async invalidateSession(refreshToken: string): Promise {
+ const hash = createHash('sha256').update(refreshToken).digest('hex');
+ await this.sessionRepo.delete({ refreshTokenHash: hash });
+ }
+
+ verifyToken(token: string): Record {
+ return this.jwtService.verify(token);
+ }
+
+ /**
+ * Issues a JWT that lets an admin act as the target user.
+ * The token carries the target user's identity but includes
+ * impersonator_uid / impersonator_name so audit logs can attribute actions.
+ */
+ async issueImpersonationToken(
+ targetUserId: string,
+ adminUid: string,
+ adminName: string,
+ ): Promise<{ token: string }> {
+ const user = await this.userRepo.findOne({ where: { id: targetUserId } });
+ if (!user) throw new Error('User not found');
+
+ const token = this.jwtService.sign({
+ sub: user.hcaSub,
+ uid: user.id,
+ email: user.email,
+ name: user.name,
+ nickname: user.nickname,
+ slack_id: user.slackId,
+ has_address: user.hasAddress,
+ has_birthdate: user.hasBirthdate,
+ gender: user.gender,
+ impersonator_uid: adminUid,
+ impersonator_name: adminName,
+ });
+
+ return { token };
+ }
+
+ async updateNickname(
+ userId: string,
+ nickname: string,
+ ): Promise<{ token: string }> {
+ const user = await this.userRepo.findOne({ where: { id: userId } });
+ if (!user) throw new Error('User not found');
+
+ user.nickname = nickname;
+ await this.userRepo.save(user);
+
+ const token = this.jwtService.sign({
+ sub: user.hcaSub,
+ uid: user.id,
+ email: user.email,
+ name: user.name,
+ nickname: user.nickname,
+ slack_id: user.slackId,
+ has_address: user.hasAddress,
+ has_birthdate: user.hasBirthdate,
+ gender: user.gender,
+ });
+
+ return { token };
+ }
+
+ async updateGender(
+ userId: string,
+ gender: Gender,
+ ): Promise<{ token: string }> {
+ const user = await this.userRepo.findOne({ where: { id: userId } });
+ if (!user) throw new Error('User not found');
+
+ user.gender = gender;
+ await this.userRepo.save(user);
+
+ const token = this.jwtService.sign({
+ sub: user.hcaSub,
+ uid: user.id,
+ email: user.email,
+ name: user.name,
+ nickname: user.nickname,
+ slack_id: user.slackId,
+ has_address: user.hasAddress,
+ has_birthdate: user.hasBirthdate,
+ gender: user.gender,
+ });
+
+ return { token };
+ }
+
+ /** Whether the one-time home "hackathon or shop?" prompt still needs answering. */
+ async getIntentStatus(userId: string): Promise<{ needsPrompt: boolean }> {
+ const user = await this.userRepo.findOne({
+ where: { id: userId },
+ select: ['id', 'intent'],
+ });
+ return { needsPrompt: !!user && !user.intent };
+ }
+
+ /**
+ * Records the user's home-prompt answer. Writes Airtable FIRST — only on
+ * success do we set the local flag, so a transient Airtable failure leaves
+ * the prompt showing (it keeps showing until answered) rather than silently
+ * dropping the response.
+ */
+ async setIntent(userId: string, intent: string): Promise<{ success: true }> {
+ const user = await this.userRepo.findOne({ where: { id: userId } });
+ if (!user) throw new Error('User not found');
+
+ await this.rsvpService.setIntent(user.email, intent);
+
+ user.intent = intent;
+ await this.userRepo.save(user);
+ return { success: true };
+ }
+
+ private async upsertUser(
+ userinfo: Record,
+ hcaAccessToken?: string,
+ hcaRefreshToken?: string,
+ attribution?: {
+ utm_source?: string | null;
+ utm_medium?: string | null;
+ utm_campaign?: string | null;
+ referrer?: string | null;
+ landing_path?: string | null;
+ },
+ ): Promise {
+ const hasAddress = !!(
+ userinfo.address ||
+ (Array.isArray(userinfo.addresses) && userinfo.addresses.length > 0)
+ );
+ const hasBirthdate = !!(
+ userinfo.birthdate && userinfo.birthdate.trim() !== ''
+ );
+
+ const trim = (v: string | null | undefined): string | undefined =>
+ typeof v === 'string' && v.trim() !== ''
+ ? v.trim().slice(0, 255)
+ : undefined;
+
+ let user = await this.userRepo.findOne({
+ where: { hcaSub: userinfo.sub },
+ });
+
+ if (user) {
+ user.email = userinfo.email;
+ user.name = userinfo.name;
+ user.nickname = userinfo.nickname;
+ user.slackId = userinfo.slack_id;
+ user.hasAddress = hasAddress;
+ user.hasBirthdate = hasBirthdate;
+ if (hcaAccessToken) user.hcaAccessToken = hcaAccessToken;
+ if (hcaRefreshToken) user.hcaRefreshToken = hcaRefreshToken;
+ return this.userRepo.save(user);
+ }
+
+ // New user — attempt insert. If a concurrent request already inserted
+ // this hca_sub, catch the unique constraint violation and update instead.
+ try {
+ user = this.userRepo.create({
+ hcaSub: userinfo.sub,
+ email: userinfo.email,
+ name: userinfo.name,
+ nickname: userinfo.nickname,
+ slackId: userinfo.slack_id,
+ hasAddress,
+ hasBirthdate,
+ hcaAccessToken: hcaAccessToken ?? undefined,
+ hcaRefreshToken: hcaRefreshToken ?? undefined,
+ utmSource: trim(attribution?.utm_source),
+ utmMedium: trim(attribution?.utm_medium),
+ utmCampaign: trim(attribution?.utm_campaign),
+ referrer: trim(attribution?.referrer),
+ landingPath: trim(attribution?.landing_path),
+ });
+ return await this.userRepo.save(user);
+ } catch (err: any) {
+ if (err?.code === '23505') {
+ // Unique violation — the other request won the insert race
+ user = await this.userRepo.findOne({
+ where: { hcaSub: userinfo.sub },
+ });
+ if (!user) throw err; // shouldn't happen, but safety net
+ user.email = userinfo.email;
+ user.name = userinfo.name;
+ user.nickname = userinfo.nickname;
+ user.slackId = userinfo.slack_id;
+ user.hasAddress = hasAddress;
+ user.hasBirthdate = hasBirthdate;
+ if (hcaAccessToken) user.hcaAccessToken = hcaAccessToken;
+ if (hcaRefreshToken) user.hcaRefreshToken = hcaRefreshToken;
+ return this.userRepo.save(user);
+ }
+ throw err;
+ }
+ }
+
+ private async createSession(userId: string): Promise {
+ const refreshToken = randomBytes(48).toString('base64url');
+ const hash = createHash('sha256').update(refreshToken).digest('hex');
+
+ const session = this.sessionRepo.create({
+ userId,
+ refreshTokenHash: hash,
+ expiresAt: new Date(Date.now() + REFRESH_TOKEN_EXPIRY_MS),
+ });
+ await this.sessionRepo.save(session);
+
+ return refreshToken;
+ }
+}
diff --git a/backend/src/auth/jwt-auth.guard.ts b/backend/src/auth/jwt-auth.guard.ts
new file mode 100644
index 0000000..1c12c1b
--- /dev/null
+++ b/backend/src/auth/jwt-auth.guard.ts
@@ -0,0 +1,29 @@
+import {
+ CanActivate,
+ ExecutionContext,
+ Injectable,
+ UnauthorizedException,
+} from '@nestjs/common';
+import { AuthService } from './auth.service';
+
+@Injectable()
+export class JwtAuthGuard implements CanActivate {
+ constructor(private authService: AuthService) {}
+
+ canActivate(context: ExecutionContext): boolean {
+ const request = context.switchToHttp().getRequest();
+ const authHeader = request.headers.authorization;
+
+ if (!authHeader?.startsWith('Bearer ')) {
+ throw new UnauthorizedException();
+ }
+
+ try {
+ const token = authHeader.split(' ')[1];
+ request.user = this.authService.verifyToken(token);
+ return true;
+ } catch {
+ throw new UnauthorizedException();
+ }
+ }
+}
diff --git a/backend/src/crypto.util.ts b/backend/src/crypto.util.ts
new file mode 100644
index 0000000..ada776b
--- /dev/null
+++ b/backend/src/crypto.util.ts
@@ -0,0 +1,58 @@
+import {
+ createCipheriv,
+ createDecipheriv,
+ randomBytes,
+} from 'crypto';
+import type { ValueTransformer } from 'typeorm';
+
+const ALGO = 'aes-256-gcm';
+const IV_LENGTH = 12;
+
+export function encrypt(plaintext: string, keyHex: string): string {
+ const key = Buffer.from(keyHex, 'hex');
+ const iv = randomBytes(IV_LENGTH);
+ const cipher = createCipheriv(ALGO, key, iv);
+
+ const encrypted = Buffer.concat([
+ cipher.update(plaintext, 'utf8'),
+ cipher.final(),
+ ]);
+ const tag = cipher.getAuthTag();
+
+ // iv.tag.ciphertext — all base64url for safe storage
+ return `${iv.toString('base64')}.${tag.toString('base64')}.${encrypted.toString('base64')}`;
+}
+
+export function decrypt(encoded: string, keyHex: string): string {
+ const key = Buffer.from(keyHex, 'hex');
+ const parts = encoded.split('.');
+ if (parts.length !== 3) throw new Error('Malformed encrypted value');
+
+ const iv = Buffer.from(parts[0], 'base64');
+ const tag = Buffer.from(parts[1], 'base64');
+ const encrypted = Buffer.from(parts[2], 'base64');
+
+ const decipher = createDecipheriv(ALGO, key, iv);
+ decipher.setAuthTag(tag);
+
+ return decipher.update(encrypted).toString('utf8') + decipher.final('utf8');
+}
+
+/**
+ * TypeORM column transformer that encrypts on write and decrypts on read.
+ * Reads DB_ENCRYPTION_KEY from process.env (available after ConfigModule.forRoot).
+ */
+export const encryptedTransformer: ValueTransformer = {
+ to(value: string | null): string | null {
+ if (!value) return value;
+ const key = process.env.DB_ENCRYPTION_KEY;
+ if (!key) throw new Error('DB_ENCRYPTION_KEY is not set');
+ return encrypt(value, key);
+ },
+ from(value: string | null): string | null {
+ if (!value) return value;
+ const key = process.env.DB_ENCRYPTION_KEY;
+ if (!key) throw new Error('DB_ENCRYPTION_KEY is not set');
+ return decrypt(value, key);
+ },
+};
diff --git a/backend/src/data-source.ts b/backend/src/data-source.ts
new file mode 100644
index 0000000..d42ee02
--- /dev/null
+++ b/backend/src/data-source.ts
@@ -0,0 +1,13 @@
+import 'dotenv/config';
+import { DataSource } from 'typeorm';
+
+/**
+ * Standalone DataSource used by the TypeORM CLI for migrations.
+ * Reads DATABASE_URL from the environment (loaded via dotenv above).
+ */
+export default new DataSource({
+ type: 'postgres',
+ url: process.env.DATABASE_URL,
+ entities: ['src/entities/*.ts'],
+ migrations: ['src/migrations/*.ts'],
+});
diff --git a/backend/src/devlogs/create-devlog.dto.ts b/backend/src/devlogs/create-devlog.dto.ts
new file mode 100644
index 0000000..8736a57
--- /dev/null
+++ b/backend/src/devlogs/create-devlog.dto.ts
@@ -0,0 +1,8 @@
+export class CreateDevlogDto {
+ title: string;
+ text: string;
+ /** Required: every devlog must be attached to one of the user's projects. */
+ projectId: string;
+ /** Base64 data URIs (data:image/png;base64,...). Max 4 images. */
+ images?: string[];
+}
diff --git a/backend/src/devlogs/devlogs.controller.ts b/backend/src/devlogs/devlogs.controller.ts
new file mode 100644
index 0000000..0d08d8e
--- /dev/null
+++ b/backend/src/devlogs/devlogs.controller.ts
@@ -0,0 +1,51 @@
+import {
+ Controller,
+ Get,
+ Post,
+ Delete,
+ Param,
+ Body,
+ Req,
+ UseGuards,
+ UnauthorizedException,
+} from '@nestjs/common';
+import { Throttle } from '@nestjs/throttler';
+import type { Request } from 'express';
+import { JwtAuthGuard } from '../auth/jwt-auth.guard';
+import { DevlogsService } from './devlogs.service';
+import { CreateDevlogDto } from './create-devlog.dto';
+
+@Controller('api/devlogs')
+export class DevlogsController {
+ constructor(private readonly devlogsService: DevlogsService) {}
+
+ /** List the current user's devlogs, newest first. */
+ @Throttle({ default: { limit: 30, ttl: 60000 } })
+ @UseGuards(JwtAuthGuard)
+ @Get()
+ async list(@Req() req: Request) {
+ const user = (req as any).user;
+ if (!user?.uid) throw new UnauthorizedException('No user identity');
+ return this.devlogsService.findByUser(user.uid);
+ }
+
+ /** Create a new devlog. Optional image upload via base64 data URIs. */
+ @Throttle({ default: { limit: 10, ttl: 60000 } })
+ @UseGuards(JwtAuthGuard)
+ @Post()
+ async create(@Req() req: Request, @Body() dto: CreateDevlogDto) {
+ const user = (req as any).user;
+ if (!user?.uid) throw new UnauthorizedException('No user identity');
+ return this.devlogsService.create(user.uid, dto);
+ }
+
+ /** Delete one of the current user's devlogs. */
+ @Throttle({ default: { limit: 10, ttl: 60000 } })
+ @UseGuards(JwtAuthGuard)
+ @Delete(':id')
+ async remove(@Param('id') id: string, @Req() req: Request) {
+ const user = (req as any).user;
+ if (!user?.uid) throw new UnauthorizedException('No user identity');
+ return this.devlogsService.deleteOwn(user.uid, id);
+ }
+}
diff --git a/backend/src/devlogs/devlogs.module.ts b/backend/src/devlogs/devlogs.module.ts
new file mode 100644
index 0000000..0652318
--- /dev/null
+++ b/backend/src/devlogs/devlogs.module.ts
@@ -0,0 +1,20 @@
+import { Module } from '@nestjs/common';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { AuthModule } from '../auth/auth.module';
+import { AuditLogModule } from '../audit-log/audit-log.module';
+import { Devlog } from '../entities/devlog.entity';
+import { Project } from '../entities/project.entity';
+import { DevlogsController } from './devlogs.controller';
+import { DevlogsService } from './devlogs.service';
+
+@Module({
+ imports: [
+ AuthModule,
+ AuditLogModule,
+ TypeOrmModule.forFeature([Devlog, Project]),
+ ],
+ controllers: [DevlogsController],
+ providers: [DevlogsService],
+ exports: [DevlogsService],
+})
+export class DevlogsModule {}
diff --git a/backend/src/devlogs/devlogs.service.ts b/backend/src/devlogs/devlogs.service.ts
new file mode 100644
index 0000000..29a03da
--- /dev/null
+++ b/backend/src/devlogs/devlogs.service.ts
@@ -0,0 +1,295 @@
+import {
+ Injectable,
+ BadRequestException,
+ ForbiddenException,
+ NotFoundException,
+ Logger,
+} from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
+import { InjectRepository } from '@nestjs/typeorm';
+import { Repository } from 'typeorm';
+import { Devlog } from '../entities/devlog.entity';
+import { Project } from '../entities/project.entity';
+import { fetchWithTimeout } from '../fetch.util';
+import { AuditLogService } from '../audit-log/audit-log.service';
+import { CreateDevlogDto } from './create-devlog.dto';
+
+const CDN_UPLOAD_URL = 'https://cdn.hackclub.com/api/v4/upload';
+
+const MIME_EXTENSIONS: Record = {
+ 'image/png': 'png',
+ 'image/jpeg': 'jpg',
+ 'image/gif': 'gif',
+ 'image/webp': 'webp',
+};
+
+const IMAGE_SIGNATURES: { mime: string; b64Prefix: string }[] = [
+ { mime: 'image/png', b64Prefix: 'iVBOR' },
+ { mime: 'image/jpeg', b64Prefix: '/9j/' },
+ { mime: 'image/gif', b64Prefix: 'R0lGOD' },
+ { mime: 'image/webp', b64Prefix: 'UklGR' },
+];
+
+const MAX_IMAGES_PER_DEVLOG = 4;
+const MAX_TITLE_LEN = 120;
+const MAX_TEXT_LEN = 5000;
+
+@Injectable()
+export class DevlogsService {
+ private readonly logger = new Logger(DevlogsService.name);
+ private readonly cdnApiKey: string;
+
+ constructor(
+ private configService: ConfigService,
+ private auditLogService: AuditLogService,
+ @InjectRepository(Devlog) private devlogRepo: Repository,
+ @InjectRepository(Project) private projectRepo: Repository,
+ ) {
+ this.cdnApiKey = this.configService.getOrThrow('CDN_API_KEY');
+ }
+
+ /* ---------------------------------------------------------------- */
+ /* Public API */
+ /* ---------------------------------------------------------------- */
+
+ async create(userId: string, dto: CreateDevlogDto) {
+ const title = this.requireTitle(dto.title);
+ const text = this.requireText(dto.text);
+ const projectId = await this.requireProjectId(dto.projectId, userId);
+
+ const validated = this.validateImages(dto.images);
+ const imageUrls = await this.uploadImages(validated);
+
+ const devlog = this.devlogRepo.create({
+ userId,
+ projectId,
+ title,
+ text,
+ imageUrls,
+ });
+ const saved = await this.devlogRepo.save(devlog);
+
+ await this.auditLogService.log(
+ userId,
+ 'devlog_created',
+ `Created devlog "${title}" (${text.length} chars) on project ${projectId}`,
+ );
+
+ return this.toPublic(saved);
+ }
+
+ async findByUser(userId: string) {
+ const rows = await this.devlogRepo.find({
+ where: { userId },
+ order: { createdAt: 'DESC' },
+ });
+ return rows.map((d) => this.toPublic(d));
+ }
+
+ async findByProject(projectId: string) {
+ const rows = await this.devlogRepo.find({
+ where: { projectId },
+ order: { createdAt: 'ASC' },
+ relations: ['user'],
+ });
+ return rows.map((d) => ({
+ id: d.id,
+ projectId: d.projectId,
+ userId: d.userId,
+ userName: d.user?.name ?? null,
+ title: d.title,
+ text: d.text,
+ imageUrls: d.imageUrls ?? [],
+ createdAt: d.createdAt,
+ }));
+ }
+
+ async deleteOwn(userId: string, id: string) {
+ const devlog = await this.devlogRepo.findOne({ where: { id } });
+ if (!devlog) throw new NotFoundException('Devlog not found');
+ if (devlog.userId !== userId) {
+ throw new ForbiddenException('You can only delete your own devlogs');
+ }
+ await this.devlogRepo.delete({ id });
+ await this.auditLogService.log(userId, 'devlog_deleted', `Deleted devlog ${id}`);
+ return { ok: true };
+ }
+
+ /* ---------------------------------------------------------------- */
+ /* Validation helpers */
+ /* ---------------------------------------------------------------- */
+
+ /**
+ * Strips characters that could be used for HTML / SQL / script injection.
+ * Devlogs preserve newlines (unlike single-line project fields).
+ */
+ private sanitize(raw: string): string {
+ return String(raw)
+ .replace(/[<>"'`\\]/g, '') // strip injection-relevant chars (keep & for ampersand-using prose)
+ .replace(/\0/g, '') // strip null bytes
+ .replace(/\r\n/g, '\n')
+ .trim();
+ }
+
+ private requireText(value: unknown): string {
+ if (!value || typeof value !== 'string') {
+ throw new BadRequestException('text is required');
+ }
+ const clean = this.sanitize(value).slice(0, MAX_TEXT_LEN);
+ if (clean.length === 0) {
+ throw new BadRequestException('text is required');
+ }
+ return clean;
+ }
+
+ private requireTitle(value: unknown): string {
+ if (!value || typeof value !== 'string') {
+ throw new BadRequestException('title is required');
+ }
+ // Titles are single-line — collapse any newlines that snuck in.
+ const oneLine = String(value).replace(/[\r\n]+/g, ' ');
+ const clean = this.sanitize(oneLine).slice(0, MAX_TITLE_LEN);
+ if (clean.length === 0) {
+ throw new BadRequestException('title is required');
+ }
+ return clean;
+ }
+
+ /**
+ * Required: returns the project id if it exists and is owned by the user.
+ * Throws BadRequest if missing, malformed, or not owned by the user.
+ */
+ private async requireProjectId(
+ projectId: string | null | undefined,
+ userId: string,
+ ): Promise {
+ if (!projectId || typeof projectId !== 'string') {
+ throw new BadRequestException('projectId is required');
+ }
+ const trimmed = projectId.trim();
+ if (trimmed.length === 0) {
+ throw new BadRequestException('projectId is required');
+ }
+
+ // UUID format check
+ if (
+ !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
+ trimmed,
+ )
+ ) {
+ throw new BadRequestException('projectId is not a valid id');
+ }
+
+ const project = await this.projectRepo.findOne({
+ where: { id: trimmed, userId },
+ select: ['id'],
+ });
+ if (!project) {
+ throw new BadRequestException('projectId not found or not yours');
+ }
+ return project.id;
+ }
+
+ private validateImages(
+ images: string[] | undefined,
+ ): { mime: string; buffer: Buffer }[] {
+ if (!images || !Array.isArray(images)) return [];
+
+ const items = images.slice(0, MAX_IMAGES_PER_DEVLOG);
+ const results: { mime: string; buffer: Buffer }[] = [];
+
+ for (let i = 0; i < items.length; i++) {
+ const raw = items[i];
+ if (!raw || typeof raw !== 'string') continue;
+
+ const match = raw.match(
+ /^data:(image\/(?:png|jpeg|gif|webp));base64,(.+)$/,
+ );
+ if (!match) {
+ throw new BadRequestException(
+ `Image ${i + 1} must be a PNG, JPEG, GIF, or WebP image`,
+ );
+ }
+
+ const declaredMime = match[1];
+ const b64Data = match[2];
+
+ const buffer = Buffer.from(b64Data, 'base64');
+
+ const sig = IMAGE_SIGNATURES.find((s) => s.mime === declaredMime);
+ if (!sig || !b64Data.startsWith(sig.b64Prefix)) {
+ throw new BadRequestException(
+ `Image ${i + 1} content does not match its declared type (${declaredMime})`,
+ );
+ }
+
+ // 8 MB hard cap per image
+ if (buffer.length > 8 * 1024 * 1024) {
+ throw new BadRequestException(
+ `Image ${i + 1} is too large (max 8 MB)`,
+ );
+ }
+
+ results.push({ mime: declaredMime, buffer });
+ }
+
+ return results;
+ }
+
+ private async uploadImages(
+ items: { mime: string; buffer: Buffer }[],
+ ): Promise {
+ const urls: string[] = [];
+ for (let i = 0; i < items.length; i++) {
+ const { mime, buffer } = items[i];
+ const ext = MIME_EXTENSIONS[mime] ?? 'bin';
+ const filename = `devlog-${Date.now()}-${i + 1}.${ext}`;
+ const blob = new Blob([new Uint8Array(buffer)], { type: mime });
+ const formData = new FormData();
+ formData.append('file', blob, filename);
+
+ let res: Response;
+ try {
+ res = await fetchWithTimeout(CDN_UPLOAD_URL, {
+ method: 'POST',
+ headers: { Authorization: `Bearer ${this.cdnApiKey}` },
+ body: formData,
+ });
+ } catch (err) {
+ this.logger.error(`CDN upload network error for image ${i + 1}: ${err}`);
+ throw new BadRequestException(
+ `Image upload failed — the CDN is unreachable. Try again without images.`,
+ );
+ }
+
+ if (!res.ok) {
+ const text = await res.text().catch(() => '');
+ this.logger.error(`CDN upload failed (${res.status}): ${text}`);
+ throw new BadRequestException(
+ `Failed to upload image ${i + 1}. Please try again.`,
+ );
+ }
+
+ const data = await res.json().catch(() => null);
+ if (!data?.url) {
+ this.logger.error(`CDN upload returned no URL for image ${i + 1}`);
+ throw new BadRequestException(
+ `Failed to upload image ${i + 1}. Please try again.`,
+ );
+ }
+ urls.push(data.url as string);
+ }
+ return urls;
+ }
+
+ private toPublic(d: Devlog) {
+ return {
+ id: d.id,
+ projectId: d.projectId,
+ title: d.title,
+ text: d.text,
+ imageUrls: d.imageUrls ?? [],
+ createdAt: d.createdAt,
+ };
+ }
+}
diff --git a/backend/src/entities/audit-log.entity.ts b/backend/src/entities/audit-log.entity.ts
new file mode 100644
index 0000000..d6aae9e
--- /dev/null
+++ b/backend/src/entities/audit-log.entity.ts
@@ -0,0 +1,58 @@
+import {
+ Entity,
+ PrimaryGeneratedColumn,
+ Column,
+ CreateDateColumn,
+ ManyToOne,
+ JoinColumn,
+} from 'typeorm';
+import { User } from './user.entity';
+
+export const AUDIT_ACTIONS = [
+ 'project_created',
+ 'project_updated',
+ 'project_submitted',
+ 'project_deleted',
+ 'hackatime_connected',
+ 'hackatime_ownership_failed',
+ 'ban_reverted',
+ 'rsvp_submitted',
+ 'admin_ban',
+ 'admin_perms_change',
+ 'admin_pipes_adjust',
+ 'project_reviewed',
+ 'admin_impersonate',
+ 'admin_resync_airtable',
+ 'shop_purchase',
+ 'order_fulfilled',
+ 'order_refunded',
+ 'order_merged',
+ 'devlog_created',
+ 'devlog_deleted',
+ 'hcb_connected',
+ 'card_grant_issued',
+] as const;
+
+export type AuditAction = (typeof AUDIT_ACTIONS)[number];
+
+@Entity('audit_logs')
+export class AuditLog {
+ @PrimaryGeneratedColumn('uuid')
+ id: string;
+
+ @Column({ name: 'user_id' })
+ userId: string;
+
+ @ManyToOne(() => User, { onDelete: 'CASCADE' })
+ @JoinColumn({ name: 'user_id' })
+ user: User;
+
+ @Column({ length: 50 })
+ action: string;
+
+ @Column({ length: 255 })
+ label: string;
+
+ @CreateDateColumn({ name: 'created_at' })
+ createdAt: Date;
+}
diff --git a/backend/src/entities/comment.entity.ts b/backend/src/entities/comment.entity.ts
new file mode 100644
index 0000000..2625f73
--- /dev/null
+++ b/backend/src/entities/comment.entity.ts
@@ -0,0 +1,36 @@
+import {
+ Entity,
+ PrimaryGeneratedColumn,
+ Column,
+ CreateDateColumn,
+ ManyToOne,
+ JoinColumn,
+} from 'typeorm';
+import { Project } from './project.entity';
+import { User } from './user.entity';
+
+@Entity('comments')
+export class Comment {
+ @PrimaryGeneratedColumn('uuid')
+ id: string;
+
+ @Column({ name: 'project_id' })
+ projectId: string;
+
+ @ManyToOne(() => Project, { onDelete: 'CASCADE' })
+ @JoinColumn({ name: 'project_id' })
+ project: Project;
+
+ @Column({ name: 'user_id' })
+ userId: string;
+
+ @ManyToOne(() => User, { onDelete: 'CASCADE' })
+ @JoinColumn({ name: 'user_id' })
+ user: User;
+
+ @Column({ type: 'varchar', length: 500 })
+ body: string;
+
+ @CreateDateColumn({ name: 'created_at' })
+ createdAt: Date;
+}
diff --git a/backend/src/entities/devlog.entity.ts b/backend/src/entities/devlog.entity.ts
new file mode 100644
index 0000000..7287c77
--- /dev/null
+++ b/backend/src/entities/devlog.entity.ts
@@ -0,0 +1,67 @@
+import {
+ Entity,
+ PrimaryGeneratedColumn,
+ Column,
+ CreateDateColumn,
+ ManyToOne,
+ JoinColumn,
+} from 'typeorm';
+import { User } from './user.entity';
+import { Project } from './project.entity';
+
+/**
+ * A user's hourly devlog entry. Optionally linked to one of their projects so
+ * reviewers can see hour-by-hour progress on the project review screen.
+ */
+@Entity('devlogs')
+export class Devlog {
+ @PrimaryGeneratedColumn('uuid')
+ id: string;
+
+ @Column({ name: 'user_id' })
+ userId: string;
+
+ @ManyToOne(() => User, { onDelete: 'CASCADE' })
+ @JoinColumn({ name: 'user_id' })
+ user: User;
+
+ @Column({ name: 'project_id', nullable: true })
+ projectId: string | null;
+
+ @ManyToOne(() => Project, { onDelete: 'SET NULL', nullable: true })
+ @JoinColumn({ name: 'project_id' })
+ project: Project | null;
+
+ @Column({ length: 120 })
+ title: string;
+
+ @Column({ type: 'text' })
+ text: string;
+
+ /**
+ * CDN URLs for any uploaded images. Stored as a JSON-serialised string
+ * for portability (matches the pattern used by Project.hackatimeProjectName).
+ */
+ @Column({
+ type: 'text',
+ name: 'image_urls',
+ nullable: true,
+ transformer: {
+ to: (value: string[] | null) =>
+ value && value.length > 0 ? JSON.stringify(value) : null,
+ from: (value: string | null) => {
+ if (!value) return [];
+ try {
+ const parsed = JSON.parse(value);
+ return Array.isArray(parsed) ? parsed : [];
+ } catch {
+ return [];
+ }
+ },
+ },
+ })
+ imageUrls: string[];
+
+ @CreateDateColumn({ name: 'created_at' })
+ createdAt: Date;
+}
diff --git a/backend/src/entities/fraud-review.entity.ts b/backend/src/entities/fraud-review.entity.ts
new file mode 100644
index 0000000..40d4336
--- /dev/null
+++ b/backend/src/entities/fraud-review.entity.ts
@@ -0,0 +1,58 @@
+import {
+ Entity,
+ PrimaryGeneratedColumn,
+ Column,
+ CreateDateColumn,
+ UpdateDateColumn,
+ OneToOne,
+ JoinColumn,
+} from 'typeorm';
+import { Project } from './project.entity';
+
+/**
+ * Tracks a beest project's submission to the joe.fraud first-pass fraud review.
+ * One row per project (unique on project_id). Created when a beest reviewer
+ * approves a project — the project then sits in `fraud_pending` until the
+ * background poller observes joe.fraud's verdict.
+ */
+@Entity('fraud_reviews')
+export class FraudReview {
+ @PrimaryGeneratedColumn('uuid')
+ id: string;
+
+ @Column({ name: 'project_id', unique: true })
+ projectId: string;
+
+ @OneToOne(() => Project, { onDelete: 'CASCADE' })
+ @JoinColumn({ name: 'project_id' })
+ project: Project;
+
+ // joe.fraud's project UUID. Null if the create call hasn't succeeded yet —
+ // the poller retries until it has one.
+ @Column({ type: 'varchar', name: 'remote_project_id', length: 64, nullable: true })
+ remoteProjectId: string | null;
+
+ // 'pending' | 'complete' (mirrors joe.fraud's project.status)
+ @Column({ length: 20, default: 'pending' })
+ status: string;
+
+ @Column({ type: 'integer', name: 'trust_score', nullable: true })
+ trustScore: number | null;
+
+ @Column({ type: 'text', nullable: true })
+ justification: string | null;
+
+ // Whether we've already POSTed the final outcome to joe.fraud after fraud
+ // passed. Skipped for fraud-rejected projects (their API forbids it).
+ @Column({ type: 'boolean', name: 'outcome_recorded', default: false })
+ outcomeRecorded: boolean;
+
+ @Column({ type: 'timestamp', name: 'reviewed_at', nullable: true })
+ reviewedAt: Date | null;
+
+ @CreateDateColumn({ name: 'created_at' })
+ createdAt: Date;
+
+ @UpdateDateColumn({ name: 'updated_at' })
+ updatedAt: Date;
+}
diff --git a/backend/src/entities/fulfillment-update.entity.ts b/backend/src/entities/fulfillment-update.entity.ts
new file mode 100644
index 0000000..bf7ce71
--- /dev/null
+++ b/backend/src/entities/fulfillment-update.entity.ts
@@ -0,0 +1,39 @@
+import {
+ Entity,
+ PrimaryGeneratedColumn,
+ Column,
+ CreateDateColumn,
+ ManyToOne,
+ JoinColumn,
+} from 'typeorm';
+import { User } from './user.entity';
+import { Order } from './order.entity';
+
+@Entity('fulfillment_updates')
+export class FulfillmentUpdate {
+ @PrimaryGeneratedColumn('uuid')
+ id: string;
+
+ @Column({ name: 'user_id' })
+ userId: string;
+
+ @ManyToOne(() => User, { onDelete: 'CASCADE' })
+ @JoinColumn({ name: 'user_id' })
+ user: User;
+
+ @Column({ name: 'order_id' })
+ orderId: string;
+
+ @ManyToOne(() => Order, { onDelete: 'CASCADE' })
+ @JoinColumn({ name: 'order_id' })
+ order: Order;
+
+ @Column({ length: 500 })
+ message: string;
+
+ @Column({ name: 'is_read', default: false })
+ isRead: boolean;
+
+ @CreateDateColumn({ name: 'created_at' })
+ createdAt: Date;
+}
diff --git a/backend/src/entities/hcb-credential.entity.ts b/backend/src/entities/hcb-credential.entity.ts
new file mode 100644
index 0000000..23498e0
--- /dev/null
+++ b/backend/src/entities/hcb-credential.entity.ts
@@ -0,0 +1,51 @@
+import {
+ Entity,
+ PrimaryColumn,
+ Column,
+ CreateDateColumn,
+ UpdateDateColumn,
+} from 'typeorm';
+import { encryptedTransformer } from '../crypto.util';
+
+/**
+ * Single-row store for the HCB OAuth connection used to issue card grants.
+ *
+ * There is exactly one connection for the whole app (one fixed issuing org),
+ * so the row is keyed by a constant id. Tokens are stored encrypted at rest
+ * via the AES-256-GCM column transformer — they must never be logged or sent
+ * to the browser.
+ */
+@Entity('hcb_credentials')
+export class HcbCredential {
+ // Constant primary key — there is only ever one connection.
+ static readonly SINGLETON_ID = 'singleton';
+
+ @PrimaryColumn({ type: 'varchar', length: 32 })
+ id: string;
+
+ @Column({ name: 'access_token', type: 'text', transformer: encryptedTransformer })
+ accessToken: string;
+
+ @Column({ name: 'refresh_token', type: 'text', transformer: encryptedTransformer })
+ refreshToken: string;
+
+ // When the current access token expires (UTC).
+ @Column({ name: 'expires_at', type: 'timestamptz' })
+ expiresAt: Date;
+
+ @Column({ type: 'varchar', length: 255, nullable: true })
+ scope: string | null;
+
+ // Audit trail: which super admin established / last refreshed the connection.
+ @Column({ name: 'connected_by_user_id', type: 'uuid', nullable: true })
+ connectedByUserId: string | null;
+
+ @Column({ name: 'connected_by_email', type: 'varchar', length: 320, nullable: true })
+ connectedByEmail: string | null;
+
+ @CreateDateColumn({ name: 'created_at' })
+ createdAt: Date;
+
+ @UpdateDateColumn({ name: 'updated_at' })
+ updatedAt: Date;
+}
diff --git a/backend/src/entities/news-item.entity.ts b/backend/src/entities/news-item.entity.ts
new file mode 100644
index 0000000..d834500
--- /dev/null
+++ b/backend/src/entities/news-item.entity.ts
@@ -0,0 +1,25 @@
+import {
+ Entity,
+ PrimaryGeneratedColumn,
+ Column,
+ CreateDateColumn,
+ UpdateDateColumn,
+} from 'typeorm';
+
+@Entity('news_items')
+export class NewsItem {
+ @PrimaryGeneratedColumn('uuid')
+ id: string;
+
+ @Column({ length: 500 })
+ text: string;
+
+ @Column({ name: 'display_date', type: 'date' })
+ displayDate: string;
+
+ @CreateDateColumn({ name: 'created_at' })
+ createdAt: Date;
+
+ @UpdateDateColumn({ name: 'updated_at' })
+ updatedAt: Date;
+}
diff --git a/backend/src/entities/order.entity.ts b/backend/src/entities/order.entity.ts
new file mode 100644
index 0000000..0ff545a
--- /dev/null
+++ b/backend/src/entities/order.entity.ts
@@ -0,0 +1,54 @@
+import {
+ Entity,
+ PrimaryGeneratedColumn,
+ Column,
+ CreateDateColumn,
+ UpdateDateColumn,
+ ManyToOne,
+ JoinColumn,
+} from 'typeorm';
+import { User } from './user.entity';
+import { ShopItem } from './shop-item.entity';
+
+@Entity('orders')
+export class Order {
+ @PrimaryGeneratedColumn('uuid')
+ id: string;
+
+ @Column({ name: 'user_id' })
+ userId: string;
+
+ @ManyToOne(() => User, { onDelete: 'CASCADE' })
+ @JoinColumn({ name: 'user_id' })
+ user: User;
+
+ @Column({ name: 'shop_item_id', nullable: true })
+ shopItemId: string | null;
+
+ @ManyToOne(() => ShopItem, { onDelete: 'SET NULL', nullable: true })
+ @JoinColumn({ name: 'shop_item_id' })
+ shopItem: ShopItem;
+
+ @Column({ type: 'integer' })
+ quantity: number;
+
+ @Column({ name: 'pipes_spent', type: 'integer' })
+ pipesSpent: number;
+
+ @Column({ name: 'item_name', length: 200 })
+ itemName: string;
+
+ @Column({ length: 20, default: 'pending' })
+ status: string; // 'pending' | 'fulfilled'
+
+ // Public ID (cdg_…) of the HCB card grant issued for this order, if any.
+ // Acts as a per-order idempotency lock: a non-null value blocks re-granting.
+ @Column({ name: 'hcb_card_grant_id', type: 'varchar', length: 64, nullable: true })
+ hcbCardGrantId: string | null;
+
+ @CreateDateColumn({ name: 'created_at' })
+ createdAt: Date;
+
+ @UpdateDateColumn({ name: 'updated_at' })
+ updatedAt: Date;
+}
diff --git a/backend/src/entities/project-review.entity.ts b/backend/src/entities/project-review.entity.ts
new file mode 100644
index 0000000..3a5c63b
--- /dev/null
+++ b/backend/src/entities/project-review.entity.ts
@@ -0,0 +1,53 @@
+import {
+ Entity,
+ PrimaryGeneratedColumn,
+ Column,
+ CreateDateColumn,
+ ManyToOne,
+ JoinColumn,
+} from 'typeorm';
+import { Project } from './project.entity';
+import { Submission } from './submission.entity';
+import { User } from './user.entity';
+
+@Entity('project_reviews')
+export class ProjectReview {
+ @PrimaryGeneratedColumn('uuid')
+ id: string;
+
+ @Column({ name: 'project_id' })
+ projectId: string;
+
+ @ManyToOne(() => Project, { onDelete: 'CASCADE' })
+ @JoinColumn({ name: 'project_id' })
+ project: Project;
+
+ @Column({ name: 'reviewer_id', nullable: true })
+ reviewerId: string | null;
+
+ @ManyToOne(() => User, { onDelete: 'SET NULL', nullable: true })
+ @JoinColumn({ name: 'reviewer_id' })
+ reviewer: User | null;
+
+ @Column({ name: 'submission_id', nullable: true })
+ submissionId: string | null;
+
+ @ManyToOne(() => Submission, { onDelete: 'CASCADE', nullable: true })
+ @JoinColumn({ name: 'submission_id' })
+ submission: Submission;
+
+ @Column({ length: 20 })
+ status: string;
+
+ @Column({ type: 'text', nullable: true })
+ feedback: string | null;
+
+ @Column({ type: 'text', name: 'internal_note', nullable: true })
+ internalNote: string | null;
+
+ @Column({ type: 'text', name: 'override_justification', nullable: true })
+ overrideJustification: string | null;
+
+ @CreateDateColumn({ name: 'created_at' })
+ createdAt: Date;
+}
diff --git a/backend/src/entities/project.entity.ts b/backend/src/entities/project.entity.ts
new file mode 100644
index 0000000..f6a5556
--- /dev/null
+++ b/backend/src/entities/project.entity.ts
@@ -0,0 +1,110 @@
+import {
+ Entity,
+ PrimaryGeneratedColumn,
+ Column,
+ CreateDateColumn,
+ UpdateDateColumn,
+ ManyToOne,
+ JoinColumn,
+} from 'typeorm';
+import { User } from './user.entity';
+
+export const VALID_PROJECT_STATUSES = [
+ 'unshipped',
+ 'unreviewed',
+ 'fraud_pending',
+ 'changes_needed',
+ 'approved',
+] as const;
+
+export type ProjectStatus = (typeof VALID_PROJECT_STATUSES)[number];
+
+export const VALID_PROJECT_TYPES = [
+ 'web',
+ 'windows',
+ 'mac',
+ 'linux',
+ 'cross-platform',
+ 'python',
+ 'android',
+ 'ios',
+ 'hardware',
+ 'cad',
+ 'other',
+] as const;
+
+export type ProjectType = (typeof VALID_PROJECT_TYPES)[number];
+
+@Entity('projects')
+export class Project {
+ @PrimaryGeneratedColumn('uuid')
+ id: string;
+
+ @Column({ name: 'user_id' })
+ userId: string;
+
+ @ManyToOne(() => User, { onDelete: 'CASCADE' })
+ @JoinColumn({ name: 'user_id' })
+ user: User;
+
+ @Column({ length: 50 })
+ name: string;
+
+ @Column({ length: 300 })
+ description: string;
+
+ @Column({ name: 'project_type', length: 20 })
+ projectType: string;
+
+ @Column({ type: 'varchar', name: 'code_url', length: 2048, nullable: true })
+ codeUrl: string | null;
+
+ @Column({ type: 'varchar', name: 'readme_url', length: 2048, nullable: true })
+ readmeUrl: string | null;
+
+ @Column({ type: 'varchar', name: 'demo_url', length: 2048, nullable: true })
+ demoUrl: string | null;
+
+ @Column({ type: 'varchar', name: 'screenshot_1_url', length: 2048, nullable: true })
+ screenshot1Url: string | null;
+
+ @Column({ type: 'varchar', name: 'screenshot_2_url', length: 2048, nullable: true })
+ screenshot2Url: string | null;
+
+ @Column({ type: 'text', name: 'hackatime_project_name', nullable: true, transformer: {
+ to: (value: string[] | null) => value && value.length > 0 ? JSON.stringify(value) : null,
+ from: (value: string | null) => {
+ if (!value) return [];
+ try { const parsed = JSON.parse(value); return Array.isArray(parsed) ? parsed : [value]; }
+ catch { return [value]; }
+ },
+ }})
+ hackatimeProjectName: string[];
+
+ @Column({ name: 'status', length: 20, default: 'unshipped' })
+ status: string;
+
+ @Column({ type: 'real', name: 'override_hours', nullable: true })
+ overrideHours: number | null;
+
+ @Column({ type: 'real', name: 'internal_hours', nullable: true })
+ internalHours: number | null;
+
+ @Column({ name: 'is_update', default: false })
+ isUpdate: boolean;
+
+ @Column({ type: 'varchar', name: 'other_hc_program', length: 255, nullable: true })
+ otherHcProgram: string | null;
+
+ @Column({ type: 'varchar', name: 'ai_use', length: 200, nullable: true })
+ aiUse: string | null;
+
+ @Column({ type: 'integer', name: 'pipes_granted', default: 0 })
+ pipesGranted: number;
+
+ @CreateDateColumn({ name: 'created_at' })
+ createdAt: Date;
+
+ @UpdateDateColumn({ name: 'updated_at' })
+ updatedAt: Date;
+}
diff --git a/backend/src/entities/session.entity.ts b/backend/src/entities/session.entity.ts
new file mode 100644
index 0000000..06a8231
--- /dev/null
+++ b/backend/src/entities/session.entity.ts
@@ -0,0 +1,31 @@
+import {
+ Entity,
+ PrimaryGeneratedColumn,
+ Column,
+ CreateDateColumn,
+ ManyToOne,
+ JoinColumn,
+} from 'typeorm';
+import { User } from './user.entity';
+
+@Entity('sessions')
+export class Session {
+ @PrimaryGeneratedColumn('uuid')
+ id: string;
+
+ @Column({ name: 'user_id' })
+ userId: string;
+
+ @ManyToOne(() => User, { onDelete: 'CASCADE' })
+ @JoinColumn({ name: 'user_id' })
+ user: User;
+
+ @Column({ unique: true, name: 'refresh_token_hash' })
+ refreshTokenHash: string;
+
+ @Column({ name: 'expires_at' })
+ expiresAt: Date;
+
+ @CreateDateColumn({ name: 'created_at' })
+ createdAt: Date;
+}
diff --git a/backend/src/entities/shop-item.entity.ts b/backend/src/entities/shop-item.entity.ts
new file mode 100644
index 0000000..5d9a8b2
--- /dev/null
+++ b/backend/src/entities/shop-item.entity.ts
@@ -0,0 +1,49 @@
+import {
+ Entity,
+ PrimaryGeneratedColumn,
+ Column,
+ CreateDateColumn,
+ UpdateDateColumn,
+} from 'typeorm';
+
+@Entity('shop_items')
+export class ShopItem {
+ @PrimaryGeneratedColumn('uuid')
+ id: string;
+
+ @Column({ length: 200 })
+ name: string;
+
+ @Column({ length: 500 })
+ description: string;
+
+ @Column({ name: 'image_url', length: 500 })
+ imageUrl: string;
+
+ @Column({ name: 'price_hours', type: 'integer' })
+ priceHours: number;
+
+ @Column({ type: 'integer', nullable: true, default: null })
+ stock: number | null;
+
+ @Column({ name: 'sort_order', type: 'integer', default: 0 })
+ sortOrder: number;
+
+ @Column({ name: 'is_active', type: 'boolean', default: true })
+ isActive: boolean;
+
+ @Column({ name: 'is_featured', type: 'boolean', default: false })
+ isFeatured: boolean;
+
+ @Column({ name: 'detailed_description', type: 'text', nullable: true, default: null })
+ detailedDescription: string | null;
+
+ @Column({ name: 'estimated_ship', type: 'varchar', length: 200, nullable: true, default: null })
+ estimatedShip: string | null;
+
+ @CreateDateColumn({ name: 'created_at' })
+ createdAt: Date;
+
+ @UpdateDateColumn({ name: 'updated_at' })
+ updatedAt: Date;
+}
diff --git a/backend/src/entities/shop-suggestion-vote.entity.ts b/backend/src/entities/shop-suggestion-vote.entity.ts
new file mode 100644
index 0000000..9fee43a
--- /dev/null
+++ b/backend/src/entities/shop-suggestion-vote.entity.ts
@@ -0,0 +1,35 @@
+import {
+ Entity,
+ PrimaryGeneratedColumn,
+ Column,
+ CreateDateColumn,
+ ManyToOne,
+ JoinColumn,
+ Unique,
+} from 'typeorm';
+import { User } from './user.entity';
+import { ShopSuggestion } from './shop-suggestion.entity';
+
+@Entity('shop_suggestion_votes')
+@Unique('UQ_shop_suggestion_votes_user_suggestion', ['userId', 'suggestionId'])
+export class ShopSuggestionVote {
+ @PrimaryGeneratedColumn('uuid')
+ id: string;
+
+ @Column({ name: 'suggestion_id' })
+ suggestionId: string;
+
+ @ManyToOne(() => ShopSuggestion, { onDelete: 'CASCADE' })
+ @JoinColumn({ name: 'suggestion_id' })
+ suggestion: ShopSuggestion;
+
+ @Column({ name: 'user_id' })
+ userId: string;
+
+ @ManyToOne(() => User, { onDelete: 'CASCADE' })
+ @JoinColumn({ name: 'user_id' })
+ user: User;
+
+ @CreateDateColumn({ name: 'created_at' })
+ createdAt: Date;
+}
diff --git a/backend/src/entities/shop-suggestion.entity.ts b/backend/src/entities/shop-suggestion.entity.ts
new file mode 100644
index 0000000..487869b
--- /dev/null
+++ b/backend/src/entities/shop-suggestion.entity.ts
@@ -0,0 +1,28 @@
+import {
+ Entity,
+ PrimaryGeneratedColumn,
+ Column,
+ CreateDateColumn,
+ ManyToOne,
+ JoinColumn,
+} from 'typeorm';
+import { User } from './user.entity';
+
+@Entity('shop_suggestions')
+export class ShopSuggestion {
+ @PrimaryGeneratedColumn('uuid')
+ id: string;
+
+ @Column({ name: 'user_id' })
+ userId: string;
+
+ @ManyToOne(() => User, { onDelete: 'CASCADE' })
+ @JoinColumn({ name: 'user_id' })
+ user: User;
+
+ @Column({ type: 'varchar', length: 200 })
+ text: string;
+
+ @CreateDateColumn({ name: 'created_at' })
+ createdAt: Date;
+}
diff --git a/backend/src/entities/submission.entity.ts b/backend/src/entities/submission.entity.ts
new file mode 100644
index 0000000..299be63
--- /dev/null
+++ b/backend/src/entities/submission.entity.ts
@@ -0,0 +1,54 @@
+import {
+ Entity,
+ PrimaryGeneratedColumn,
+ Column,
+ CreateDateColumn,
+ ManyToOne,
+ JoinColumn,
+} from 'typeorm';
+import { Project } from './project.entity';
+import { User } from './user.entity';
+
+@Entity('submissions')
+export class Submission {
+ @PrimaryGeneratedColumn('uuid')
+ id: string;
+
+ @Column({ name: 'project_id' })
+ projectId: string;
+
+ @ManyToOne(() => Project, { onDelete: 'CASCADE' })
+ @JoinColumn({ name: 'project_id' })
+ project: Project;
+
+ @Column({ name: 'user_id' })
+ userId: string;
+
+ @ManyToOne(() => User, { onDelete: 'CASCADE' })
+ @JoinColumn({ name: 'user_id' })
+ user: User;
+
+ @Column({ type: 'text', name: 'change_description', nullable: true })
+ changeDescription: string | null;
+
+ @Column({ name: 'min_hours_confirmed', default: false })
+ minHoursConfirmed: boolean;
+
+ @Column({ type: 'text', name: 'reviewer_note', nullable: true })
+ reviewerNote: string | null;
+
+ @Column({ length: 20, default: 'unreviewed' })
+ status: string; // 'unreviewed' | 'approved' | 'changes_needed'
+
+ @Column({ type: 'real', name: 'override_hours', nullable: true })
+ overrideHours: number | null;
+
+ @Column({ type: 'real', name: 'internal_hours', nullable: true })
+ internalHours: number | null;
+
+ @Column({ type: 'integer', name: 'pipes_granted', default: 0 })
+ pipesGranted: number;
+
+ @CreateDateColumn({ name: 'created_at' })
+ createdAt: Date;
+}
diff --git a/backend/src/entities/user.entity.ts b/backend/src/entities/user.entity.ts
new file mode 100644
index 0000000..ff57f6c
--- /dev/null
+++ b/backend/src/entities/user.entity.ts
@@ -0,0 +1,97 @@
+import {
+ Entity,
+ PrimaryGeneratedColumn,
+ Column,
+ CreateDateColumn,
+ UpdateDateColumn,
+} from 'typeorm';
+import { encryptedTransformer } from '../crypto.util';
+
+@Entity('users')
+export class User {
+ @PrimaryGeneratedColumn('uuid')
+ id: string;
+
+ @Column({ unique: true, name: 'hca_sub' })
+ hcaSub: string;
+
+ @Column({ type: 'text', transformer: encryptedTransformer })
+ email: string;
+
+ @Column({ nullable: true })
+ name: string;
+
+ @Column({ nullable: true })
+ nickname: string;
+
+ @Column({ nullable: true, name: 'slack_id' })
+ slackId: string;
+
+ @Column({ name: 'two_emails', default: false })
+ twoEmails: boolean;
+
+ @Column({ name: 'has_address', default: false })
+ hasAddress: boolean;
+
+ @Column({ name: 'has_birthdate', default: false })
+ hasBirthdate: boolean;
+
+ @Column({ nullable: true, name: 'hackatime_user_id' })
+ hackatimeUserId: string;
+
+ @Column({ nullable: true })
+ gender: string;
+
+ // Answer to the one-time "here for the hackathon or the shop?" home prompt.
+ // null = not answered yet → the modal keeps showing until they pick one.
+ @Column({ type: 'varchar', length: 20, nullable: true })
+ intent: string | null;
+
+ @Column({
+ nullable: true,
+ name: 'hackatime_token',
+ type: 'text',
+ transformer: encryptedTransformer,
+ })
+ hackatimeToken: string;
+
+ @Column({
+ nullable: true,
+ name: 'hca_access_token',
+ type: 'text',
+ transformer: encryptedTransformer,
+ })
+ hcaAccessToken: string;
+
+ @Column({
+ nullable: true,
+ name: 'hca_refresh_token',
+ type: 'text',
+ transformer: encryptedTransformer,
+ })
+ hcaRefreshToken: string;
+
+ @Column({ type: 'integer', default: 0 })
+ pipes: number;
+
+ @Column({ nullable: true, name: 'utm_source' })
+ utmSource: string;
+
+ @Column({ nullable: true, name: 'utm_medium' })
+ utmMedium: string;
+
+ @Column({ nullable: true, name: 'utm_campaign' })
+ utmCampaign: string;
+
+ @Column({ nullable: true })
+ referrer: string;
+
+ @Column({ nullable: true, name: 'landing_path' })
+ landingPath: string;
+
+ @CreateDateColumn({ name: 'created_at' })
+ createdAt: Date;
+
+ @UpdateDateColumn({ name: 'updated_at' })
+ updatedAt: Date;
+}
diff --git a/backend/src/fetch.util.ts b/backend/src/fetch.util.ts
new file mode 100644
index 0000000..3e1630f
--- /dev/null
+++ b/backend/src/fetch.util.ts
@@ -0,0 +1,16 @@
+/**
+ * Wrapper around fetch with a default timeout.
+ * Prevents hanging requests to external services from blocking the server.
+ */
+export function fetchWithTimeout(
+ url: string | URL,
+ init?: RequestInit & { timeoutMs?: number },
+): Promise {
+ const { timeoutMs = 10000, ...fetchInit } = init ?? {};
+ const controller = new AbortController();
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
+
+ return fetch(url, { ...fetchInit, signal: controller.signal }).finally(() =>
+ clearTimeout(timer),
+ );
+}
diff --git a/backend/src/fraud-review/fraud-review.module.ts b/backend/src/fraud-review/fraud-review.module.ts
new file mode 100644
index 0000000..30163c8
--- /dev/null
+++ b/backend/src/fraud-review/fraud-review.module.ts
@@ -0,0 +1,24 @@
+import { Module } from '@nestjs/common';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { Project } from '../entities/project.entity';
+import { User } from '../entities/user.entity';
+import { Devlog } from '../entities/devlog.entity';
+import { FraudReview } from '../entities/fraud-review.entity';
+import { ProjectReview } from '../entities/project-review.entity';
+import { Submission } from '../entities/submission.entity';
+import { AuditLogModule } from '../audit-log/audit-log.module';
+import { ProjectAirtableSyncModule } from '../projects/project-airtable-sync.module';
+import { RsvpModule } from '../rsvp/rsvp.module';
+import { FraudReviewService } from './fraud-review.service';
+
+@Module({
+ imports: [
+ TypeOrmModule.forFeature([Project, User, Devlog, FraudReview, ProjectReview, Submission]),
+ AuditLogModule,
+ ProjectAirtableSyncModule,
+ RsvpModule,
+ ],
+ providers: [FraudReviewService],
+ exports: [FraudReviewService],
+})
+export class FraudReviewModule {}
diff --git a/backend/src/fraud-review/fraud-review.service.ts b/backend/src/fraud-review/fraud-review.service.ts
new file mode 100644
index 0000000..de1fc1e
--- /dev/null
+++ b/backend/src/fraud-review/fraud-review.service.ts
@@ -0,0 +1,668 @@
+import {
+ Injectable,
+ Logger,
+ OnApplicationBootstrap,
+ OnApplicationShutdown,
+} from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
+import { InjectRepository } from '@nestjs/typeorm';
+import { IsNull, Repository } from 'typeorm';
+import { Project } from '../entities/project.entity';
+import { User } from '../entities/user.entity';
+import { Devlog } from '../entities/devlog.entity';
+import { FraudReview } from '../entities/fraud-review.entity';
+import { ProjectReview } from '../entities/project-review.entity';
+import { Submission } from '../entities/submission.entity';
+import { fetchWithTimeout } from '../fetch.util';
+import { AuditLogService } from '../audit-log/audit-log.service';
+import { ProjectAirtableSyncService } from '../projects/project-airtable-sync.service';
+import { RsvpService } from '../rsvp/rsvp.service';
+
+const POLL_INTERVAL_MS = 5 * 60 * 1000;
+const FRAUD_REJECT_THRESHOLD = 4;
+const USER_FACING_FRAUD_FEEDBACK =
+ 'This project was flagged for review and could not be approved at this time. Please reach out to the organizers if you believe this was in error.';
+
+interface JoeFraudListProject {
+ id: string;
+ organizerPlatformId?: string | null;
+ status: 'pending' | 'complete';
+ review?: {
+ trustScore: number;
+ justification: string;
+ reviewedAt: string;
+ } | null;
+ outcome?: { status: 'approved' | 'rejected' } | null;
+}
+
+interface HardwareJournalEntry {
+ title: string;
+ content: string;
+ timestamp: string;
+ hours: number;
+ images?: string[];
+}
+
+@Injectable()
+export class FraudReviewService implements OnApplicationBootstrap, OnApplicationShutdown {
+ private readonly logger = new Logger(FraudReviewService.name);
+ private readonly apiBaseUrl: string;
+ private readonly apiKey: string | undefined;
+ private readonly eventId: string | undefined;
+ private readonly hackatimeBaseUrl: string;
+ private readonly hackatimeAdminKey: string | undefined;
+ private readonly configured: boolean;
+
+ private pollTimer: NodeJS.Timeout | null = null;
+ private polling = false;
+
+ constructor(
+ private readonly config: ConfigService,
+ @InjectRepository(Project) private readonly projectRepo: Repository,
+ @InjectRepository(User) private readonly userRepo: Repository,
+ @InjectRepository(Devlog) private readonly devlogRepo: Repository,
+ @InjectRepository(FraudReview) private readonly fraudRepo: Repository,
+ @InjectRepository(ProjectReview) private readonly reviewRepo: Repository,
+ @InjectRepository(Submission) private readonly submissionRepo: Repository,
+ private readonly auditLogService: AuditLogService,
+ private readonly airtableSync: ProjectAirtableSyncService,
+ private readonly rsvpService: RsvpService,
+ ) {
+ this.apiBaseUrl = (
+ this.config.get('FRAUD_REVIEW_API_URL') ??
+ 'https://joe.fraud.hackclub.com/api/v1/ysws'
+ ).replace(/\/+$/, '');
+ this.apiKey = this.config.get('FRAUD_REVIEW_API_KEY');
+ this.eventId = this.config.get('FRAUD_REVIEW_EVENT_ID');
+ this.hackatimeBaseUrl = this.config.get(
+ 'HACKATIME_BASE_URL',
+ 'https://hackatime.hackclub.com',
+ );
+ this.hackatimeAdminKey = this.config.get('HACKATIME_ADMIN_API_KEY');
+ this.configured = !!(this.apiKey && this.eventId);
+ if (!this.configured) {
+ this.logger.warn(
+ 'FRAUD_REVIEW_API_KEY/FRAUD_REVIEW_EVENT_ID not set — fraud review integration disabled',
+ );
+ }
+ }
+
+ // ── Lifecycle ──────────────────────────────────────────────────────────────
+
+ onApplicationBootstrap() {
+ return;
+ }
+
+ onApplicationShutdown() {
+ if (this.pollTimer) {
+ clearInterval(this.pollTimer);
+ this.pollTimer = null;
+ }
+ }
+
+ // ── Public API (called from AdminService when a beest reviewer approves) ───
+
+ /**
+ * Idempotently records that a project has been approved by beest review and
+ * is now waiting for joe.fraud's first-pass verdict. The actual upstream
+ * Create Project call is deferred to the poller so the reviewer's request is
+ * never blocked by joe.fraud's availability.
+ */
+ async stageProjectForReview(projectId: string): Promise {
+ const existing = await this.fraudRepo.findOne({ where: { projectId } });
+ if (existing) {
+ // Re-stage — clear any prior verdict so the poller treats it as fresh.
+ existing.remoteProjectId = null;
+ existing.status = 'pending';
+ existing.trustScore = null;
+ existing.justification = null;
+ existing.reviewedAt = null;
+ existing.outcomeRecorded = false;
+ await this.fraudRepo.save(existing);
+ return;
+ }
+ const row = this.fraudRepo.create({ projectId, status: 'pending' });
+ await this.fraudRepo.save(row);
+ }
+
+ // ── Poller ─────────────────────────────────────────────────────────────────
+
+ /** Public so an admin endpoint can trigger a manual reconcile if needed. */
+ async poll(): Promise {
+ if (!this.configured) return;
+ if (this.polling) return; // skip overlapping cycles
+ this.polling = true;
+ try {
+ // 1. Submit any rows that haven't been posted upstream yet.
+ const unsubmitted = await this.fraudRepo.find({
+ where: { remoteProjectId: IsNull(), status: 'pending' },
+ });
+ for (const row of unsubmitted) {
+ try {
+ await this.submitOne(row);
+ } catch (err) {
+ this.logger.error(
+ `Fraud submit failed for project ${row.projectId}: ${err}`,
+ );
+ }
+ }
+
+ // 2. Reconcile pending verdicts.
+ const remoteList = await this.listRemoteProjects();
+ if (!remoteList) return;
+ const byId = new Map(remoteList.map((p) => [p.id, p]));
+
+ const pending = await this.fraudRepo.find({
+ where: { status: 'pending' },
+ });
+ for (const row of pending) {
+ if (!row.remoteProjectId) continue;
+ const remote = byId.get(row.remoteProjectId);
+ if (!remote || remote.status !== 'complete' || !remote.review) continue;
+ try {
+ await this.reconcile(row, remote);
+ } catch (err) {
+ this.logger.error(
+ `Fraud reconcile failed for project ${row.projectId}: ${err}`,
+ );
+ }
+ }
+ } finally {
+ this.polling = false;
+ }
+ }
+
+ // ── Submit ─────────────────────────────────────────────────────────────────
+
+ private async submitOne(row: FraudReview): Promise {
+ const project = await this.projectRepo.findOne({
+ where: { id: row.projectId },
+ relations: ['user'],
+ });
+ if (!project || !project.user) {
+ this.logger.warn(
+ `Cannot submit fraud review for ${row.projectId} — project or user missing`,
+ );
+ return;
+ }
+
+ const submitter = this.buildSubmitter(project.user);
+ if (!submitter) {
+ this.logger.warn(
+ `Cannot submit fraud review for ${row.projectId} — no slackId or email on user`,
+ );
+ return;
+ }
+
+ const isHardware = project.projectType === 'hardware';
+
+ const body: Record = {
+ name: project.name,
+ codeLink: project.codeUrl ?? '',
+ submitter,
+ organizerPlatformId: project.id,
+ };
+ if (project.demoUrl) body.demoLink = project.demoUrl;
+
+ if (isHardware) {
+ body.isHardware = true;
+ body.hardwareJournal = await this.buildHardwareJournal(project);
+ } else if (project.hackatimeProjectName?.length) {
+ body.hackatimeProjects = project.hackatimeProjectName;
+ }
+
+ const res = await fetchWithTimeout(
+ `${this.apiBaseUrl}/events/${encodeURIComponent(this.eventId!)}/projects`,
+ {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${this.apiKey}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(body),
+ },
+ );
+
+ const respBody = await res.json().catch(() => null);
+ if (!res.ok && res.status !== 200) {
+ this.logger.error(
+ `joe.fraud create failed (${res.status}) for ${row.projectId}: ${JSON.stringify(respBody)}`,
+ );
+ return;
+ }
+ const remoteId = respBody?.id;
+ if (typeof remoteId !== 'string' || remoteId.length === 0) {
+ this.logger.error(
+ `joe.fraud create returned no id for ${row.projectId}: ${JSON.stringify(respBody)}`,
+ );
+ return;
+ }
+ row.remoteProjectId = remoteId;
+ await this.fraudRepo.save(row);
+ this.logger.log(
+ `Submitted project ${project.id} to joe.fraud as ${remoteId}`,
+ );
+ }
+
+ private buildSubmitter(
+ user: User,
+ ): { slackId: string } | { email: string } | null {
+ if (user.slackId) return { slackId: user.slackId };
+ if (user.email) return { email: user.email };
+ return null;
+ }
+
+ // ── List ───────────────────────────────────────────────────────────────────
+
+ private async listRemoteProjects(): Promise {
+ const res = await fetchWithTimeout(
+ `${this.apiBaseUrl}/events/${encodeURIComponent(this.eventId!)}/projects`,
+ { headers: { Authorization: `Bearer ${this.apiKey}` } },
+ );
+ if (!res.ok) {
+ const text = await res.text().catch(() => '');
+ this.logger.error(`joe.fraud list failed (${res.status}): ${text}`);
+ return null;
+ }
+ const body = await res.json().catch(() => null);
+ const raw = body?.projects;
+ if (!Array.isArray(raw)) {
+ this.logger.error(`joe.fraud list returned non-array body`);
+ return null;
+ }
+
+ // Defensively validate each entry's shape — only act on records that
+ // pass schema checks. A malformed entry is logged and dropped.
+ const out: JoeFraudListProject[] = [];
+ for (const entry of raw) {
+ const validated = this.validateRemoteProject(entry);
+ if (validated) out.push(validated);
+ }
+ return out;
+ }
+
+ /**
+ * Validate that a list entry from joe.fraud has the structure we expect
+ * before we let it influence beest state. Returns a typed object on success
+ * or null if the entry is malformed.
+ */
+ private validateRemoteProject(entry: unknown): JoeFraudListProject | null {
+ if (!entry || typeof entry !== 'object') return null;
+ const e = entry as Record;
+
+ if (typeof e.id !== 'string' || e.id.length === 0 || e.id.length > 64) {
+ return null;
+ }
+ if (e.status !== 'pending' && e.status !== 'complete') return null;
+
+ let review: JoeFraudListProject['review'] = null;
+ if (e.review && typeof e.review === 'object') {
+ const r = e.review as Record;
+ if (
+ typeof r.trustScore !== 'number' ||
+ !Number.isInteger(r.trustScore) ||
+ r.trustScore < 1 ||
+ r.trustScore > 10
+ ) {
+ return null;
+ }
+ if (typeof r.justification !== 'string') return null;
+ if (typeof r.reviewedAt !== 'string') return null;
+ review = {
+ trustScore: r.trustScore,
+ // Cap justification length to 4 KB — defense against pathological
+ // payloads that would bloat our DB or audit logs.
+ justification: r.justification.slice(0, 4000),
+ reviewedAt: r.reviewedAt,
+ };
+ }
+
+ return {
+ id: e.id,
+ organizerPlatformId:
+ typeof e.organizerPlatformId === 'string' ? e.organizerPlatformId : null,
+ status: e.status as 'pending' | 'complete',
+ review,
+ outcome: null, // not used in reconcile path
+ };
+ }
+
+ // ── Reconcile ──────────────────────────────────────────────────────────────
+
+ private async reconcile(
+ row: FraudReview,
+ remote: JoeFraudListProject,
+ ): Promise {
+ const review = remote.review!;
+
+ // Defense in depth: confirm the remote record is actually for this beest
+ // project. If joe.fraud's response had an organizerPlatformId that doesn't
+ // match our project id, refuse to act and log loudly. (The list endpoint
+ // map is keyed by remote id, but we still verify identity before mutating
+ // local state to make response-tampering immediately obvious.)
+ if (
+ remote.organizerPlatformId &&
+ remote.organizerPlatformId !== row.projectId
+ ) {
+ this.logger.error(
+ `Refusing to reconcile ${row.projectId}: remote ${remote.id} reports organizerPlatformId=${remote.organizerPlatformId}`,
+ );
+ return;
+ }
+
+ row.status = 'complete';
+ row.trustScore = review.trustScore;
+ row.justification = review.justification;
+ row.reviewedAt = new Date(review.reviewedAt);
+ await this.fraudRepo.save(row);
+
+ // If the beest reviewer flipped the project out of 'fraud_pending' (e.g.
+ // back to changes_needed) between submit and verdict, don't act on the
+ // verdict — record it on our row but leave the project alone.
+ const project = await this.projectRepo.findOne({
+ where: { id: row.projectId },
+ });
+ if (!project || project.status !== 'fraud_pending') {
+ this.logger.log(
+ `Skipping reconcile side-effects for ${row.projectId} — current status is ${project?.status ?? 'missing'}`,
+ );
+ return;
+ }
+
+ if (review.trustScore <= FRAUD_REJECT_THRESHOLD) {
+ await this.markFraudRejected(row, review.justification);
+ return;
+ }
+
+ await this.completeApproval(row);
+ }
+
+ private async markFraudRejected(
+ row: FraudReview,
+ justification: string,
+ ): Promise {
+ const project = await this.projectRepo.findOne({
+ where: { id: row.projectId },
+ relations: ['user'],
+ });
+ if (!project) return;
+
+ project.status = 'changes_needed';
+ // Beest review previously bumped overrideHours; clear since the project is
+ // now rejected. (No pipes were granted yet — that only happens after fraud
+ // passes — so there's nothing to claw back.)
+ project.overrideHours = 0;
+ project.internalHours = 0;
+ await this.projectRepo.save(project);
+
+ // Also bump the latest submission back to 'changes_needed' so the user
+ // sees the project as needing changes in the dashboard.
+ const submission = await this.submissionRepo.findOne({
+ where: { projectId: row.projectId },
+ order: { createdAt: 'DESC' },
+ });
+ if (submission && submission.status !== 'changes_needed') {
+ submission.status = 'changes_needed';
+ submission.overrideHours = 0;
+ submission.internalHours = 0;
+ await this.submissionRepo.save(submission);
+ }
+
+ // Surface a generic message to the user; keep the upstream justification
+ // internal so reviewers can see the real reason without exposing it.
+ const review = this.reviewRepo.create({
+ projectId: row.projectId,
+ reviewerId: null, // system-authored — no human reviewer
+ submissionId: submission?.id ?? null,
+ status: 'changes_needed',
+ feedback: USER_FACING_FRAUD_FEEDBACK,
+ internalNote: `Fraud review flagged: ${justification}`,
+ overrideJustification: null,
+ });
+ await this.reviewRepo.save(review);
+
+ await this.auditLogService.log(
+ project.userId,
+ 'project_reviewed',
+ `Project "${project.name}" was flagged by fraud review`,
+ );
+ this.logger.log(
+ `Fraud-rejected project ${project.id} (trust=${row.trustScore})`,
+ );
+ }
+
+ private async completeApproval(row: FraudReview): Promise {
+ const project = await this.projectRepo.findOne({
+ where: { id: row.projectId },
+ relations: ['user'],
+ });
+ if (!project) return;
+
+ // 1. Flip status to approved.
+ project.status = 'approved';
+ await this.projectRepo.save(project);
+
+ // 2. Find the latest submission for this project (the one that was just
+ // fraud-approved). It currently sits at status='unreviewed' because
+ // AdminService deferred the flip to here.
+ const submission = await this.submissionRepo.findOne({
+ where: { projectId: row.projectId },
+ order: { createdAt: 'DESC' },
+ });
+ if (submission && submission.status !== 'approved') {
+ submission.status = 'approved';
+ await this.submissionRepo.save(submission);
+ }
+
+ // 3. Grant pipes — same delta logic as AdminService.reviewProject. Target =
+ // floor(sum of override_hours across the user's earned projects), delta =
+ // target − sum(pipes_granted) across all the user's projects.
+ if ((project.overrideHours ?? 0) > 0) {
+ const totals = await this.projectRepo
+ .createQueryBuilder('p')
+ .select('COALESCE(SUM(p.override_hours), 0)', 'earnedHours')
+ .addSelect('COALESCE(SUM(p.pipes_granted), 0)', 'granted')
+ .where('p.user_id = :uid', { uid: project.userId })
+ .andWhere(
+ `(p.status = 'approved' OR (p.status <> 'approved' AND p.pipes_granted > 0))`,
+ )
+ .getRawOne<{ earnedHours: string; granted: string }>();
+ const target = Math.floor(Number(totals?.earnedHours ?? 0));
+ const previouslyGranted = Number(totals?.granted ?? 0);
+ const delta = target - previouslyGranted;
+ if (delta > 0) {
+ await this.userRepo.increment({ id: project.userId }, 'pipes', delta);
+ project.pipesGranted = (project.pipesGranted ?? 0) + delta;
+ await this.projectRepo.save(project);
+ if (submission) {
+ submission.pipesGranted = delta;
+ await this.submissionRepo.save(submission);
+ }
+ }
+ }
+
+ // 4. Find the review record the beest reviewer just created so we can pass
+ // its overrideJustification to the Airtable sync, with the fraud
+ // reviewer's justification appended for downstream traceability.
+ const beestReview = await this.reviewRepo.findOne({
+ where: { projectId: row.projectId, status: 'approved' },
+ order: { createdAt: 'DESC' },
+ });
+ const combinedJustification = this.combineJustifications(
+ beestReview?.overrideJustification ?? null,
+ row.trustScore,
+ row.justification,
+ );
+
+ // 5. Loops sync + Airtable Projects push.
+ if (project.user?.email) {
+ this.rsvpService.updateDateField(
+ project.user.email,
+ 'Loops - beestApprovedProject',
+ );
+ }
+ try {
+ await this.airtableSync.syncApprovedProject(
+ project,
+ combinedJustification,
+ submission ?? null,
+ );
+ } catch (err) {
+ this.logger.error(
+ `Airtable sync failed for fraud-approved project ${project.id}: ${err}`,
+ );
+ }
+
+ // 6. Audit log + outcome callback.
+ await this.auditLogService.log(
+ project.userId,
+ 'project_reviewed',
+ `Project "${project.name}" was approved (fraud-cleared)`,
+ );
+
+ if (!row.outcomeRecorded && row.remoteProjectId) {
+ try {
+ await this.recordOutcome(row.remoteProjectId, 'approved');
+ row.outcomeRecorded = true;
+ await this.fraudRepo.save(row);
+ } catch (err) {
+ this.logger.error(
+ `joe.fraud outcome push failed for ${project.id}: ${err}`,
+ );
+ }
+ }
+
+ this.logger.log(
+ `Fraud-approved project ${project.id} (trust=${row.trustScore})`,
+ );
+ }
+
+ private combineJustifications(
+ beest: string | null,
+ fraudScore: number | null,
+ fraudJustification: string | null,
+ ): string | null {
+ const parts: string[] = [];
+ if (beest && beest.trim().length > 0) parts.push(beest.trim());
+ if (fraudJustification && fraudJustification.trim().length > 0) {
+ const scoreLabel = fraudScore != null ? `trust ${fraudScore}/10` : 'verdict';
+ parts.push(`Fraud review (${scoreLabel}): ${fraudJustification.trim()}`);
+ }
+ return parts.length > 0 ? parts.join('\n\n') : null;
+ }
+
+ private async recordOutcome(
+ remoteProjectId: string,
+ status: 'approved' | 'rejected',
+ reason?: string,
+ ): Promise {
+ const body: Record = { status };
+ if (status === 'rejected' && reason) body.reason = reason;
+ const res = await fetchWithTimeout(
+ `${this.apiBaseUrl}/events/${encodeURIComponent(this.eventId!)}/projects/${encodeURIComponent(remoteProjectId)}/outcome`,
+ {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${this.apiKey}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(body),
+ },
+ );
+ if (!res.ok) {
+ const text = await res.text().catch(() => '');
+ throw new Error(`outcome ${res.status}: ${text}`);
+ }
+ }
+
+ // ── Hardware journal mapping ───────────────────────────────────────────────
+
+ private async buildHardwareJournal(
+ project: Project,
+ ): Promise {
+ const devlogs = await this.devlogRepo.find({
+ where: { projectId: project.id },
+ order: { createdAt: 'ASC' },
+ });
+ if (devlogs.length === 0) return [];
+
+ // Bucket devlogs by UTC date so we can spread that day's Hackatime hours
+ // across the entries written on the same day.
+ const devlogsByDay = new Map();
+ for (const d of devlogs) {
+ const day = d.createdAt.toISOString().slice(0, 10);
+ const bucket = devlogsByDay.get(day) ?? [];
+ bucket.push(d);
+ devlogsByDay.set(day, bucket);
+ }
+
+ const hoursByDay = await this.hoursPerDayFromHackatime(project);
+
+ return devlogs.map((d) => {
+ const day = d.createdAt.toISOString().slice(0, 10);
+ const dayHours = hoursByDay.get(day) ?? 0;
+ const sameDayCount = devlogsByDay.get(day)?.length ?? 1;
+ const hours = sameDayCount > 0 ? dayHours / sameDayCount : 0;
+
+ return {
+ title: d.title,
+ content: d.text,
+ timestamp: d.createdAt.toISOString(),
+ hours: Math.round(hours * 100) / 100,
+ ...(d.imageUrls && d.imageUrls.length > 0 ? { images: d.imageUrls } : {}),
+ };
+ });
+ }
+
+ private async hoursPerDayFromHackatime(
+ project: Project,
+ ): Promise