From 393fd5d4c3546833a8222461c34ca629814a88a9 Mon Sep 17 00:00:00 2001 From: Gautam Kumar Date: Wed, 1 Apr 2026 16:14:52 +0530 Subject: [PATCH 1/5] removed unnecessary docs --- API_INTEGRATION.md | 257 ----------------------------- API_SETUP.md | 274 ------------------------------- DOCKER_DEBUG.md | 393 --------------------------------------------- QUICKSTART.md | 186 --------------------- SECURITY_AUDIT.md | 165 ------------------- 5 files changed, 1275 deletions(-) delete mode 100644 API_INTEGRATION.md delete mode 100644 API_SETUP.md delete mode 100644 DOCKER_DEBUG.md delete mode 100644 QUICKSTART.md delete mode 100644 SECURITY_AUDIT.md diff --git a/API_INTEGRATION.md b/API_INTEGRATION.md deleted file mode 100644 index 73b3b6d..0000000 --- a/API_INTEGRATION.md +++ /dev/null @@ -1,257 +0,0 @@ -# API Integration & Docker Setup Guide - -## Overview - -This guide covers the API integration between the Go backend server and Next.js frontend, with Docker support for running both services independently or together. - -## Architecture - -``` -┌──────────────────────────────────────────────────────────────┐ -│ Docker Network │ -├──────────────────────┬──────────────────┬────────────────────┤ -│ PostgreSQL │ Backend API │ Frontend Web │ -│ (Port 5432) │ (Port 8080) │ (Port 3000) │ -└──────────────────────┴──────────────────┴────────────────────┘ -``` - -## Running Services - -### Local Development (Without Docker) - -1. **Start PostgreSQL locally** (port 5432) - - Ensure PostgreSQL is running - - Create database `coderz` with user `coderz-space` - -2. **Start Backend Server** - ```bash - cd apps/server - go mod tidy - go run cmd/main.go - # API runs on http://localhost:8080/api - ``` - -3. **Start Frontend Web App** (in another terminal) - ```bash - cd apps/web - npm install - npm run dev - # Web app runs on http://localhost:3000 - ``` - -4. **Environment Configuration** - - Backend uses `apps/server/.env` - - Frontend uses `apps/web/.env.local` (for local dev) - - See `.env.example` files for reference - -### Docker Deployment - -#### Spin up entire stack (Web + API + Database): -```bash -docker-compose up --build -``` - -This will: -- Start PostgreSQL on port 5432 -- Run migrations automatically -- Start API on port 8080 -- Start Web on port 3000 - -#### Run services separately: - -**Backend only:** -```bash -docker-compose up --build api postgres migrate -``` - -**Frontend only (requires external API):** -```bash -docker build -t coderz-web apps/web -docker run -p 3000:3000 \ - -e NEXT_PUBLIC_API_URL=http://host.docker.internal:8080/api \ - coderz-web -``` - -## API Integration Architecture - -### Client-Side Security (Frontend) - -**File:** `apps/web/services/api.ts` - -Features: -- ✅ Centralized HTTP client using Axios -- ✅ Automatic auth token injection from localStorage -- ✅ Request timeouts (10 seconds) -- ✅ CORS credentials enabled -- ✅ X-Requested-With header for CSRF protection -- ✅ Automatic token refresh on 401 responses -- ✅ Custom error handling via `APIError` class -- ✅ Server-side rendering safe (no direct DOM access) - -### Authentication Flow - -1. **Login Request** - - POST `/api/auth/mentee/login` with credentials - - Backend responds with `{ token, refreshToken, mentee }` - -2. **Token Storage** - - Access token stored in `localStorage` (client-side) - - Used automatically in `Authorization: Bearer ` header - - Cleared on 401 response - -3. **Protected Endpoints** - - All subsequent requests include auth header - - Backend middleware validates JWT - - Invalid token triggers redirect to login - -### Service Layer Integration - -**File:** `apps/web/services/menteeService.ts` & `roleService.ts` - -Features: -- ✅ All functions return Promises (async) -- ✅ In-memory cache with 5-minute TTL -- ✅ Automatic cache invalidation on mutations -- ✅ Graceful error handling with defaults -- ✅ TypeScript types for all responses -- ✅ Server-side rendering compatible - -**Frontend Functions** → **Backend Endpoints:** - -``` -registerMentee() → POST /api/auth/mentee-register -getMenteeRequests() → GET /api/mentee-requests -updateMenteeStatus() → PATCH /api/mentee-requests/:id -loginMentee() → POST /api/auth/mentee/login -loginMenteeByEmail() → POST /api/auth/mentee/login -getMenteeQuestions() → GET /api/mentees/:username/questions -updateQuestionProgress() → PATCH /api/mentees/:username/questions/:questionId -updateQuestionDetails() → PATCH /api/mentees/:username/questions/:questionId -getMenteeProfile() → GET /api/mentees/:profileUsername/profile -getLeaderboard() → GET /api/leaderboard -getMentorProfile() → GET /api/mentor/profile -updateMentorProfile() → PATCH /api/mentor/profile -selectRole() → POST /api/auth/select-role -getSelectedRole() → GET /api/auth/get-role -``` - -## Security Best Practices Implemented - -### 1. **CORS Configuration** -✅ Backend explicitly allows frontend origin only -```go -AllowOrigins: []string{cfg.FrontendOrigin}, -AllowCredentials: true, -AllowMethods: [...specific methods...], -``` - -### 2. **Authentication & Authorization** -✅ JWT-based authentication -✅ Automatic token refresh handling -✅ Clear tokens on unauthorized (401) responses -✅ Tokens NOT exposed in responses headers (secure) - -### 3. **Transport Security** -✅ HTTPS ready (use in production) -✅ CORS credentials enabled for secure cookies -✅ X-Requested-With header prevents CSRF -✅ Content-Type validation required - -### 4. **Input Validation** -✅ Role validation in frontend service layer (defense in depth) -✅ Backend validates all inputs before database queries -✅ Error messages don't leak sensitive information - -### 5. **Error Handling** -✅ Centralized error handling via `APIError` class -✅ Console warnings for debugging, not user-facing -✅ Generic error messages to prevent information leakage - -### 6. **Environment Variables** -✅ Sensitive values (JWT_SECRET) never committed -✅ Different configs for local dev and Docker -✅ Production environment uses secure defaults - -### 7. **Session Management** -✅ Tokens stored in localStorage (XSS-protected via CSP in production) -✅ RefreshToken for token rotation support -✅ Token expiration: 1 hour (access), 24 hours (refresh) - -## Environment Variables Reference - -### Frontend (`apps/web/.env.local`) -``` -NEXT_PUBLIC_API_URL=http://localhost:8080/api -NEXT_PUBLIC_ENVIRONMENT=development -``` - -### Backend (`apps/server/.env`) -``` -PORT=8080 -FRONTEND_ORIGIN=http://localhost:3000 -JWT_SECRET= -JWT_EXPIRES=1h -``` - -### Docker Services -- **API connects to Database:** `postgres://coderz-space:coderz-space@postgres:5432/coderz` -- **Web connects to API:** `http://api:8080/api` -- **Frontend connects from outside:** `http://localhost:8080/api` - -## Troubleshooting - -### CORS Errors -**Fix:** Update `FRONTEND_ORIGIN` in backend `.env` to match frontend URL - -### API Connection Failed -**Check:** -- Is backend running? `curl http://localhost:8080/api/health` -- Do hostnames match in docker-compose? -- Is firewall blocking ports? - -### Docker Networking Issues -**Solution:** Services communicate via docker service names (e.g., `api`, `postgres`) -Don't use `localhost` inside Docker containers. - -### Token Expiration -Clear tokens on 401, user redirected to login page automatically. - -## Component Usage Example - -```typescript -// components/LoginForm.tsx -import { loginMentee } from "@/services/menteeService"; -import { selectRole } from "@/services/roleService"; - -export async function handleLogin(username: string, password: string) { - try { - const { token, mentee } = await loginMentee(username, password); - // Token auto-stored by API client - await selectRole("mentee"); - // Redirect to dashboard - } catch (error) { - console.error("Login failed:", error.message); - // Show user-friendly error - } -} -``` - -## Production Considerations - -1. **Use HTTPS** - All API calls over HTTPS -2. **Environment Secrets** - Use secure vault for JWT_SECRET -3. **Database** - Use managed PostgreSQL service (AWS RDS, etc.) -4. **CSP Headers** - Add Content-Security-Policy headers -5. **Rate Limiting** - Implement rate limiting on backend -6. **Logging** - Monitor auth failures and errors -7. **Refresh Token Rotation** - Implement secure refresh token rotation -8. **HTTPS Enforced** - Redirect HTTP to HTTPS - -## Next Steps - -1. Implement remaining backend endpoints as needed -2. Add request/response logging middleware -3. Implement rate limiting -4. Add comprehensive error handling tests -5. Set up CI/CD pipeline for Docker builds -6. Configure production secrets management diff --git a/API_SETUP.md b/API_SETUP.md deleted file mode 100644 index 799d772..0000000 --- a/API_SETUP.md +++ /dev/null @@ -1,274 +0,0 @@ -# Coderz.space - Complete API Integration Guide - -Welcome! This project has been fully integrated with API support. Both the frontend (Next.js) and backend (Go server) are now connected via Docker and ready for development and deployment. - -## 🚀 Quick Start (Choose One) - -### Option A: Docker (Recommended - One Command) -```bash -docker-compose up --build -``` -Then open: -- Frontend: http://localhost:3000 -- Backend API: http://localhost:8080/api -- Health: http://localhost:8080/api/health - -### Option B: Local Development (Two Terminals) - -**Terminal 1 - Backend:** -```bash -cd apps/server -go run cmd/main.go -``` - -**Terminal 2 - Frontend:** -```bash -cd apps/web -npm install -npm run dev -``` - -Requires: PostgreSQL running on localhost:5432 - -## 📚 Documentation - -### Essential Reading -- **[QUICKSTART.md](./QUICKSTART.md)** ← Start here (5 min read) -- **[API_INTEGRATION.md](./API_INTEGRATION.md)** ← Architecture & endpoints (detailed) -- **[SECURITY_AUDIT.md](./SECURITY_AUDIT.md)** ← Security implementation - -### Troubleshooting & Debugging -- **[DOCKER_DEBUG.md](./DOCKER_DEBUG.md)** ← Docker troubleshooting -- **[IMPLEMENTATION_SUMMARY.md](./IMPLEMENTATION_SUMMARY.md)** ← What changed - -## 🏗️ Project Structure - -``` -coderz.space/ -├── apps/ -│ ├── web/ # Next.js frontend -│ │ ├── services/ # API integration layer ✨ UPDATED -│ │ │ ├── api.ts # HTTP client (NEW) -│ │ │ ├── roleService.ts # ✨ NOW API-INTEGRATED -│ │ │ └── menteeService.ts # ✨ NOW API-INTEGRATED -│ │ ├── .env.local # Local config (NEW) -│ │ ├── .env.production # Docker config (NEW) -│ │ └── Dockerfile # Already present -│ │ -│ ├── server/ # Go backend -│ │ ├── .env # Config (NEW) -│ │ ├── .env.example # Template (UPDATED) -│ │ ├── dockerfile # Docker build (NEW) -│ │ └── cmd/main.go # Entry point -│ │ -│ └── mobile/ # React Native app -│ -├── docker-compose.yml # Orchestration (NEW) ✨ -├── QUICKSTART.md # Get started (NEW) -├── API_INTEGRATION.md # Full guide (NEW) -├── SECURITY_AUDIT.md # Security details (NEW) -├── DOCKER_DEBUG.md # Debugging help (NEW) -└── IMPLEMENTATION_SUMMARY.md # What changed (NEW) -``` - -## ✨ What's New - -### Frontend (apps/web/) -✅ Secure HTTP client with automatic auth token injection -✅ API-integrated services (roleService, menteeService) -✅ Environment configuration for local & Docker -✅ In-memory caching with TTL -✅ Graceful error handling -✅ Full TypeScript support - -### Backend (apps/server/) -✅ Dockerfile for containerization -✅ Environment configuration for Docker -✅ Updated .env.example with explanations - -### DevOps -✅ Root docker-compose.yml for full orchestration -✅ PostgreSQL, API, Web, and migrations all included -✅ Health checks for each service -✅ Volume management for database persistence - -### Documentation -✅ 5 comprehensive guides (QUICKSTART, API, SECURITY, DEBUG, SUMMARY) -✅ Setup instructions -✅ API endpoint mapping -✅ Security best practices -✅ Troubleshooting guides - -## 🔐 Security Highlights - -✓ **JWT Authentication** - Secure token-based auth -✓ **Auto Token Injection** - Tokens added to every request automatically -✓ **CORS Protection** - Only frontend can access API -✓ **Error Sanitization** - Generic error messages (no info leakage) -✓ **Type Safety** - Full TypeScript for runtime safety -✓ **Environment Secrets** - Never hardcoded, using .env -✓ **Cache Layer** - Reduces API surface area -✓ **Timeout Protection** - 10-second request timeouts - -## 📊 API Integration Status - -| Component | Status | Location | -|-----------|--------|----------| -| HTTP Client | ✅ Complete | `apps/web/services/api.ts` | -| Role Service | ✅ Complete | `apps/web/services/roleService.ts` | -| Mentee Service | ✅ Complete | `apps/web/services/menteeService.ts` | -| Environment Config | ✅ Complete | `.env` files | -| Docker Orchestration | ✅ Complete | `docker-compose.yml` | -| Documentation | ✅ Complete | 5 guide files | - -## 🎯 Next Steps - -### 1. **Get It Running** (5 minutes) -```bash -docker-compose up --build -``` - -### 2. **Read QUICKSTART** (5 minutes) -Open [QUICKSTART.md](./QUICKSTART.md) for overview - -### 3. **Understand Architecture** (15 minutes) -Read [API_INTEGRATION.md](./API_INTEGRATION.md) for full details - -### 4. **Check Security** (10 minutes) -Review [SECURITY_AUDIT.md](./SECURITY_AUDIT.md) for practices - -### 5. **Implement Backend Endpoints** (Ongoing) -- Backend needs to implement the 18+ mapped endpoints -- Frontend is ready to consume them -- See [API_INTEGRATION.md](./API_INTEGRATION.md) for complete mapping - -## 📋 Environment Variables - -### Frontend (.env.local for local dev) -``` -NEXT_PUBLIC_API_URL=http://localhost:8080/api -NEXT_PUBLIC_ENVIRONMENT=development -``` - -### Frontend (.env.production for Docker) -``` -NEXT_PUBLIC_API_URL=http://api:8080/api -``` - -### Backend (.env for local dev) -``` -PORT=8080 -FRONTEND_ORIGIN=http://localhost:3000 -JWT_SECRET= -DB_URL=postgres://coderz-space:coderz-space@localhost:5432/coderz -``` - -### Docker Environment -- Services communicate via service names (api, postgres, web) -- Defined in docker-compose.yml - -## 🐳 Docker Commands - -```bash -# Start everything -docker-compose up --build - -# Stop everything -docker-compose down - -# View logs for a service -docker-compose logs -f api -docker-compose logs -f web - -# Run one service -docker-compose up --build api - -# Rebuild everything (hard reset) -docker-compose down -v && docker-compose up --build -``` - -## 🔍 Testing the Integration - -### 1. **Frontend Loads** -``` -http://localhost:3000 -``` -Should load without CORS errors - -### 2. **API Health Check** -```bash -curl http://localhost:8080/api/health -# {"status":"ok","timestamp":"..."} -``` - -### 3. **Test Login Flow** (Once backend endpoints implemented) -```bash -# Register -curl -X POST http://localhost:8080/api/auth/mentee-register \ - -H "Content-Type: application/json" \ - -d '{"firstName":"John","lastName":"Doe","username":"johndoe","email":"john@example.com","passwordHash":"hashed"}' - -# Login -curl -X POST http://localhost:8080/api/auth/mentee/login \ - -H "Content-Type: application/json" \ - -d '{"username":"johndoe","password":"password"}' -``` - -## ✅ Features Preserved - -- ✅ All existing UI components work -- ✅ All styling and layouts intact -- ✅ Dashboard functionality preserved -- ✅ Leaderboard display ready -- ✅ Profile pages working -- ✅ Role-based navigation functioning -- ✅ No breaking changes - -## 🎓 Learning Resources - -### For Frontend Developers -- React & Next.js usage unchanged -- Services now return Promises -- See [API_INTEGRATION.md](./API_INTEGRATION.md) for component examples - -### For Backend Developers -- API endpoints defined in [API_INTEGRATION.md](./API_INTEGRATION.md) -- Implement handlers according to spec -- Database queries already set up (sqlc) - -### For DevOps/SRE -- Docker Compose for local orchestration -- See [DOCKER_DEBUG.md](./DOCKER_DEBUG.md) for troubleshooting -- Production checklist in [API_INTEGRATION.md](./API_INTEGRATION.md) - -## 📞 Support & Troubleshooting - -| Issue | Solution | -|-------|----------| -| Can't start Docker | Check [DOCKER_DEBUG.md](./DOCKER_DEBUG.md) | -| CORS errors | Check FRONTEND_ORIGIN in backend .env | -| Port already in use | Kill other services or change ports | -| Database won't start | Check PostgreSQL installation | - -## 🔗 Important Links - -- **[QUICKSTART.md](./QUICKSTART.md)** - 5-minute setup -- **[API_INTEGRATION.md](./API_INTEGRATION.md)** - 30-minute deep dive -- **[SECURITY_AUDIT.md](./SECURITY_AUDIT.md)** - Security details -- **[DOCKER_DEBUG.md](./DOCKER_DEBUG.md)** - Troubleshooting -- **[IMPLEMENTATION_SUMMARY.md](./IMPLEMENTATION_SUMMARY.md)** - What changed - -## 🎉 Ready to Go - -Your API integration is complete and production-ready. All services can run: -- ✅ Locally for development -- ✅ In Docker for isolation -- ✅ In orchestrated containers for production - -**Start here:** [QUICKSTART.md](./QUICKSTART.md) - ---- - -**Last Updated:** March 31, 2026 -**Status:** ✅ Complete -**Version:** 1.0.0 diff --git a/DOCKER_DEBUG.md b/DOCKER_DEBUG.md deleted file mode 100644 index 3959ac0..0000000 --- a/DOCKER_DEBUG.md +++ /dev/null @@ -1,393 +0,0 @@ -# Docker Debugging Guide - -## Environment Variables Inside Docker - -When services run in Docker, they can communicate via service names: - -```yaml -# docker-compose.yml defines: -services: - api: # Service name = hostname - web: # Can access api as: http://api:8080 - postgres: # Can access db as: postgres:5432 -``` - -## Environment Variable Mapping - -### Frontend Service Names -Inside Docker container, frontend connects to: -``` -NEXT_PUBLIC_API_URL=http://api:8080/api -``` - -From your laptop browser, connect to: -``` -http://localhost:3000 → calls → http://localhost:8080/api -``` - -### Backend Service Names -Inside Docker container, backend connects to: -``` -DB_URL=postgres://user:pass@postgres:5432/coderz -``` - -From your laptop psql client, connect to: -``` -psql -h localhost -p 5432 -U coderz-space coderz -``` - -## Verification Commands - -### Check if containers are running -```bash -docker ps -``` - -Expected output: -``` -coderz-api -coderz-web -coderz-postgres -``` - -### Check container logs -```bash -# API logs -docker-compose logs api - -# Web logs -docker-compose logs web - -# Database logs -docker-compose logs postgres - -# Follow logs in real-time -docker-compose logs -f api -``` - -### Test API from container -```bash -# From your laptop -curl http://localhost:8080/api/health - -# Expected response -{"status":"ok","timestamp":"2026-03-31T..."} -``` - -### Test network connectivity inside containers -```bash -# Open shell in API container -docker-compose exec api sh - -# Inside container, test DB connection -nc -zv postgres 5432 # Should show: postgres:5432 open - -# Test API health -wget http://localhost:8080/api/health -O - -``` - -## Common Issues - -### Issue: "Connection refused" to API from frontend - -**Cause:** Frontend using `http://localhost:8080` instead of `http://api:8080` inside Docker - -**Solution:** -Check `.env.production`: -``` -# Wrong for Docker -NEXT_PUBLIC_API_URL=http://localhost:8080/api - -# Correct for Docker -NEXT_PUBLIC_API_URL=http://api:8080/api -``` - -**Rebuild:** `docker-compose up --build web` - -### Issue: Database migrations not running - -**Check migration logs:** -```bash -docker-compose logs migrate -``` - -**Common causes:** -- PostgreSQL not healthy yet (wait for health check) -- Wrong DB connection string -- Missing migration files - -**Fix:** -```bash -docker-compose down -v # Remove volume -docker-compose up --build # Rebuild everything -``` - -### Issue: Port already in use - -**Cause:** Another service using port 3000, 8080, or 5432 - -**Solution:** -```bash -# Find what's using the port (Linux/Mac) -lsof -i :8080 - -# Kill it -kill - -# Or change port in docker-compose.yml -# ports: -# - "8081:8080" # Changed from 8080 -``` - -### Issue: Containers keep restarting - -**Check logs:** -```bash -docker-compose logs -``` - -**Common causes:** -- Database not initialized -- Wrong environment variables -- Port conflicts -- Out of memory - -**Debug:** -```bash -# Run container in foreground to see errors -docker-compose run --rm api sh - -# Inside container, run server manually -./server # See actual error -``` - -### Issue: Frontend can't see API even though it's running - -**Check:** -1. Is API health check passing? - ```bash - docker-compose ps - # Look for "healthy" status - ``` - -2. Is web connected to network? - ```bash - docker network inspect coderz-network - # Should list both 'api' and 'web' containers - ``` - -3. Can web reach API from container? - ```bash - docker-compose exec web wget -O - http://api:8080/api/health - ``` - -**Solution:** -```bash -docker-compose down -docker-compose up --build -``` - -## Environment Variable Debugging - -### Print environment inside container -```bash -# In API container -docker-compose exec api env | grep -E "API|DB|FRONTEND" - -# In web container -docker-compose exec web env | grep -E "NEXT_PUBLIC" -``` - -### Verify environment variables loaded -Check container startup logs: -```bash -docker-compose logs api | grep -E "PORT|ORIGIN|DATABASE" -``` - -### Override environment at runtime -```bash -docker run -e NEXT_PUBLIC_API_URL=http://example.com coderz-web -``` - -## Performance Debugging - -### Container resource usage -```bash -docker stats # See CPU, memory, network usage - -# Monitor specific container -docker stats coderz-api -``` - -### Slow startup? -```bash -# Check when each step completed -docker-compose logs --timestamps api - -# Timings: -# 1. Build image (~30s) -# 2. Start database (~5s) -# 3. Run migrations (~5s) -# 4. Start API (~2s) -# 5. Start web (~15s) -``` - -## Volume & Persistence - -### Check volume status -```bash -docker volume ls | grep coderz -docker volume inspect coderz-postgres-data -``` - -### Remove volume (WARNING: deletes data!) -```bash -docker-compose down -v -``` - -### Backup database from Docker -```bash -docker-compose exec postgres pg_dump -U coderz-space coderz > backup.sql -``` - -### Restore database -```bash -cat backup.sql | docker-compose exec -T postgres psql -U coderz-space coderz -``` - -## Network Debugging - -### Inspect docker network -```bash -docker network inspect coderz-network -``` - -Shows all containers connected and their IP addresses. - -### Test DNS resolution inside container -```bash -docker-compose exec api nslookup postgres -# Should resolve to 172.x.x.x -``` - -### Check exposed ports -```bash -docker ps --format "table {{.Names}}\t{{.Ports}}" -``` - -## Security Verification - -### Check CORS headers -```bash -curl -H "Origin: http://localhost:3000" \ - -H "Access-Control-Request-Method: POST" \ - http://localhost:8080/api/health -v -``` - -Should see `Access-Control-Allow-Origin: http://localhost:3000` - -### Verify JWT validation -1. Login to get token -2. Test with wrong token -3. Should get 401 Unauthorized - -### Check auth flow -```bash -# Login -TOKEN=$(curl -X POST http://localhost:8080/api/auth/mentee/login \ - -H "Content-Type: application/json" \ - -d '{"username":"testuser","password":"testpass"}' | jq -r '.token') - -# Use token -curl -H "Authorization: Bearer $TOKEN" \ - http://localhost:8080/api/mentees/testuser/profile -``` - -## Rebuild & Restart - -### Rebuild everything -```bash -docker-compose down -docker-compose up --build -``` - -### Rebuild specific service -```bash -docker-compose up --build api # Rebuild only API -``` - -### Hard reset (remove everything) -```bash -docker-compose down -v # Stop & remove volumes -docker system prune -a # Clean unused images -docker-compose up --build # Fresh start -``` - -## Production Debugging - -### Enable debug mode -Add to `.env`: -``` -LOG_LEVEL=debug -``` - -Rebuild: -```bash -docker-compose up --build -``` - -### View request/response in logs -API logs should show: -- Request method & path -- Response status code -- Processing time - -Frontend logs (browser console): -- API call details -- Response data or errors - -### Monitor API metrics -```bash -# Check response times -docker-compose logs api | grep "duration" - -# Find slow requests (>1s) -docker-compose logs api | grep "duration.*[1-9][0-9][0-9][0-9]ms" -``` - -## Extracting Logs for Support - -```bash -# Save all logs to file -docker-compose logs > debug.log - -# Just API logs -docker-compose logs api > api.log - -# With timestamps -docker-compose logs --timestamps > debug_time.log - -# Follow in real-time -docker-compose logs -f -``` - -## Quick Reference - -| Command | Purpose | -|---------|---------| -| `docker-compose up` | Start all services | -| `docker-compose down` | Stop all services | -| `docker-compose ps` | List running containers | -| `docker-compose logs api` | View API logs | -| `docker-compose exec api sh` | Shell into API container | -| `docker-compose build` | Rebuild images | -| `docker stats` | Monitor resource usage | -| `docker system prune -a` | Clean up everything | - -## Getting Help - -1. Check logs first: `docker-compose logs -f` -2. Review [QUICKSTART.md](./QUICKSTART.md) troubleshooting -3. Check [API_INTEGRATION.md](./API_INTEGRATION.md) for architecture -4. Verify all containers healthy: `docker-compose ps` -5. Try full reset: `docker-compose down -v && docker-compose up --build` diff --git a/QUICKSTART.md b/QUICKSTART.md deleted file mode 100644 index ae9bffa..0000000 --- a/QUICKSTART.md +++ /dev/null @@ -1,186 +0,0 @@ -# Quick Start - API Integration - -## 📋 What's Been Set Up - -- ✅ Secure HTTP client (`services/api.ts`) -- ✅ API-integrated services (`roleService.ts`, `menteeService.ts`) -- ✅ JWT authentication with auto-token injection -- ✅ Environment configuration for local & Docker -- ✅ Docker Compose with Web + API + PostgreSQL -- ✅ Security best practices implemented -- ✅ Comprehensive documentation - -## 🚀 Get Started - -### Option 1: Full Stack in Docker (Recommended) - -```bash -# From project root -docker-compose up --build - -# Services will be available at: -# Frontend: http://localhost:3000 -# Backend: http://localhost:8080/api -# Health: http://localhost:8080/api/health -``` - -### Option 2: Local Development - -**Terminal 1 - Backend:** -```bash -cd apps/server -go run cmd/main.go -# Runs on http://localhost:8080/api -``` - -**Terminal 2 - Frontend:** -```bash -cd apps/web -npm install -npm run dev -# Runs on http://localhost:3000 -``` - -**Start PostgreSQL independently:** -- Docker: `docker run -p 5432:5432 -e POSTGRES_USER=coderz-space -e POSTGRES_PASSWORD=coderz-space postgres:18` -- Or use local PostgreSQL installation - -## 📝 Environment Setup - -### For Local Development Edit - -**`apps/web/.env.local`:** -``` -NEXT_PUBLIC_API_URL=http://localhost:8080/api -NEXT_PUBLIC_ENVIRONMENT=development -``` - -**`apps/server/.env`:** -- Already configured for localhost -- Update `FRONTEND_ORIGIN` if using different frontend URL - -## 🧪 Test the Integration - -### Check API Health -```bash -curl http://localhost:8080/api/health -# Response: {"status":"ok","timestamp":"2026-03-31T..."} -``` - -### Test Frontend Connection -1. Open http://localhost:3000 -2. Browser console should not show CORS errors -3. Try logging in - requests should go to backend - -## 📚 Documentation - -- **API Integration Guide:** [API_INTEGRATION.md](./API_INTEGRATION.md) -- **Security Audit:** [SECURITY_AUDIT.md](./SECURITY_AUDIT.md) - -## 🔧 Docker Commands - -```bash -# Start everything -docker-compose up --build - -# Stop everything -docker-compose down - -# View logs -docker-compose logs -f api -docker-compose logs -f web - -# Rebuild specific service -docker-compose up --build api - -# Run backend only -docker-compose up postgres migrate api - -# Clean up everything (including data) -docker-compose down -v -``` - -## 🔌 API Endpoints (Implemented) - -All endpoints below are integrated in frontend services: - -### Authentication -- `POST /api/auth/mentee-register` - Register new mentee -- `POST /api/auth/mentee/login` - Login mentee -- `POST /api/auth/select-role` - Select user role -- `GET /api/auth/get-role` - Get selected role - -### Mentee Management -- `GET /api/mentee-requests` - Get all mentee requests (admin) -- `PATCH /api/mentee-requests/:id` - Update mentee status -- `DELETE /api/mentee-requests/:id` - Delete mentee -- `GET /api/mentees/:username/profile` - Get mentee profile -- `PATCH /api/mentees/:username/profile` - Update mentee profile -- `PATCH /api/mentees/:username/password` - Change password - -### Questions & Progress -- `GET /api/mentees/:username/questions` - Get questions -- `PATCH /api/mentees/:username/questions/:questionId` - Update progress/notes -- `GET /api/mentees/:username/questions/:questionId` - Get question detail - -### Leaderboard -- `GET /api/leaderboard` - Get mentee rankings - -### Mentor -- `GET /api/mentor/profile` - Get mentor profile -- `PATCH /api/mentor/profile` - Update mentor profile -- `PATCH /api/mentor/password` - Change password - -### Health -- `GET /api/health` - Health check - -## 🛡️ Security Features - -✅ **CORS Protection** - Only frontend can access API -✅ **JWT Authentication** - Secure token-based auth -✅ **Auto Token Injection** - No manual header management -✅ **Centralized Error Handling** - Generic error messages -✅ **Cache Layer** - Reduced API load with TTL -✅ **Type Safety** - Full TypeScript support - -## 🚨 Common Issues - -| Issue | Solution | -|-------|----------| -| CORS Error | Check FRONTEND_ORIGIN in `.env` | -| Cannot connect to DB | Ensure PostgreSQL is running | -| Port already in use | `docker-compose down` or change ports | -| API not responding | Check logs: `docker-compose logs api` | - -## 📦 Dependencies Added - -- **Frontend:** `axios@^1.7.0` (HTTP client) -- **Backend:** Already complete - -Install frontend dependencies: -```bash -cd apps/web -npm install -``` - -## ✅ Features Keeping Existing UI - -All frontend components remain unchanged: -- UI components, layouts, and styling intact -- Only service layer implementations updated -- Backward compatible with existing component code -- No breaking changes to component APIs - -## 🎯 Next: Implement Backend Endpoints - -The frontend is now ready. Backend should implement the API endpoints mapped in `API_INTEGRATION.md`. - -Start with these core endpoints: -1. Auth endpoints (login, register, role selection) -2. Mentee questions endpoint -3. Profile endpoints -4. Leaderboard endpoint - -## 📞 Support - -See `API_INTEGRATION.md` for detailed troubleshooting and architecture diagrams. diff --git a/SECURITY_AUDIT.md b/SECURITY_AUDIT.md deleted file mode 100644 index 982725d..0000000 --- a/SECURITY_AUDIT.md +++ /dev/null @@ -1,165 +0,0 @@ -# API Integration Security Audit - -## Security Checklist ✓ - -### Authentication & Authorization -- ✅ **JWT-based Authentication**: Stateless, scalable authentication -- ✅ **Automatic Token Injection**: Auth token added to all requests automatically -- ✅ **Token Storage**: Secure localStorage storage with clear on 401 -- ✅ **Auth Interceptor**: Request interceptor adds Bearer token -- ✅ **Unauthorized Handling**: 401 responses trigger logout & redirect -- ✅ **Role-based Access**: Frontend enforces role selection before API calls - -### Transport Security -- ✅ **CORS Enforcement**: Backend restricts to specific frontend origin - ```go - AllowOrigins: []string{cfg.FrontendOrigin} - ``` -- ✅ **Credentials Support**: `withCredentials: true` for secure cookies -- ✅ **CSRF Protection**: X-Requested-With header included in requests -- ✅ **Content-Type Validation**: Application/json enforced -- ✅ **Timeout Protection**: 10-second request timeouts prevent hanging - -### Request/Response Handling -- ✅ **Custom Error Class**: APIError wraps axios errors safely -- ✅ **Error Sanitization**: Error messages don't leak implementation details -- ✅ **Response Validation**: Type-safe responses via TypeScript generics -- ✅ **Request Config**: Centralized axios instance prevents misconfiguration -- ✅ **Cache Layer**: In-memory cache reduces API load - -### Input Validation -- ✅ **Frontend Validation**: Role type checking before API calls -- ✅ **Backend Validation**: Should validate all inputs (implement in Go handlers) -- ✅ **Type Safety**: TypeScript prevents invalid data structure passing -- ✅ **Parameter Validation**: IDs and usernames validated by backend - -### Environment & Config -- ✅ **Environment Separation**: Different configs for dev, local, production -- ✅ **Secrets Management**: JWT_SECRET never hardcoded in source -- ✅ **.env Files**: Git-ignored sensitive configuration -- ✅ **Public vs Private**: NEXT_PUBLIC_ prefix controls exposure -- ✅ **Docker Secrets**: Service names used for inter-service communication - -### Error Handling -- ✅ **No Stack Traces**: User doesn't see implementation details -- ✅ **Consistent Errors**: APIError class standardizes format -- ✅ **Silent Failures**: Graceful degradation on network errors -- ✅ **Cache Fallback**: Data returned from cache if API fails -- ✅ **Error Logging**: Console warnings for debugging (not production) - -### Caching Strategy -- ✅ **TTL-based Caching**: 5-minute cache prevents stale data -- ✅ **Cache Invalidation**: Mutations clear relevant cache keys -- ✅ **Memory-safe**: Map-based cache doesn't grow unbounded -- ✅ **No Sensitive Data**: Auth tokens not cached - -### Dependency Security -- ✅ **Axios**: Industry-standard HTTP client, actively maintained -- ✅ **No OAuth Libraries**: JWT used directly (minimal dependencies) -- ✅ **Type Definitions**: @types/axios for type safety -- ✅ **Regular Updates**: npm packages should be updated regularly - -### Frontend Best Practices -- ✅ **SSR-safe**: API client checks for window object -- ✅ **No Client-side Secrets**: JWT_SECRET not exposed to frontend -- ✅ **TypeScript Strict**: Type checking prevents misuse -- ✅ **Error Boundaries**: Each service has try-catch error handling - -## Security Recommendations - -### Immediate (High Priority) -1. **Implement Backend Input Validation** - - Validate all request bodies - - Sanitize user inputs - - Implement SQL injection protection (use parameterized queries in sqlc) - -2. **Add Rate Limiting** - - Prevent brute force attacks - - Use middleware like `echo-rate-limit` - -3. **TLS/HTTPS** - - Use HTTPS in production - - Set Strict-Transport-Security headers - -### Short-term (Medium Priority) -1. **Implement Refresh Token Rotation** - - Issue new refresh tokens on each use - - Invalidate old refresh tokens - -2. **Add Request Logging** - - Log all authentication attempts - - Monitor for suspicious patterns - -3. **Implement HSTS Headers** - - Force HTTPS for all future requests - - Prevent SSL stripping attacks - -### Medium-term (Nice to Have) -1. **OAuth 2.0 Integration** - - Support Google/GitHub login - - Reduces password management burden - -2. **Two-Factor Authentication** - - Time-based OTP (TOTP) - - Recovery codes - -3. **Content Security Policy** - - Prevent XSS attacks - - Restrict script sources - -4. **API Key Management** - - For service-to-service communication - - Separate from user authentication - -## Security Test Checklist - -### Manual Testing -- [ ] Verify token is cleared on login failure -- [ ] Test 401 response redirects to login -- [ ] Confirm CORS blocks unauthorized origins -- [ ] Test API health endpoint returns 200 -- [ ] Verify CSRF header is present in requests - -### Automated Testing (Future) -- [ ] Unit tests for error handling -- [ ] Integration tests for auth flow -- [ ] E2E tests for login/logout -- [ ] Security scanning with OWASP ZAP -- [ ] Dependency scanning with Snyk - -## Threat Model Mitigation - -| Threat | Mitigation | -|--------|-----------| -| **XSS (Cross-site Scripting)** | CSP headers (production), React escaping | -| **CSRF (Cross-site Request Forgery)** | X-Requested-With header, SameSite cookies | -| **SQL Injection** | sqlc prevents (uses parameterized queries) | -| **Unauthorized Access** | JWT validation, role-based checks | -| **Man-in-the-Middle** | HTTPS/TLS (production) | -| **Brute Force** | Rate limiting (future) | -| **Token Theft** | localStorage with HTTPS, clear on 401 | -| **Information Disclosure** | Generic error messages, no stack traces | - -## Compliance Considerations - -- **GDPR**: Ensure user data deletion endpoints exist -- **CCPA**: Provide data export functionality -- **PCI DSS**: If handling payments, follow PCI standards -- **HIPAA**: If health data, implement additional controls - -## Code Review Points - -1. ✅ No hardcoded secrets in code -2. ✅ Environment variables properly configured -3. ✅ Error messages are generic (not implementation-specific) -4. ✅ All external inputs validated -5. ✅ Dependencies kept updated -6. ✅ No console.log with sensitive data in production -7. ✅ CORS origin strictly configured -8. ✅ Database queries use parameterized statements - -## References - -- OWASP Top 10: https://owasp.org/www-project-top-ten/ -- JWT Best Practices: https://tools.ietf.org/html/rfc8725 -- REST API Security: https://restfulapi.net/security-essentials/ From 919430e9c10b7b82852e23296785b7f64c2642f0 Mon Sep 17 00:00:00 2001 From: Gautam Kumar Date: Wed, 1 Apr 2026 16:20:37 +0530 Subject: [PATCH 2/5] cleaned web folder --- apps/web/AGENTS.md | 5 - apps/web/CLAUDE.md | 1 - docker-compose.yml | 112 - package-lock.json | 6042 -------------------------------------------- package.json | 8 - 5 files changed, 6168 deletions(-) delete mode 100644 apps/web/AGENTS.md delete mode 100644 apps/web/CLAUDE.md delete mode 100644 docker-compose.yml delete mode 100644 package-lock.json delete mode 100644 package.json diff --git a/apps/web/AGENTS.md b/apps/web/AGENTS.md deleted file mode 100644 index 8bd0e39..0000000 --- a/apps/web/AGENTS.md +++ /dev/null @@ -1,5 +0,0 @@ - -# This is NOT the Next.js you know - -This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices. - diff --git a/apps/web/CLAUDE.md b/apps/web/CLAUDE.md deleted file mode 100644 index 43c994c..0000000 --- a/apps/web/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -@AGENTS.md diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 82b6687..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,112 +0,0 @@ -services: - # PostgreSQL Database - postgres: - image: postgres:18-alpine - container_name: coderz-space-postgres - environment: - POSTGRES_USER: coderz-space - POSTGRES_PASSWORD: coderz-space - POSTGRES_DB: coderz - ports: - - "5432:5432" - volumes: - - coderz-postgres-data:/var/lib/postgresql/data - - ./apps/server/db/migrations:/migrations:ro - healthcheck: - test: ["CMD-SHELL", "pg_isready -U coderz-space"] - interval: 10s - timeout: 5s - retries: 5 - networks: - - coderz-network - - # Database Migration - migrate: - image: migrate/migrate - container_name: coderz-migrate - volumes: - - ./apps/server/db/migrations:/migrations:ro - command: - - -path=/migrations - - -database=postgres://coderz-space:coderz-space@postgres:5432/coderz?sslmode=disable - - up - depends_on: - postgres: - condition: service_healthy - networks: - - coderz-network - - # Backend API Server (Go) - api: - build: - context: ./apps/server - dockerfile: dockerfile - container_name: coderz-api - environment: - PORT: 8080 - # For Docker, backend should allow web service as origin - FRONTEND_ORIGIN: http://web:3000 - ENVIRONMENT: development - # Use service name for DB connectivity within Docker network - DB_URL: postgres://coderz-space:coderz-space@postgres:5432/coderz?sslmode=disable - JWT_SECRET: j3QE2U6eBQj8EvRUnhPF2Sf2YuChgfhgfjhg0JMeSVWDNO138RYMj3QE2U6eBQj8EvRUnhPF2Sf2YuC0JMeSVWDNO138RYMj3QE2U6eBQj8EvRUnhPF2Sf2YuC0JMeSVWDNO138RYM - JWT_EXPIRES: 1h - REFRESH_TOKEN_EXPIRES: 24h - LOG_LEVEL: info - FILE_LOG_LEVEL: info - APP_NAME: Coderz_Space - VERSION: 0.1.0 - MAX_DB_CONNS: 10 - MIN_DB_CONNS: 2 - MAX_DB_CONN_LIFETIME: 1h - MAX_DB_CONN_IDLE_TIME: 30m - ports: - - "8080:8080" - depends_on: - postgres: - condition: service_healthy - migrate: - condition: service_completed_successfully - healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/api/health"] - interval: 30s - timeout: 5s - retries: 3 - start_period: 10s - networks: - - coderz-network - restart: unless-stopped - - # Frontend Web App (Next.js) - web: - build: - context: ./apps/web - dockerfile: Dockerfile - container_name: coderz-web - environment: - # API client connects to backend service within Docker network - NEXT_PUBLIC_API_URL: http://api:8080/api - NEXT_PUBLIC_ENVIRONMENT: production - NEXT_PUBLIC_ENABLE_ANALYTICS: "true" - NEXT_PUBLIC_ENABLE_ERROR_REPORTING: "false" - ports: - - "3000:3000" - depends_on: - api: - condition: service_healthy - healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000"] - interval: 30s - timeout: 5s - retries: 3 - start_period: 15s - networks: - - coderz-network - restart: unless-stopped - -volumes: - coderz-postgres-data: - -networks: - coderz-network: - driver: bridge diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index dd29ba0..0000000 --- a/package-lock.json +++ /dev/null @@ -1,6042 +0,0 @@ -{ - "name": "Coderz.space", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@react-native-async-storage/async-storage": "^3.0.1" - }, - "devDependencies": { - "@react-native-community/cli": "^20.1.3" - } - }, - "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==", - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "ISC", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "ISC", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "devOptional": true, - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "devOptional": true, - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@isaacs/ttlcache": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", - "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", - "license": "ISC", - "peer": true, - "engines": { - "node": ">=12" - } - }, - "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==", - "license": "ISC", - "peer": true, - "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/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/create-cache-key-function": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", - "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true - }, - "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==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@react-native-async-storage/async-storage": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-3.0.1.tgz", - "integrity": "sha512-VHwHb19sMg4Xh3W5M6YmJ/HSm1uh8RYFa6Dozm9o/jVYTYUgz2BmDXqXF7sum3glQaR34/hlwVc94px1sSdC2A==", - "license": "MIT", - "dependencies": { - "idb": "8.0.3" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/@react-native-community/cli": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-20.1.3.tgz", - "integrity": "sha512-sLo8cu9JyFNfuuF1C+8NJ4DHE/PEFaXGd4enkcxi/OJjGG8+sOQrdjNQ4i+cVh/2c+ah1mEMwsYjc3z0+/MqSg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@react-native-community/cli-clean": "20.1.3", - "@react-native-community/cli-config": "20.1.3", - "@react-native-community/cli-doctor": "20.1.3", - "@react-native-community/cli-server-api": "20.1.3", - "@react-native-community/cli-tools": "20.1.3", - "@react-native-community/cli-types": "20.1.3", - "commander": "^9.4.1", - "deepmerge": "^4.3.0", - "execa": "^5.0.0", - "find-up": "^5.0.0", - "fs-extra": "^8.1.0", - "graceful-fs": "^4.1.3", - "picocolors": "^1.1.1", - "prompts": "^2.4.2", - "semver": "^7.5.2" - }, - "bin": { - "rnc-cli": "build/bin.js" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/@react-native-community/cli-clean": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-20.1.3.tgz", - "integrity": "sha512-sFLdLzapfC0scjgzBJJWYDY2RhHPjuuPkA5r6q0gc/UQH/izXpMpLrhh1DW84cMDraNACK0U62tU7ebNaQ1LMQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@react-native-community/cli-tools": "20.1.3", - "execa": "^5.0.0", - "fast-glob": "^3.3.2", - "picocolors": "^1.1.1" - } - }, - "node_modules/@react-native-community/cli-config": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-20.1.3.tgz", - "integrity": "sha512-n73nW0cG92oNF0r994pPqm0DjAShOm3F8LSffDYhJqNAno+h/csmv/37iL4NtSpmKIO8xqsG3uVTXz9X/hzNaQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@react-native-community/cli-tools": "20.1.3", - "cosmiconfig": "^9.0.0", - "deepmerge": "^4.3.0", - "fast-glob": "^3.3.2", - "joi": "^17.2.1", - "picocolors": "^1.1.1" - } - }, - "node_modules/@react-native-community/cli-config-android": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-android/-/cli-config-android-20.1.3.tgz", - "integrity": "sha512-DNHDP+OWLyhKShGciBqPcxhxfp1Z/7GQcb4F+TGyCeKQAr+JdnUjRXN3X+YCU/v+g2kbYYyRJKlGabzkVvdrAw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@react-native-community/cli-tools": "20.1.3", - "fast-glob": "^3.3.2", - "fast-xml-parser": "^5.3.6", - "picocolors": "^1.1.1" - } - }, - "node_modules/@react-native-community/cli-config-apple": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-apple/-/cli-config-apple-20.1.3.tgz", - "integrity": "sha512-QX9B83nAfCPs0KiaYz61kAEHWr9sttooxzRzNdQwvZTwnsIpvWOT9GvMMj/19OeXiQzMJBzZX0Pgt6+spiUsDQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@react-native-community/cli-tools": "20.1.3", - "execa": "^5.0.0", - "fast-glob": "^3.3.2", - "picocolors": "^1.1.1" - } - }, - "node_modules/@react-native-community/cli-doctor": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-20.1.3.tgz", - "integrity": "sha512-EI+mAPWn255/WZ4CQohy1I049yiaxVr41C3BeQ2BCyhxODIDR8XRsLzYb1t9MfqK/C3ZncUN2mPSRXFeKPPI1w==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@react-native-community/cli-config": "20.1.3", - "@react-native-community/cli-platform-android": "20.1.3", - "@react-native-community/cli-platform-apple": "20.1.3", - "@react-native-community/cli-platform-ios": "20.1.3", - "@react-native-community/cli-tools": "20.1.3", - "command-exists": "^1.2.8", - "deepmerge": "^4.3.0", - "envinfo": "^7.13.0", - "execa": "^5.0.0", - "node-stream-zip": "^1.9.1", - "ora": "^5.4.1", - "picocolors": "^1.1.1", - "semver": "^7.5.2", - "wcwidth": "^1.0.1", - "yaml": "^2.2.1" - } - }, - "node_modules/@react-native-community/cli-platform-android": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-20.1.3.tgz", - "integrity": "sha512-bzB9ELPOISuqgtDZXFPQlkuxx1YFkNx3cNgslc5ElCrk+5LeCLQLIBh/dmIuK8rwUrPcrramjeBj++Noc+TaAA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@react-native-community/cli-config-android": "20.1.3", - "@react-native-community/cli-tools": "20.1.3", - "execa": "^5.0.0", - "logkitty": "^0.7.1", - "picocolors": "^1.1.1" - } - }, - "node_modules/@react-native-community/cli-platform-apple": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-20.1.3.tgz", - "integrity": "sha512-XJ+DqAD4hkplWVXK5AMgN7pP9+4yRSe5KfZ/b42+ofkDBI55ALlUmX+9HWE3fMuRjcotTCoNZqX2ov97cFDXpQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@react-native-community/cli-config-apple": "20.1.3", - "@react-native-community/cli-tools": "20.1.3", - "execa": "^5.0.0", - "fast-xml-parser": "^5.3.6", - "picocolors": "^1.1.1" - } - }, - "node_modules/@react-native-community/cli-platform-ios": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-20.1.3.tgz", - "integrity": "sha512-2qL48SINotuHbZO73cgqSwqd/OWNx0xTbFSdujhpogV4p8BNwYYypfjh4vJY5qJEB5PxuoVkMXT+aCADpg9nBg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@react-native-community/cli-platform-apple": "20.1.3" - } - }, - "node_modules/@react-native-community/cli-server-api": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-20.1.3.tgz", - "integrity": "sha512-hsNsdUKZDd2T99OuNuiXz4VuvLa1UN0zcxefmPjXQgI0byrBLzzDr+o7p03sKuODSzKi2h+BMnUxiS07HACQLA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@react-native-community/cli-tools": "20.1.3", - "body-parser": "^2.2.2", - "compression": "^1.7.1", - "connect": "^3.6.5", - "errorhandler": "^1.5.1", - "nocache": "^3.0.1", - "open": "^6.2.0", - "pretty-format": "^29.7.0", - "serve-static": "^1.13.1", - "strict-url-sanitise": "0.0.1", - "ws": "^6.2.3" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/open": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "is-wsl": "^1.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/ws": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", - "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/@react-native-community/cli-tools": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-20.1.3.tgz", - "integrity": "sha512-EAn0vPCMxtHhfWk2UwLmSUfPfLUnFgC7NjiVJVTKJyVk5qGnkPfoT8te/1IUXFTysUB0F0RIi+NgDB4usFOLeA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@vscode/sudo-prompt": "^9.0.0", - "appdirsjs": "^1.2.4", - "execa": "^5.0.0", - "find-up": "^5.0.0", - "launch-editor": "^2.9.1", - "mime": "^2.4.1", - "ora": "^5.4.1", - "picocolors": "^1.1.1", - "prompts": "^2.4.2", - "semver": "^7.5.2" - } - }, - "node_modules/@react-native-community/cli-tools/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==", - "devOptional": 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/@react-native-community/cli-tools/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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "devOptional": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/@react-native-community/cli-tools/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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-tools/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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-types": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-20.1.3.tgz", - "integrity": "sha512-IdAcegf0pH1hVraxWTG1ACLkYC0LDQfqtaEf42ESyLIF3Xap70JzL/9tAlxw7lSCPZPFWhrcgU0TBc4SkC/ecw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "joi": "^17.2.1" - } - }, - "node_modules/@react-native-community/cli/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || >=14" - } - }, - "node_modules/@react-native-community/cli/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==", - "devOptional": 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/@react-native-community/cli/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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli/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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli/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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native/assets-registry": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.84.1.tgz", - "integrity": "sha512-lAJ6PDZv95FdT9s9uhc9ivhikW1Zwh4j9XdXM7J2l4oUA3t37qfoBmTSDLuPyE3Bi+Xtwa11hJm0BUTT2sc/gg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 20.19.4" - } - }, - "node_modules/@react-native/codegen": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.84.1.tgz", - "integrity": "sha512-n1RIU0QAavgCg1uC5+s53arL7/mpM+16IBhJ3nCFSd/iK5tUmCwxQDcIDC703fuXfpub/ZygeSjVN8bcOWn0gA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/parser": "^7.25.3", - "hermes-parser": "0.32.0", - "invariant": "^2.2.4", - "nullthrows": "^1.1.1", - "tinyglobby": "^0.2.15", - "yargs": "^17.6.2" - }, - "engines": { - "node": ">= 20.19.4" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/community-cli-plugin": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.84.1.tgz", - "integrity": "sha512-f6a+mJEJ6Joxlt/050TqYUr7uRRbeKnz8lnpL7JajhpsgZLEbkJRjH8HY5QiLcRdUwWFtizml4V+vcO3P4RxoQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@react-native/dev-middleware": "0.84.1", - "debug": "^4.4.0", - "invariant": "^2.2.4", - "metro": "^0.83.3", - "metro-config": "^0.83.3", - "metro-core": "^0.83.3", - "semver": "^7.1.3" - }, - "engines": { - "node": ">= 20.19.4" - }, - "peerDependencies": { - "@react-native-community/cli": "*", - "@react-native/metro-config": "*" - }, - "peerDependenciesMeta": { - "@react-native-community/cli": { - "optional": true - }, - "@react-native/metro-config": { - "optional": true - } - } - }, - "node_modules/@react-native/debugger-frontend": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.84.1.tgz", - "integrity": "sha512-rUU/Pyh3R5zT0WkVgB+yA6VwOp7HM5Hz4NYE97ajFS07OUIcv8JzBL3MXVdSSjLfldfqOuPEuKUaZcAOwPgabw==", - "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": ">= 20.19.4" - } - }, - "node_modules/@react-native/debugger-shell": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.84.1.tgz", - "integrity": "sha512-LIGhh4q4ette3yW5OzmukNMYwmINYrRGDZqKyTYc/VZyNpblZPw72coXVHXdfpPT6+YlxHqXzn3UjFZpNODGCQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "cross-spawn": "^7.0.6", - "debug": "^4.4.0", - "fb-dotslash": "0.5.8" - }, - "engines": { - "node": ">= 20.19.4" - } - }, - "node_modules/@react-native/dev-middleware": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.84.1.tgz", - "integrity": "sha512-Z83ra+Gk6ElAhH3XRrv3vwbwCPTb04sPPlNpotxcFZb5LtRQZwT91ZQEXw3GOJCVIFp9EQ/gj8AQbVvtHKOUlQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.84.1", - "@react-native/debugger-shell": "0.84.1", - "chrome-launcher": "^0.15.2", - "chromium-edge-launcher": "^0.2.0", - "connect": "^3.6.5", - "debug": "^4.4.0", - "invariant": "^2.2.4", - "nullthrows": "^1.1.1", - "open": "^7.0.3", - "serve-static": "^1.16.2", - "ws": "^7.5.10" - }, - "engines": { - "node": ">= 20.19.4" - } - }, - "node_modules/@react-native/gradle-plugin": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.84.1.tgz", - "integrity": "sha512-7uVlPBE3uluRNRX4MW7PUJIO1LDBTpAqStKHU7LHH+GRrdZbHsWtOEAX8PiY4GFfBEvG8hEjiuTOqAxMjV+hDg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 20.19.4" - } - }, - "node_modules/@react-native/js-polyfills": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.84.1.tgz", - "integrity": "sha512-UsTe2AbUugsfyI7XIHMQq4E7xeC8a6GrYwuK+NohMMMJMxmyM3JkzIk+GB9e2il6ScEQNMJNaj+q+i5za8itxQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 20.19.4" - } - }, - "node_modules/@react-native/normalize-colors": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.84.1.tgz", - "integrity": "sha512-/UPaQ4jl95soXnLDEJ6Cs6lnRXhwbxtT4KbZz+AFDees7prMV2NOLcHfCnzmTabf5Y3oxENMVBL666n4GMLcTA==", - "license": "MIT", - "peer": true - }, - "node_modules/@react-native/virtualized-lists": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.84.1.tgz", - "integrity": "sha512-sJoDunzhci8ZsqxlUiKoLut4xQeQcmbIgvDHGQKeBz6uEq9HgU+hCWOijMRr6sLP0slQVfBAza34Rq7IbXZZOA==", - "license": "MIT", - "peer": true, - "dependencies": { - "invariant": "^2.2.4", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">= 20.19.4" - }, - "peerDependencies": { - "@types/react": "^19.2.0", - "react": "*", - "react-native": "*" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "devOptional": true, - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "devOptional": true, - "license": "BSD-3-Clause" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "devOptional": true, - "license": "BSD-3-Clause" - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", - "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==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@sinonjs/commons": "^3.0.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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "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==", - "license": "MIT", - "peer": true - }, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/node": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", - "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", - "license": "MIT", - "peer": true, - "dependencies": { - "undici-types": "~7.18.0" - } - }, - "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==", - "license": "MIT", - "peer": true - }, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true - }, - "node_modules/@vscode/sudo-prompt": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/@vscode/sudo-prompt/-/sudo-prompt-9.3.2.tgz", - "integrity": "sha512-gcXoCN00METUNFeQOFJ+C9xUI0DKB+0EGMVg7wbVYRHBw2Eq3fKisDZOkRdOz3kqXRKOENMfShPOmypw1/8nOw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", - "peer": true, - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "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", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 14" - } - }, - "node_modules/anser": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", - "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", - "license": "MIT", - "peer": true - }, - "node_modules/ansi-fragments": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", - "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "colorette": "^1.0.7", - "slice-ansi": "^2.0.0", - "strip-ansi": "^5.0.0" - } - }, - "node_modules/ansi-fragments/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-fragments/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "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==", - "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==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "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==", - "license": "ISC", - "peer": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/appdirsjs": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", - "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "peer": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "license": "MIT", - "peer": true - }, - "node_modules/astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-plugin-syntax-hermes-parser": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.32.0.tgz", - "integrity": "sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg==", - "license": "MIT", - "peer": true, - "dependencies": { - "hermes-parser": "0.32.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==", - "license": "MIT", - "peer": true, - "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": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "license": "MIT", - "peer": true, - "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "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==", - "license": "MIT", - "peer": true - }, - "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==", - "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==", - "license": "Apache-2.0", - "peer": true, - "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==", - "devOptional": 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==", - "devOptional": true, - "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/body-parser/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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "license": "MIT", - "peer": true, - "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==", - "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==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "license": "Apache-2.0", - "peer": true, - "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==", - "devOptional": 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-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", - "peer": true - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "devOptional": true, - "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==", - "devOptional": true, - "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==", - "devOptional": true, - "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==", - "devOptional": 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==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001780", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", - "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", - "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", - "peer": true - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chrome-launcher": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", - "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/chromium-edge-launcher": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.2.0.tgz", - "integrity": "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0", - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "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==", - "devOptional": 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==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "license": "ISC", - "peer": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/compression/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT", - "peer": true - }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "devOptional": true, - "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==", - "license": "MIT", - "peer": true - }, - "node_modules/cosmiconfig": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", - "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/cosmiconfig/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==", - "devOptional": true, - "license": "Python-2.0" - }, - "node_modules/cosmiconfig/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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "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==", - "devOptional": true, - "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/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "devOptional": 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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/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.321", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz", - "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==", - "license": "ISC", - "peer": true - }, - "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==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/envinfo": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", - "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", - "devOptional": true, - "license": "MIT", - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/error-stack-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "stackframe": "^1.3.4" - } - }, - "node_modules/errorhandler": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.2.tgz", - "integrity": "sha512-kNAL7hESndBCrWwS72QyV3IVOTrVmj9D062FV5BQswNL5zEdeRmz/WJFyh6Aj/plvvSOrzddkxW57HgkZcR9Fw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "escape-html": "~1.0.3" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/errorhandler/node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/errorhandler/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==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/errorhandler/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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/errorhandler/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "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==", - "devOptional": true, - "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==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "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", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "license": "BSD-2-Clause", - "peer": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "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/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "devOptional": 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/exponential-backoff": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", - "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", - "license": "Apache-2.0", - "peer": true - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.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==", - "license": "MIT", - "peer": true - }, - "node_modules/fast-xml-builder": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", - "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", - "devOptional": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "path-expression-matcher": "^1.1.3" - } - }, - "node_modules/fast-xml-parser": { - "version": "5.5.9", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.9.tgz", - "integrity": "sha512-jldvxr1MC6rtiZKgrFnDSvT8xuH+eJqxqOBThUVjYrxssYTo1avZLGql5l0a0BAERR01CadYzZ83kVEkbyDg+g==", - "devOptional": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "fast-xml-builder": "^1.1.4", - "path-expression-matcher": "^1.2.0", - "strnum": "^2.2.2" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-dotslash": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz", - "integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==", - "license": "(MIT OR Apache-2.0)", - "peer": true, - "bin": { - "dotslash": "bin/dotslash" - }, - "engines": { - "node": ">=20" - } - }, - "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==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "bser": "2.1.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==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flow-enums-runtime": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", - "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", - "license": "MIT", - "peer": true - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "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==", - "license": "ISC", - "peer": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "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==", - "devOptional": true, - "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==", - "license": "MIT", - "peer": true, - "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-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/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==", - "license": "MIT", - "peer": true, - "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==", - "devOptional": true, - "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==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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", - "license": "ISC", - "peer": true, - "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/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "devOptional": true, - "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==", - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "devOptional": true, - "license": "MIT", - "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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hermes-compiler": { - "version": "250829098.0.9", - "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.9.tgz", - "integrity": "sha512-hZ5O7PDz1vQ99TS7HD3FJ9zVynfU1y+VWId6U1Pldvd8hmAYrNec/XLPYJKD3dLOW6NXak6aAQAuMuSo3ji0tQ==", - "license": "MIT", - "peer": true - }, - "node_modules/hermes-estree": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", - "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", - "license": "MIT", - "peer": true - }, - "node_modules/hermes-parser": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", - "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", - "license": "MIT", - "peer": true, - "dependencies": { - "hermes-estree": "0.32.0" - } - }, - "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/http-errors/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/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "license": "MIT", - "peer": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "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==", - "devOptional": 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==", - "devOptional": true, - "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/idb": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.3.tgz", - "integrity": "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==", - "license": "ISC" - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "devOptional": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/image-size": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", - "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", - "license": "MIT", - "peer": true, - "dependencies": { - "queue": "6.0.2" - }, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=16.x" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "devOptional": 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-fresh/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==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "license": "MIT", - "peer": true, - "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.", - "license": "ISC", - "peer": true, - "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/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "license": "MIT", - "peer": true, - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "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==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "license": "MIT", - "peer": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "devOptional": 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==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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==", - "devOptional": 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==", - "devOptional": 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==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "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==", - "devOptional": 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==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "license": "MIT", - "peer": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "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==", - "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "license": "MIT", - "peer": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "license": "MIT", - "peer": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.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==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.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==", - "license": "MIT", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/joi": { - "version": "17.13.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", - "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", - "devOptional": true, - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, - "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==", - "license": "MIT" - }, - "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==", - "license": "MIT", - "peer": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsc-safe-url": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", - "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", - "license": "0BSD", - "peer": true - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "peer": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "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==", - "devOptional": 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==", - "license": "MIT", - "peer": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "devOptional": true, - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/launch-editor": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.2.tgz", - "integrity": "sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "picocolors": "^1.1.1", - "shell-quote": "^1.8.3" - } - }, - "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==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/lighthouse-logger": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", - "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" - } - }, - "node_modules/lighthouse-logger/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/lighthouse-logger/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT", - "peer": true - }, - "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==", - "devOptional": true, - "license": "MIT" - }, - "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==", - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "license": "MIT", - "peer": true - }, - "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==", - "devOptional": 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/logkitty": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", - "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "ansi-fragments": "^0.2.1", - "dayjs": "^1.8.15", - "yargs": "^15.1.0" - }, - "bin": { - "logkitty": "bin/logkitty.js" - } - }, - "node_modules/logkitty/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/logkitty/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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "devOptional": true, - "license": "ISC" - }, - "node_modules/logkitty/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "peer": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "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==", - "license": "ISC", - "peer": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/marky": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", - "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", - "license": "Apache-2.0", - "peer": true - }, - "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==", - "devOptional": true, - "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==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "license": "MIT", - "peer": true - }, - "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==", - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/metro": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.83.5.tgz", - "integrity": "sha512-BgsXevY1MBac/3ZYv/RfNFf/4iuW9X7f4H8ZNkiH+r667HD9sVujxcmu4jvEzGCAm4/WyKdZCuyhAcyhTHOucQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/core": "^7.25.2", - "@babel/generator": "^7.29.1", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "accepts": "^2.0.0", - "chalk": "^4.0.0", - "ci-info": "^2.0.0", - "connect": "^3.6.5", - "debug": "^4.4.0", - "error-stack-parser": "^2.0.6", - "flow-enums-runtime": "^0.0.6", - "graceful-fs": "^4.2.4", - "hermes-parser": "0.33.3", - "image-size": "^1.0.2", - "invariant": "^2.2.4", - "jest-worker": "^29.7.0", - "jsc-safe-url": "^0.2.2", - "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.83.5", - "metro-cache": "0.83.5", - "metro-cache-key": "0.83.5", - "metro-config": "0.83.5", - "metro-core": "0.83.5", - "metro-file-map": "0.83.5", - "metro-resolver": "0.83.5", - "metro-runtime": "0.83.5", - "metro-source-map": "0.83.5", - "metro-symbolicate": "0.83.5", - "metro-transform-plugins": "0.83.5", - "metro-transform-worker": "0.83.5", - "mime-types": "^3.0.1", - "nullthrows": "^1.1.1", - "serialize-error": "^2.1.0", - "source-map": "^0.5.6", - "throat": "^5.0.0", - "ws": "^7.5.10", - "yargs": "^17.6.2" - }, - "bin": { - "metro": "src/cli.js" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/metro-babel-transformer": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.83.5.tgz", - "integrity": "sha512-d9FfmgUEVejTiSb7bkQeLRGl6aeno2UpuPm3bo3rCYwxewj03ymvOn8s8vnS4fBqAPQ+cE9iQM40wh7nGXR+eA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/core": "^7.25.2", - "flow-enums-runtime": "^0.0.6", - "hermes-parser": "0.33.3", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/metro-babel-transformer/node_modules/hermes-estree": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", - "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", - "license": "MIT", - "peer": true - }, - "node_modules/metro-babel-transformer/node_modules/hermes-parser": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz", - "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==", - "license": "MIT", - "peer": true, - "dependencies": { - "hermes-estree": "0.33.3" - } - }, - "node_modules/metro-cache": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.83.5.tgz", - "integrity": "sha512-oH+s4U+IfZyg8J42bne2Skc90rcuESIYf86dYittcdWQtPfcaFXWpByPyTuWk3rR1Zz3Eh5HOrcVImfEhhJLng==", - "license": "MIT", - "peer": true, - "dependencies": { - "exponential-backoff": "^3.1.1", - "flow-enums-runtime": "^0.0.6", - "https-proxy-agent": "^7.0.5", - "metro-core": "0.83.5" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/metro-cache-key": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.83.5.tgz", - "integrity": "sha512-Ycl8PBajB7bhbAI7Rt0xEyiF8oJ0RWX8EKkolV1KfCUlC++V/GStMSGpPLwnnBZXZWkCC5edBPzv1Hz1Yi0Euw==", - "license": "MIT", - "peer": true, - "dependencies": { - "flow-enums-runtime": "^0.0.6" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/metro-config": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.83.5.tgz", - "integrity": "sha512-JQ/PAASXH7yczgV6OCUSRhZYME+NU8NYjI2RcaG5ga4QfQ3T/XdiLzpSb3awWZYlDCcQb36l4Vl7i0Zw7/Tf9w==", - "license": "MIT", - "peer": true, - "dependencies": { - "connect": "^3.6.5", - "flow-enums-runtime": "^0.0.6", - "jest-validate": "^29.7.0", - "metro": "0.83.5", - "metro-cache": "0.83.5", - "metro-core": "0.83.5", - "metro-runtime": "0.83.5", - "yaml": "^2.6.1" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/metro-core": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.83.5.tgz", - "integrity": "sha512-YcVcLCrf0ed4mdLa82Qob0VxYqfhmlRxUS8+TO4gosZo/gLwSvtdeOjc/Vt0pe/lvMNrBap9LlmvZM8FIsMgJQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "flow-enums-runtime": "^0.0.6", - "lodash.throttle": "^4.1.1", - "metro-resolver": "0.83.5" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/metro-file-map": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.83.5.tgz", - "integrity": "sha512-ZEt8s3a1cnYbn40nyCD+CsZdYSlwtFh2kFym4lo+uvfM+UMMH+r/BsrC6rbNClSrt+B7rU9T+Te/sh/NL8ZZKQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "debug": "^4.4.0", - "fb-watchman": "^2.0.0", - "flow-enums-runtime": "^0.0.6", - "graceful-fs": "^4.2.4", - "invariant": "^2.2.4", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "nullthrows": "^1.1.1", - "walker": "^1.0.7" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/metro-minify-terser": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.83.5.tgz", - "integrity": "sha512-Toe4Md1wS1PBqbvB0cFxBzKEVyyuYTUb0sgifAZh/mSvLH84qA1NAWik9sISWatzvfWf3rOGoUoO5E3f193a3Q==", - "license": "MIT", - "peer": true, - "dependencies": { - "flow-enums-runtime": "^0.0.6", - "terser": "^5.15.0" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/metro-resolver": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.83.5.tgz", - "integrity": "sha512-7p3GtzVUpbAweJeCcUJihJeOQl1bDuimO5ueo1K0BUpUtR41q5EilbQ3klt16UTPPMpA+tISWBtsrqU556mY1A==", - "license": "MIT", - "peer": true, - "dependencies": { - "flow-enums-runtime": "^0.0.6" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/metro-runtime": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.5.tgz", - "integrity": "sha512-f+b3ue9AWTVlZe2Xrki6TAoFtKIqw30jwfk7GQ1rDUBQaE0ZQ+NkiMEtb9uwH7uAjJ87U7Tdx1Jg1OJqUfEVlA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.25.0", - "flow-enums-runtime": "^0.0.6" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/metro-source-map": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.5.tgz", - "integrity": "sha512-VT9bb2KO2/4tWY9Z2yeZqTUao7CicKAOps9LUg2aQzsz+04QyuXL3qgf1cLUVRjA/D6G5u1RJAlN1w9VNHtODQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "flow-enums-runtime": "^0.0.6", - "invariant": "^2.2.4", - "metro-symbolicate": "0.83.5", - "nullthrows": "^1.1.1", - "ob1": "0.83.5", - "source-map": "^0.5.6", - "vlq": "^1.0.0" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/metro-symbolicate": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.83.5.tgz", - "integrity": "sha512-EMIkrjNRz/hF+p0RDdxoE60+dkaTLPN3vaaGkFmX5lvFdO6HPfHA/Ywznzkev+za0VhPQ5KSdz49/MALBRteHA==", - "license": "MIT", - "peer": true, - "dependencies": { - "flow-enums-runtime": "^0.0.6", - "invariant": "^2.2.4", - "metro-source-map": "0.83.5", - "nullthrows": "^1.1.1", - "source-map": "^0.5.6", - "vlq": "^1.0.0" - }, - "bin": { - "metro-symbolicate": "src/index.js" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/metro-transform-plugins": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.83.5.tgz", - "integrity": "sha512-KxYKzZL+lt3Os5H2nx7YkbkWVduLZL5kPrE/Yq+Prm/DE1VLhpfnO6HtPs8vimYFKOa58ncl60GpoX0h7Wm0Vw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/generator": "^7.29.1", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "flow-enums-runtime": "^0.0.6", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/metro-transform-worker": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.83.5.tgz", - "integrity": "sha512-8N4pjkNXc6ytlP9oAM6MwqkvUepNSW39LKYl9NjUMpRDazBQ7oBpQDc8Sz4aI8jnH6AGhF7s1m/ayxkN1t04yA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/generator": "^7.29.1", - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "flow-enums-runtime": "^0.0.6", - "metro": "0.83.5", - "metro-babel-transformer": "0.83.5", - "metro-cache": "0.83.5", - "metro-cache-key": "0.83.5", - "metro-minify-terser": "0.83.5", - "metro-source-map": "0.83.5", - "metro-transform-plugins": "0.83.5", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/metro/node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "license": "MIT", - "peer": true - }, - "node_modules/metro/node_modules/hermes-estree": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", - "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", - "license": "MIT", - "peer": true - }, - "node_modules/metro/node_modules/hermes-parser": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz", - "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==", - "license": "MIT", - "peer": true, - "dependencies": { - "hermes-estree": "0.33.3" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/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==", - "devOptional": 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==", - "license": "ISC", - "peer": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "peer": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "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/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", - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/nocache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", - "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - } - }, - "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==", - "license": "MIT", - "peer": true - }, - "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==", - "license": "MIT", - "peer": true - }, - "node_modules/node-stream-zip": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", - "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/antelle" - } - }, - "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==", - "license": "MIT", - "peer": true, - "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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nullthrows": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", - "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", - "license": "MIT", - "peer": true - }, - "node_modules/ob1": { - "version": "0.83.5", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.5.tgz", - "integrity": "sha512-vNKPYC8L5ycVANANpF/S+WZHpfnRWKx/F3AYP4QMn6ZJTh+l2HOrId0clNkEmua58NB9vmI9Qh7YOoV/4folYg==", - "license": "MIT", - "peer": true, - "dependencies": { - "flow-enums-runtime": "^0.0.6" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "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==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", - "devOptional": true, - "license": "MIT", - "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", - "peer": true, - "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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "license": "MIT", - "peer": true, - "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "devOptional": 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": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "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==", - "devOptional": 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==", - "devOptional": 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==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-expression-matcher": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.0.tgz", - "integrity": "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==", - "devOptional": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "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==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.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==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/promise": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", - "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", - "license": "MIT", - "peer": true, - "dependencies": { - "asap": "~2.0.6" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", - "devOptional": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", - "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", - "license": "MIT", - "peer": true, - "dependencies": { - "inherits": "~2.0.3" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "devOptional": 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/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==", - "devOptional": true, - "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": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-devtools-core": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz", - "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", - "license": "MIT", - "peer": true, - "dependencies": { - "shell-quote": "^1.6.1", - "ws": "^7" - } - }, - "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==", - "license": "MIT" - }, - "node_modules/react-native": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.84.1.tgz", - "integrity": "sha512-0PjxOyXRu3tZ8EobabxSukvhKje2HJbsZikR0U+pvS0pYZza2hXKjcSBiBdFN4h9D0S3v6a8kkrDK6WTRKMwzg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/create-cache-key-function": "^29.7.0", - "@react-native/assets-registry": "0.84.1", - "@react-native/codegen": "0.84.1", - "@react-native/community-cli-plugin": "0.84.1", - "@react-native/gradle-plugin": "0.84.1", - "@react-native/js-polyfills": "0.84.1", - "@react-native/normalize-colors": "0.84.1", - "@react-native/virtualized-lists": "0.84.1", - "abort-controller": "^3.0.0", - "anser": "^1.4.9", - "ansi-regex": "^5.0.0", - "babel-jest": "^29.7.0", - "babel-plugin-syntax-hermes-parser": "0.32.0", - "base64-js": "^1.5.1", - "commander": "^12.0.0", - "flow-enums-runtime": "^0.0.6", - "hermes-compiler": "250829098.0.9", - "invariant": "^2.2.4", - "jest-environment-node": "^29.7.0", - "memoize-one": "^5.0.0", - "metro-runtime": "^0.83.3", - "metro-source-map": "^0.83.3", - "nullthrows": "^1.1.1", - "pretty-format": "^29.7.0", - "promise": "^8.3.0", - "react-devtools-core": "^6.1.5", - "react-refresh": "^0.14.0", - "regenerator-runtime": "^0.13.2", - "scheduler": "0.27.0", - "semver": "^7.1.3", - "stacktrace-parser": "^0.1.10", - "tinyglobby": "^0.2.15", - "whatwg-fetch": "^3.0.0", - "ws": "^7.5.10", - "yargs": "^17.6.2" - }, - "bin": { - "react-native": "cli.js" - }, - "engines": { - "node": ">= 20.19.4" - }, - "peerDependencies": { - "@types/react": "^19.1.1", - "react": "^19.2.3" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "license": "MIT", - "peer": true - }, - "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==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "devOptional": true, - "license": "ISC" - }, - "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==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "devOptional": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "peer": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "devOptional": 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": { - "queue-microtask": "^1.2.2" - } - }, - "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==", - "devOptional": 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/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT", - "peer": true - }, - "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": "0.19.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", - "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.4.1", - "range-parser": "~1.2.1", - "statuses": "~2.0.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/send/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/send/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/send/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/serialize-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", - "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/serve-static": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", - "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "~0.19.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-static/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/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "devOptional": true, - "license": "ISC" - }, - "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==", - "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==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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==", - "devOptional": true, - "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==", - "devOptional": true, - "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==", - "devOptional": true, - "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==", - "devOptional": true, - "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": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "license": "BSD-3-Clause", - "peer": true - }, - "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==", - "license": "MIT", - "peer": true, - "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==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/stackframe": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", - "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", - "license": "MIT", - "peer": true - }, - "node_modules/stacktrace-parser": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", - "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==", - "license": "MIT", - "peer": true, - "dependencies": { - "type-fest": "^0.7.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/strict-url-sanitise": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/strict-url-sanitise/-/strict-url-sanitise-0.0.1.tgz", - "integrity": "sha512-nuFtF539K8jZg3FjaWH/L8eocCR6gegz5RDOsaWxfdbF5Jqr2VXWxZayjTwUzsWJDC91k2EbnJXp6FuWW+Z4hg==", - "devOptional": true, - "license": "MIT" - }, - "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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "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==", - "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==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "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==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strnum": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.2.tgz", - "integrity": "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==", - "devOptional": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/terser": { - "version": "5.46.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", - "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", - "license": "BSD-2-Clause", - "peer": true, - "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/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==", - "license": "MIT", - "peer": true - }, - "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==", - "license": "ISC", - "peer": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "license": "MIT", - "peer": true - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/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", - "peer": true, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "license": "BSD-3-Clause", - "peer": true - }, - "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==", - "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/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==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", - "license": "(MIT OR CC0-1.0)", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "license": "MIT", - "peer": true - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 4.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/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==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.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==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vlq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", - "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", - "license": "MIT", - "peer": true - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/whatwg-fetch": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", - "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", - "license": "MIT", - "peer": true - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "devOptional": true, - "license": "ISC" - }, - "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==", - "license": "MIT", - "peer": true, - "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", - "peer": true - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "license": "ISC", - "peer": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "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", - "peer": true, - "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==", - "license": "ISC", - "peer": true - }, - "node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "license": "MIT", - "peer": true, - "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==", - "license": "ISC", - "peer": true, - "engines": { - "node": ">=12" - } - }, - "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==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 0bbdeb5..0000000 --- a/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "dependencies": { - "@react-native-async-storage/async-storage": "^3.0.1" - }, - "devDependencies": { - "@react-native-community/cli": "^20.1.3" - } -} From 0594fc9c5e4f4583e4b4cdedf4d1db402594c66b Mon Sep 17 00:00:00 2001 From: Gautam Kumar Date: Wed, 1 Apr 2026 18:17:45 +0530 Subject: [PATCH 3/5] refactorin and feature completion attempt --- .../migrations/0002_algo_buddy_app.down.sql | 30 + .../db/migrations/0002_algo_buddy_app.up.sql | 129 ++ .../internal/common/validator/validator.go | 24 + apps/server/internal/container/container.go | 11 + apps/server/internal/modules/app/data.go | 130 ++ apps/server/internal/modules/app/data_test.go | 54 + apps/server/internal/modules/app/dto.go | 166 ++ apps/server/internal/modules/app/handler.go | 310 +++ .../internal/modules/app/handler_test.go | 41 + apps/server/internal/modules/app/routes.go | 33 + apps/server/internal/modules/app/service.go | 1859 +++++++++++++++++ .../internal/modules/app/service_test.go | 119 ++ apps/server/internal/modules/auth/handler.go | 12 +- apps/server/internal/routes/router.go | 5 + apps/web/app/globals.css | 6 +- apps/web/app/layout.tsx | 30 +- .../completed/[questionId]/page.tsx | 112 +- .../[username]/completed/page.tsx | 139 +- .../[username]/leaderboard/page.tsx | 88 +- .../[username]/my-profile/page.tsx | 305 +-- .../[username]/pending/page.tsx | 121 +- .../profile/[profileUsername]/page.tsx | 184 +- .../mentor-dashboard/approve-mentee/page.tsx | 14 +- .../[day]/[menteeUsername]/[sheetId]/page.tsx | 147 +- .../[day]/[menteeUsername]/page.tsx | 62 +- .../assign-tasklist/[day]/page.tsx | 352 ++-- .../app/mentor-dashboard/leaderboard/page.tsx | 81 +- .../master-tasklist/[sheetId]/page.tsx | 227 +- .../mentor-dashboard/master-tasklist/page.tsx | 32 +- .../app/mentor-dashboard/my-profile/page.tsx | 280 +-- .../profile/[profileUsername]/page.tsx | 144 +- apps/web/components/LandingPage.tsx | 2 +- apps/web/components/MenteeLoginCard.test.tsx | 62 + apps/web/components/MenteeLoginCard.tsx | 94 +- apps/web/components/MenteeSignUpCard.test.tsx | 51 + apps/web/components/MenteeSignUpCard.tsx | 169 +- apps/web/components/StubToast.tsx | 101 - apps/web/components/ThemeToggle.tsx | 49 +- .../dashboard/AccountProfileEditor.tsx | 323 +++ .../components/dashboard/LeaderboardList.tsx | 83 + .../components/dashboard/MenteeSidebar.tsx | 32 +- .../dashboard/PublicMenteeProfile.tsx | 148 ++ apps/web/components/dashboard/Sidebar.tsx | 23 +- apps/web/components/dashboard/constants.ts | 48 + apps/web/jest.config.ts | 9 + apps/web/public/file.svg | 1 - apps/web/public/globe.svg | 1 - apps/web/public/next.svg | 1 - apps/web/public/vercel.svg | 1 - apps/web/public/window.svg | 1 - apps/web/services/api.test.ts | 55 + apps/web/services/api.ts | 187 +- apps/web/services/appContext.ts | 6 + apps/web/services/auth.ts | 59 + apps/web/services/authService.ts | 105 - apps/web/services/index.ts | 8 +- apps/web/services/leaderboard.ts | 6 + apps/web/services/mentee.ts | 36 + apps/web/services/menteeService.ts | 400 ---- apps/web/services/mentor.ts | 52 + apps/web/services/profile.ts | 133 ++ apps/web/types/index.ts | 133 +- 62 files changed, 5113 insertions(+), 2513 deletions(-) create mode 100644 apps/server/db/migrations/0002_algo_buddy_app.down.sql create mode 100644 apps/server/db/migrations/0002_algo_buddy_app.up.sql create mode 100644 apps/server/internal/modules/app/data.go create mode 100644 apps/server/internal/modules/app/data_test.go create mode 100644 apps/server/internal/modules/app/dto.go create mode 100644 apps/server/internal/modules/app/handler.go create mode 100644 apps/server/internal/modules/app/handler_test.go create mode 100644 apps/server/internal/modules/app/routes.go create mode 100644 apps/server/internal/modules/app/service.go create mode 100644 apps/server/internal/modules/app/service_test.go create mode 100644 apps/web/components/MenteeLoginCard.test.tsx create mode 100644 apps/web/components/MenteeSignUpCard.test.tsx delete mode 100644 apps/web/components/StubToast.tsx create mode 100644 apps/web/components/dashboard/AccountProfileEditor.tsx create mode 100644 apps/web/components/dashboard/LeaderboardList.tsx create mode 100644 apps/web/components/dashboard/PublicMenteeProfile.tsx create mode 100644 apps/web/components/dashboard/constants.ts delete mode 100644 apps/web/public/file.svg delete mode 100644 apps/web/public/globe.svg delete mode 100644 apps/web/public/next.svg delete mode 100644 apps/web/public/vercel.svg delete mode 100644 apps/web/public/window.svg create mode 100644 apps/web/services/api.test.ts create mode 100644 apps/web/services/appContext.ts create mode 100644 apps/web/services/auth.ts delete mode 100644 apps/web/services/authService.ts create mode 100644 apps/web/services/leaderboard.ts create mode 100644 apps/web/services/mentee.ts delete mode 100644 apps/web/services/menteeService.ts create mode 100644 apps/web/services/mentor.ts create mode 100644 apps/web/services/profile.ts diff --git a/apps/server/db/migrations/0002_algo_buddy_app.down.sql b/apps/server/db/migrations/0002_algo_buddy_app.down.sql new file mode 100644 index 0000000..4c15c5a --- /dev/null +++ b/apps/server/db/migrations/0002_algo_buddy_app.down.sql @@ -0,0 +1,30 @@ +SET search_path TO coderz, public; + +DROP TRIGGER IF EXISTS trg_mentee_day_assignments_updated_at ON mentee_day_assignments; +DROP TABLE IF EXISTS mentee_day_assignments; + +DROP TRIGGER IF EXISTS trg_mentee_requests_updated_at ON mentee_requests; +DROP TABLE IF EXISTS mentee_requests; + +ALTER TABLE assignment_problems + DROP CONSTRAINT IF EXISTS chk_assignment_problems_app_progress_status; + +ALTER TABLE assignment_problems + DROP COLUMN IF EXISTS app_progress_status, + DROP COLUMN IF EXISTS resources; + +ALTER TABLE bootcamp_enrollments + DROP CONSTRAINT IF EXISTS chk_bootcamp_enrollments_assigned_sheet_key; + +ALTER TABLE bootcamp_enrollments + DROP COLUMN IF EXISTS assigned_sheet_key; + +ALTER TABLE users + DROP CONSTRAINT IF EXISTS chk_users_username_format, + DROP CONSTRAINT IF EXISTS uq_users_username; + +ALTER TABLE users + DROP COLUMN IF EXISTS linkedin_url, + DROP COLUMN IF EXISTS github_url, + DROP COLUMN IF EXISTS bio, + DROP COLUMN IF EXISTS username; diff --git a/apps/server/db/migrations/0002_algo_buddy_app.up.sql b/apps/server/db/migrations/0002_algo_buddy_app.up.sql new file mode 100644 index 0000000..5d7b8c8 --- /dev/null +++ b/apps/server/db/migrations/0002_algo_buddy_app.up.sql @@ -0,0 +1,129 @@ +SET search_path TO coderz, public; + +ALTER TABLE users + ADD COLUMN username VARCHAR(80), + ADD COLUMN bio TEXT, + ADD COLUMN github_url TEXT, + ADD COLUMN linkedin_url TEXT; + +ALTER TABLE users + ALTER COLUMN username SET DEFAULT ('user_' || REPLACE(LEFT(uuidv7()::text, 8), '-', '')); + +WITH prepared AS ( + SELECT + id, + COALESCE( + NULLIF( + LOWER(REGEXP_REPLACE(SPLIT_PART(COALESCE(email, ''), '@', 1), '[^a-zA-Z0-9_]+', '', 'g')), + '' + ), + NULLIF( + LOWER(REGEXP_REPLACE(COALESCE(name, ''), '[^a-zA-Z0-9_]+', '', 'g')), + '' + ), + 'user' + ) AS base_username + FROM users +), +ranked AS ( + SELECT + id, + base_username, + ROW_NUMBER() OVER (PARTITION BY base_username ORDER BY id) AS seq + FROM prepared +) +UPDATE users u +SET username = CASE + WHEN ranked.seq = 1 THEN ranked.base_username + ELSE ranked.base_username || ranked.seq::text +END +FROM ranked +WHERE ranked.id = u.id + AND u.username IS NULL; + +ALTER TABLE users + ALTER COLUMN username SET NOT NULL; + +ALTER TABLE users + ADD CONSTRAINT uq_users_username UNIQUE (username), + ADD CONSTRAINT chk_users_username_format CHECK (username ~ '^[a-z0-9_]+$'); + +ALTER TABLE bootcamp_enrollments + ADD COLUMN assigned_sheet_key VARCHAR(64); + +ALTER TABLE bootcamp_enrollments + ADD CONSTRAINT chk_bootcamp_enrollments_assigned_sheet_key + CHECK ( + assigned_sheet_key IS NULL + OR assigned_sheet_key IN ('gfg-dsa-360', 'strivers-dsa-sheet') + ); + +ALTER TABLE assignment_problems + ADD COLUMN resources TEXT, + ADD COLUMN app_progress_status VARCHAR(32) NOT NULL DEFAULT 'not_started'; + +ALTER TABLE assignment_problems + ADD CONSTRAINT chk_assignment_problems_app_progress_status + CHECK ( + app_progress_status IN ( + 'not_started', + 'discussion_needed', + 'revision_needed', + 'completed' + ) + ); + +CREATE TABLE mentee_requests ( + id UUID PRIMARY KEY DEFAULT uuidv7(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, + bootcamp_id UUID NOT NULL REFERENCES bootcamps(id) ON DELETE CASCADE, + status VARCHAR(32) NOT NULL DEFAULT 'pending', + sheet_key VARCHAR(64), + reviewed_by UUID REFERENCES organization_members(id) ON DELETE SET NULL, + reviewed_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT uq_mentee_requests_user_bootcamp UNIQUE (user_id, bootcamp_id), + CONSTRAINT chk_mentee_requests_status CHECK (status IN ('pending', 'approved', 'rejected')), + CONSTRAINT chk_mentee_requests_sheet_key CHECK ( + sheet_key IS NULL + OR sheet_key IN ('gfg-dsa-360', 'strivers-dsa-sheet') + ) +); + +CREATE TRIGGER trg_mentee_requests_updated_at + BEFORE UPDATE ON mentee_requests + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +CREATE INDEX idx_mentee_requests_bootcamp_status ON mentee_requests(bootcamp_id, status); +CREATE INDEX idx_mentee_requests_user_id ON mentee_requests(user_id); + +CREATE TABLE mentee_day_assignments ( + id UUID PRIMARY KEY DEFAULT uuidv7(), + bootcamp_enrollment_id UUID NOT NULL REFERENCES bootcamp_enrollments(id) ON DELETE CASCADE, + weekday VARCHAR(16) NOT NULL, + created_by UUID REFERENCES organization_members(id) ON DELETE SET NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT uq_mentee_day_assignments UNIQUE (bootcamp_enrollment_id, weekday), + CONSTRAINT chk_mentee_day_assignments_weekday CHECK ( + weekday IN ( + 'monday', + 'tuesday', + 'wednesday', + 'thursday', + 'friday', + 'saturday', + 'sunday' + ) + ) +); + +CREATE TRIGGER trg_mentee_day_assignments_updated_at + BEFORE UPDATE ON mentee_day_assignments + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +CREATE INDEX idx_mentee_day_assignments_weekday ON mentee_day_assignments(weekday); diff --git a/apps/server/internal/common/validator/validator.go b/apps/server/internal/common/validator/validator.go index 3b89a63..d0fdbe3 100644 --- a/apps/server/internal/common/validator/validator.go +++ b/apps/server/internal/common/validator/validator.go @@ -111,6 +111,30 @@ func (v *validator) registerCustomValidators() { if err != nil { panic(err) } + + // Register password complexity validator + err = v.validator.RegisterValidation("password_complexity", func(fl go_validator.FieldLevel) bool { + value := fl.Field().String() + hasLetter := false + hasNumber := false + + for _, char := range value { + if (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') { + hasLetter = true + } + if char >= '0' && char <= '9' { + hasNumber = true + } + if hasLetter && hasNumber { + return true + } + } + + return false + }) + if err != nil { + panic(err) + } } // you can register your custom validation diff --git a/apps/server/internal/container/container.go b/apps/server/internal/container/container.go index 1b07845..40ea71d 100644 --- a/apps/server/internal/container/container.go +++ b/apps/server/internal/container/container.go @@ -5,6 +5,7 @@ import ( "github.com/coderz-space/coderz.space/internal/db" db_sqlc "github.com/coderz-space/coderz.space/internal/db/sqlc" "github.com/coderz-space/coderz.space/internal/modules/analytics" + "github.com/coderz-space/coderz.space/internal/modules/app" "github.com/coderz-space/coderz.space/internal/modules/assignment" "github.com/coderz-space/coderz.space/internal/modules/auth" "github.com/coderz-space/coderz.space/internal/modules/bootcamp" @@ -49,6 +50,10 @@ type Container struct { AnalyticsHandler *analytics.Handler AnalyticsService *analytics.Service + // app facade + AppHandler *app.Handler + AppService *app.Service + // DB DB *pgxpool.Pool } @@ -90,6 +95,10 @@ func NewContainer(config *config.Config, logger *zap.Logger) (*Container, error) analyticsService := analytics.NewService(pool) analyticsHandler := analytics.NewHandler(analyticsService) + // Initialize app facade module + appService := app.NewService(pool) + appHandler := app.NewHandler(appService) + container := &Container{ Config: config, Logger: logger, @@ -107,6 +116,8 @@ func NewContainer(config *config.Config, logger *zap.Logger) (*Container, error) ProgressService: progressService, AnalyticsHandler: analyticsHandler, AnalyticsService: analyticsService, + AppHandler: appHandler, + AppService: appService, DB: pool, } return container, nil diff --git a/apps/server/internal/modules/app/data.go b/apps/server/internal/modules/app/data.go new file mode 100644 index 0000000..d1cea52 --- /dev/null +++ b/apps/server/internal/modules/app/data.go @@ -0,0 +1,130 @@ +package app + +import "strings" + +type sheetQuestion struct { + ID string + Title string + Topic string + Difficulty string + Description string +} + +type sheetCatalog struct { + Key string + Name string + Questions []sheetQuestion +} + +var catalogs = map[string]sheetCatalog{ + "gfg-dsa-360": { + Key: "gfg-dsa-360", + Name: "GFG DSA 360", + Questions: []sheetQuestion{ + {ID: "gfg-1", Title: "Array Rotation", Topic: "Arrays", Difficulty: "easy", Description: "Practice array rotation techniques and in-place updates."}, + {ID: "gfg-2", Title: "Kadane's Algorithm", Topic: "Arrays", Difficulty: "medium", Description: "Find the maximum subarray sum using dynamic running totals."}, + {ID: "gfg-3", Title: "Stock Buy and Sell", Topic: "Arrays", Difficulty: "easy", Description: "Track the best buy and sell window for maximum profit."}, + {ID: "gfg-4", Title: "Trapping Rain Water", Topic: "Arrays", Difficulty: "hard", Description: "Compute trapped water using prefix/suffix or two-pointer logic."}, + {ID: "gfg-5", Title: "Reverse a Linked List", Topic: "Linked List", Difficulty: "easy", Description: "Reverse a singly linked list iteratively or recursively."}, + {ID: "gfg-6", Title: "Detect Loop in Linked List", Topic: "Linked List", Difficulty: "medium", Description: "Use fast and slow pointers to detect a cycle."}, + {ID: "gfg-7", Title: "Merge Two Sorted Lists", Topic: "Linked List", Difficulty: "easy", Description: "Merge two sorted linked lists while preserving order."}, + {ID: "gfg-8", Title: "Binary Search", Topic: "Binary Search", Difficulty: "easy", Description: "Implement binary search on a sorted collection."}, + {ID: "gfg-9", Title: "Search in Rotated Array", Topic: "Binary Search", Difficulty: "medium", Description: "Find a target in a rotated sorted array."}, + {ID: "gfg-10", Title: "Balanced Parentheses", Topic: "Stack", Difficulty: "easy", Description: "Validate bracket matching using a stack."}, + {ID: "gfg-11", Title: "Next Greater Element", Topic: "Stack", Difficulty: "medium", Description: "Use a monotonic stack to find next greater values."}, + {ID: "gfg-12", Title: "Level Order Traversal", Topic: "Trees", Difficulty: "easy", Description: "Traverse a binary tree level by level using a queue."}, + {ID: "gfg-13", Title: "Height of Binary Tree", Topic: "Trees", Difficulty: "easy", Description: "Compute binary tree depth using DFS or BFS."}, + {ID: "gfg-14", Title: "Lowest Common Ancestor", Topic: "Trees", Difficulty: "medium", Description: "Find the lowest common ancestor of two nodes."}, + {ID: "gfg-15", Title: "Dijkstra's Algorithm", Topic: "Graphs", Difficulty: "hard", Description: "Compute shortest paths in a weighted graph."}, + }, + }, + "strivers-dsa-sheet": { + Key: "strivers-dsa-sheet", + Name: "Striver's DSA Sheet", + Questions: []sheetQuestion{ + {ID: "stv-1", Title: "Set Matrix Zeroes", Topic: "Arrays", Difficulty: "medium", Description: "Zero matrix rows and columns in-place with minimal extra space."}, + {ID: "stv-2", Title: "Pascal's Triangle", Topic: "Arrays", Difficulty: "easy", Description: "Generate rows of Pascal's triangle."}, + {ID: "stv-3", Title: "Next Permutation", Topic: "Arrays", Difficulty: "medium", Description: "Produce the next lexicographical permutation in-place."}, + {ID: "stv-4", Title: "Maximum Subarray", Topic: "Arrays", Difficulty: "medium", Description: "Find the maximum contiguous subarray sum."}, + {ID: "stv-5", Title: "Sort Colors", Topic: "Arrays", Difficulty: "medium", Description: "Sort three values using the Dutch national flag pattern."}, + {ID: "stv-6", Title: "Two Sum", Topic: "Arrays", Difficulty: "easy", Description: "Return indices of the two numbers that add to the target."}, + {ID: "stv-7", Title: "Reverse Linked List", Topic: "Linked List", Difficulty: "easy", Description: "Reverse a singly linked list."}, + {ID: "stv-8", Title: "Middle of Linked List", Topic: "Linked List", Difficulty: "easy", Description: "Find the middle node with fast and slow pointers."}, + {ID: "stv-9", Title: "Merge Sort", Topic: "Sorting", Difficulty: "medium", Description: "Implement divide-and-conquer merge sort."}, + {ID: "stv-10", Title: "Quick Sort", Topic: "Sorting", Difficulty: "medium", Description: "Partition and sort recursively using quick sort."}, + {ID: "stv-11", Title: "Implement Stack using Queue", Topic: "Stack/Queue", Difficulty: "easy", Description: "Simulate stack operations with queue primitives."}, + {ID: "stv-12", Title: "Sliding Window Maximum", Topic: "Sliding Window", Difficulty: "hard", Description: "Track maximum values inside a moving window."}, + {ID: "stv-13", Title: "Inorder Traversal", Topic: "Trees", Difficulty: "easy", Description: "Traverse a binary tree in inorder sequence."}, + {ID: "stv-14", Title: "Diameter of Binary Tree", Topic: "Trees", Difficulty: "medium", Description: "Compute the longest path through a binary tree."}, + {ID: "stv-15", Title: "Number of Islands", Topic: "Graphs", Difficulty: "medium", Description: "Count connected land components in a grid."}, + }, + }, +} + +var orderedSheetKeys = []string{ + "gfg-dsa-360", + "strivers-dsa-sheet", +} + +func listSheets() []SheetData { + sheets := make([]SheetData, 0, len(orderedSheetKeys)) + for _, key := range orderedSheetKeys { + sheets = append(sheets, sheetToData(catalogs[key])) + } + return sheets +} + +func sheetToData(catalog sheetCatalog) SheetData { + questions := make([]SheetQuestionData, 0, len(catalog.Questions)) + for _, question := range catalog.Questions { + questions = append(questions, SheetQuestionData{ + ID: question.ID, + Title: question.Title, + Topic: question.Topic, + Difficulty: question.Difficulty, + }) + } + + return SheetData{ + Key: catalog.Key, + Name: catalog.Name, + Questions: questions, + } +} + +func findSheet(key string) (sheetCatalog, bool) { + catalog, ok := catalogs[key] + return catalog, ok +} + +func findSheetQuestion(sheetKey, questionID string) (sheetQuestion, bool) { + catalog, ok := catalogs[sheetKey] + if !ok { + return sheetQuestion{}, false + } + + for _, question := range catalog.Questions { + if question.ID == questionID { + return question, true + } + } + + return sheetQuestion{}, false +} + +func catalogLink(sheetKey, questionID string) string { + return "app-sheet:" + sheetKey + ":" + questionID +} + +func findSheetQuestionByLink(link string) (sheetQuestion, bool) { + if !strings.HasPrefix(link, "app-sheet:") { + return sheetQuestion{}, false + } + + parts := strings.Split(link, ":") + if len(parts) != 3 { + return sheetQuestion{}, false + } + + return findSheetQuestion(parts[1], parts[2]) +} diff --git a/apps/server/internal/modules/app/data_test.go b/apps/server/internal/modules/app/data_test.go new file mode 100644 index 0000000..a514a1a --- /dev/null +++ b/apps/server/internal/modules/app/data_test.go @@ -0,0 +1,54 @@ +package app + +import "testing" + +func TestListSheetsPreservesSupportedOrder(t *testing.T) { + sheets := listSheets() + + if len(sheets) != len(orderedSheetKeys) { + t.Fatalf("expected %d sheets, got %d", len(orderedSheetKeys), len(sheets)) + } + + for index, key := range orderedSheetKeys { + if sheets[index].Key != key { + t.Fatalf("expected sheet %d to be %q, got %q", index, key, sheets[index].Key) + } + if len(sheets[index].Questions) == 0 { + t.Fatalf("expected sheet %q to expose questions", key) + } + } +} + +func TestFindSheetQuestionByLinkRoundTrip(t *testing.T) { + link := catalogLink("gfg-dsa-360", "gfg-1") + + question, ok := findSheetQuestionByLink(link) + if !ok { + t.Fatalf("expected link %q to resolve", link) + } + + if question.Title != "Array Rotation" { + t.Fatalf("expected resolved question title %q, got %q", "Array Rotation", question.Title) + } + if question.Topic != "Arrays" { + t.Fatalf("expected resolved question topic %q, got %q", "Arrays", question.Topic) + } +} + +func TestFindSheetQuestionByLinkRejectsUnknownLinks(t *testing.T) { + tests := []string{ + "", + "https://example.com", + "app-sheet:missing-parts", + "app-sheet:unknown-sheet:gfg-1", + "app-sheet:gfg-dsa-360:missing-question", + } + + for _, link := range tests { + t.Run(link, func(t *testing.T) { + if _, ok := findSheetQuestionByLink(link); ok { + t.Fatalf("expected link %q to be rejected", link) + } + }) + } +} diff --git a/apps/server/internal/modules/app/dto.go b/apps/server/internal/modules/app/dto.go new file mode 100644 index 0000000..57337c0 --- /dev/null +++ b/apps/server/internal/modules/app/dto.go @@ -0,0 +1,166 @@ +package app + +type UserData struct { + ID string `json:"id"` + Name string `json:"name"` + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + Username string `json:"username"` + Email string `json:"email"` + Bio string `json:"bio,omitempty"` + Github string `json:"github,omitempty"` + Linkedin string `json:"linkedin,omitempty"` +} + +type OrganizationData struct { + ID string `json:"id"` + Name string `json:"name"` + Slug string `json:"slug"` +} + +type BootcampData struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type EnrollmentData struct { + ID string `json:"id,omitempty"` + AssignedSheet string `json:"assignedSheet,omitempty"` +} + +type ContextData struct { + Role string `json:"role"` + AccountStatus string `json:"accountStatus"` + User UserData `json:"user"` + Organization *OrganizationData `json:"organization,omitempty"` + Bootcamp *BootcampData `json:"bootcamp,omitempty"` + Enrollment *EnrollmentData `json:"enrollment,omitempty"` +} + +type MenteeSignupRequest struct { + FirstName string `json:"firstName" validate:"required,min=2,max=50"` + LastName string `json:"lastName" validate:"omitempty,max=50"` + Username string `json:"username" validate:"required,min=3,max=80"` + Email string `json:"email" validate:"required,email"` + Password string `json:"password" validate:"required,min=8,max=50,password_complexity"` +} + +type MenteeSignupData struct { + RequestID string `json:"requestId"` + Status string `json:"status"` + Username string `json:"username"` + Email string `json:"email"` +} + +type MenteeRequestData struct { + ID string `json:"id"` + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + Username string `json:"username"` + Email string `json:"email"` + SignedUpAt string `json:"signedUpAt"` + Status string `json:"status"` + AssignedSheet string `json:"assignedSheet,omitempty"` +} + +type ReviewMenteeRequest struct { + Status string `json:"status" validate:"required,oneof=approved rejected"` + SheetKey string `json:"sheetKey" validate:"omitempty,oneof=gfg-dsa-360 strivers-dsa-sheet"` +} + +type SheetQuestionData struct { + ID string `json:"id"` + Title string `json:"title"` + Topic string `json:"topic"` + Difficulty string `json:"difficulty"` +} + +type SheetData struct { + Key string `json:"key"` + Name string `json:"name"` + Questions []SheetQuestionData `json:"questions"` +} + +type DayAssignmentMenteeData struct { + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + Username string `json:"username"` + Email string `json:"email"` + Assigned bool `json:"assigned"` + AssignedSheet string `json:"assignedSheet,omitempty"` +} + +type DayAssignmentsData struct { + Day string `json:"day"` + Mentees []DayAssignmentMenteeData `json:"mentees"` +} + +type UpdateDayAssignmentsRequest struct { + Usernames []string `json:"usernames" validate:"required,min=0,dive,min=3,max=80"` +} + +type CreateAssignmentsRequest struct { + Day string `json:"day" validate:"omitempty,oneof=monday tuesday wednesday thursday friday saturday sunday"` + MenteeUsernames []string `json:"menteeUsernames" validate:"required,min=1,dive,min=3,max=80"` + SheetKey string `json:"sheetKey" validate:"required,oneof=gfg-dsa-360 strivers-dsa-sheet"` + QuestionIDs []string `json:"questionIds" validate:"required,min=1,dive,min=1,max=64"` +} + +type CreateAssignmentsData struct { + AssignmentGroupID string `json:"assignmentGroupId"` + AssignmentsCount int `json:"assignmentsCount"` +} + +type QuestionData struct { + ID string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + Difficulty string `json:"difficulty"` + Topic string `json:"topic"` + Status string `json:"status"` + ProgressStatus string `json:"progressStatus"` + AssignedAt string `json:"assignedAt"` + CompletedAt string `json:"completedAt,omitempty"` + Solution string `json:"solution,omitempty"` + Resources string `json:"resources,omitempty"` +} + +type UpdateQuestionRequest struct { + ProgressStatus *string `json:"progressStatus" validate:"omitempty,oneof=not_started discussion_needed revision_needed completed"` + Solution *string `json:"solution" validate:"omitempty,max=2000"` + Resources *string `json:"resources" validate:"omitempty,max=2000"` +} + +type ProfileData struct { + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + Username string `json:"username"` + Email string `json:"email"` + Solved int `json:"solved"` + JoinedAt string `json:"joinedAt"` + Bio string `json:"bio,omitempty"` + Github string `json:"github,omitempty"` + Linkedin string `json:"linkedin,omitempty"` +} + +type UpdateProfileRequest struct { + FirstName string `json:"firstName" validate:"required,min=2,max=50"` + LastName string `json:"lastName" validate:"omitempty,max=50"` + Username string `json:"username" validate:"required,min=3,max=80"` + Email string `json:"email" validate:"required,email"` + Bio string `json:"bio" validate:"omitempty,max=500"` + Github string `json:"github" validate:"omitempty,url"` + Linkedin string `json:"linkedin" validate:"omitempty,url"` +} + +type UpdatePasswordRequest struct { + CurrentPassword string `json:"currentPassword" validate:"required,min=8,max=50"` + NewPassword string `json:"newPassword" validate:"required,min=8,max=50,password_complexity"` +} + +type LeaderboardEntryData struct { + Username string `json:"username"` + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + Solved int `json:"solved"` +} diff --git a/apps/server/internal/modules/app/handler.go b/apps/server/internal/modules/app/handler.go new file mode 100644 index 0000000..a918a9b --- /dev/null +++ b/apps/server/internal/modules/app/handler.go @@ -0,0 +1,310 @@ +package app + +import ( + "net/http" + + authmw "github.com/coderz-space/coderz.space/internal/common/middleware/auth" + "github.com/coderz-space/coderz.space/internal/common/response" + "github.com/coderz-space/coderz.space/internal/common/utils" + "github.com/coderz-space/coderz.space/internal/common/validator" + "github.com/labstack/echo/v5" +) + +type Handler struct { + service *Service +} + +func NewHandler(service *Service) *Handler { + return &Handler{service: service} +} + +func (h *Handler) GetContext(c *echo.Context) error { + userID, err := currentUserID(c) + if err != nil { + return unauthorizedResponse(c, err) + } + + data, err := h.service.GetContext(c.Request().Context(), userID) + if err != nil { + return handleAppError(c, err) + } + + return response.NewResponse(c, http.StatusOK, "OK", "APP_CONTEXT_RETRIEVED", data, nil) +} + +func (h *Handler) MenteeSignup(c *echo.Context) error { + var body MenteeSignupRequest + if err := (&echo.DefaultBinder{}).Bind(c, &body); err != nil { + return response.NewResponse(c, http.StatusBadRequest, "BAD_REQUEST", "INVALID_REQUEST_BODY", nil, err) + } + if err := validator.NewValidator().ValidateStruct(body); err != nil { + return response.NewResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", "VALIDATION_FAILED", nil, err) + } + + data, err := h.service.MenteeSignup(c.Request().Context(), body) + if err != nil { + return handleAppError(c, err) + } + + return response.NewResponse(c, http.StatusCreated, "CREATED", "MENTEE_SIGNUP_REQUEST_CREATED", data, nil) +} + +func (h *Handler) ListMenteeRequests(c *echo.Context) error { + userID, err := currentUserID(c) + if err != nil { + return unauthorizedResponse(c, err) + } + + data, err := h.service.ListMenteeRequests(c.Request().Context(), userID) + if err != nil { + return handleAppError(c, err) + } + + return response.NewResponse(c, http.StatusOK, "OK", "MENTEE_REQUESTS_RETRIEVED", data, nil) +} + +func (h *Handler) ReviewMenteeRequest(c *echo.Context) error { + userID, err := currentUserID(c) + if err != nil { + return unauthorizedResponse(c, err) + } + + var body ReviewMenteeRequest + if err := (&echo.DefaultBinder{}).Bind(c, &body); err != nil { + return response.NewResponse(c, http.StatusBadRequest, "BAD_REQUEST", "INVALID_REQUEST_BODY", nil, err) + } + if err := validator.NewValidator().ValidateStruct(body); err != nil { + return response.NewResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", "VALIDATION_FAILED", nil, err) + } + + data, err := h.service.ReviewMenteeRequest(c.Request().Context(), userID, (*c).Param("requestId"), body) + if err != nil { + return handleAppError(c, err) + } + + return response.NewResponse(c, http.StatusOK, "OK", "MENTEE_REQUEST_UPDATED", data, nil) +} + +func (h *Handler) ListSheets(c *echo.Context) error { + return response.NewResponse(c, http.StatusOK, "OK", "SHEETS_RETRIEVED", h.service.ListSheets(), nil) +} + +func (h *Handler) GetDayAssignments(c *echo.Context) error { + userID, err := currentUserID(c) + if err != nil { + return unauthorizedResponse(c, err) + } + + data, err := h.service.GetDayAssignments(c.Request().Context(), userID, (*c).Param("day")) + if err != nil { + return handleAppError(c, err) + } + + return response.NewResponse(c, http.StatusOK, "OK", "DAY_ASSIGNMENTS_RETRIEVED", data, nil) +} + +func (h *Handler) UpdateDayAssignments(c *echo.Context) error { + userID, err := currentUserID(c) + if err != nil { + return unauthorizedResponse(c, err) + } + + var body UpdateDayAssignmentsRequest + if err := (&echo.DefaultBinder{}).Bind(c, &body); err != nil { + return response.NewResponse(c, http.StatusBadRequest, "BAD_REQUEST", "INVALID_REQUEST_BODY", nil, err) + } + if err := validator.NewValidator().ValidateStruct(body); err != nil { + return response.NewResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", "VALIDATION_FAILED", nil, err) + } + + data, err := h.service.UpdateDayAssignments(c.Request().Context(), userID, (*c).Param("day"), body) + if err != nil { + return handleAppError(c, err) + } + + return response.NewResponse(c, http.StatusOK, "OK", "DAY_ASSIGNMENTS_UPDATED", data, nil) +} + +func (h *Handler) CreateAssignments(c *echo.Context) error { + userID, err := currentUserID(c) + if err != nil { + return unauthorizedResponse(c, err) + } + + var body CreateAssignmentsRequest + if err := (&echo.DefaultBinder{}).Bind(c, &body); err != nil { + return response.NewResponse(c, http.StatusBadRequest, "BAD_REQUEST", "INVALID_REQUEST_BODY", nil, err) + } + if err := validator.NewValidator().ValidateStruct(body); err != nil { + return response.NewResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", "VALIDATION_FAILED", nil, err) + } + + data, err := h.service.CreateAssignments(c.Request().Context(), userID, body) + if err != nil { + return handleAppError(c, err) + } + + return response.NewResponse(c, http.StatusCreated, "CREATED", "ASSIGNMENTS_CREATED", data, nil) +} + +func (h *Handler) ListMenteeQuestions(c *echo.Context) error { + userID, err := currentUserID(c) + if err != nil { + return unauthorizedResponse(c, err) + } + + data, err := h.service.ListMenteeQuestions(c.Request().Context(), userID, (*c).Param("username")) + if err != nil { + return handleAppError(c, err) + } + + return response.NewResponse(c, http.StatusOK, "OK", "QUESTIONS_RETRIEVED", data, nil) +} + +func (h *Handler) GetMenteeQuestion(c *echo.Context) error { + userID, err := currentUserID(c) + if err != nil { + return unauthorizedResponse(c, err) + } + + data, err := h.service.GetMenteeQuestion(c.Request().Context(), userID, (*c).Param("username"), (*c).Param("assignmentProblemId")) + if err != nil { + return handleAppError(c, err) + } + + return response.NewResponse(c, http.StatusOK, "OK", "QUESTION_RETRIEVED", data, nil) +} + +func (h *Handler) UpdateMenteeQuestion(c *echo.Context) error { + userID, err := currentUserID(c) + if err != nil { + return unauthorizedResponse(c, err) + } + + var body UpdateQuestionRequest + if err := (&echo.DefaultBinder{}).Bind(c, &body); err != nil { + return response.NewResponse(c, http.StatusBadRequest, "BAD_REQUEST", "INVALID_REQUEST_BODY", nil, err) + } + if err := validator.NewValidator().ValidateStruct(body); err != nil { + return response.NewResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", "VALIDATION_FAILED", nil, err) + } + + data, err := h.service.UpdateMenteeQuestion(c.Request().Context(), userID, (*c).Param("username"), (*c).Param("assignmentProblemId"), body) + if err != nil { + return handleAppError(c, err) + } + + return response.NewResponse(c, http.StatusOK, "OK", "QUESTION_UPDATED", data, nil) +} + +func (h *Handler) GetMenteeProfile(c *echo.Context) error { + userID, err := currentUserID(c) + if err != nil { + return unauthorizedResponse(c, err) + } + + data, err := h.service.GetMenteeProfile(c.Request().Context(), userID, (*c).Param("username")) + if err != nil { + return handleAppError(c, err) + } + + return response.NewResponse(c, http.StatusOK, "OK", "PROFILE_RETRIEVED", data, nil) +} + +func (h *Handler) GetMyProfile(c *echo.Context) error { + userID, err := currentUserID(c) + if err != nil { + return unauthorizedResponse(c, err) + } + + data, err := h.service.GetMyProfile(c.Request().Context(), userID) + if err != nil { + return handleAppError(c, err) + } + + return response.NewResponse(c, http.StatusOK, "OK", "PROFILE_RETRIEVED", data, nil) +} + +func (h *Handler) UpdateMyProfile(c *echo.Context) error { + userID, err := currentUserID(c) + if err != nil { + return unauthorizedResponse(c, err) + } + + var body UpdateProfileRequest + if err := (&echo.DefaultBinder{}).Bind(c, &body); err != nil { + return response.NewResponse(c, http.StatusBadRequest, "BAD_REQUEST", "INVALID_REQUEST_BODY", nil, err) + } + if err := validator.NewValidator().ValidateStruct(body); err != nil { + return response.NewResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", "VALIDATION_FAILED", nil, err) + } + + data, err := h.service.UpdateMyProfile(c.Request().Context(), userID, body) + if err != nil { + return handleAppError(c, err) + } + + return response.NewResponse(c, http.StatusOK, "OK", "PROFILE_UPDATED", data, nil) +} + +func (h *Handler) UpdateMyPassword(c *echo.Context) error { + userID, err := currentUserID(c) + if err != nil { + return unauthorizedResponse(c, err) + } + + var body UpdatePasswordRequest + if err := (&echo.DefaultBinder{}).Bind(c, &body); err != nil { + return response.NewResponse(c, http.StatusBadRequest, "BAD_REQUEST", "INVALID_REQUEST_BODY", nil, err) + } + if err := validator.NewValidator().ValidateStruct(body); err != nil { + return response.NewResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", "VALIDATION_FAILED", nil, err) + } + + if err := h.service.UpdateMyPassword(c.Request().Context(), userID, body); err != nil { + return handleAppError(c, err) + } + + return response.NewResponse(c, http.StatusOK, "OK", "PASSWORD_UPDATED", map[string]any{}, nil) +} + +func (h *Handler) GetLeaderboard(c *echo.Context) error { + userID, err := currentUserID(c) + if err != nil { + return unauthorizedResponse(c, err) + } + + data, err := h.service.GetLeaderboard(c.Request().Context(), userID) + if err != nil { + return handleAppError(c, err) + } + + return response.NewResponse(c, http.StatusOK, "OK", "LEADERBOARD_RETRIEVED", data, nil) +} + +func currentUserID(c *echo.Context) (string, error) { + claims, ok := (*c).Get(authmw.ClaimsKey).(*utils.TokenPayload) + if !ok { + return "", echo.NewHTTPError(http.StatusUnauthorized, "INVALID_TOKEN_CLAIMS") + } + return claims.UserID, nil +} + +func unauthorizedResponse(c *echo.Context, err error) error { + return response.NewResponse(c, http.StatusUnauthorized, "UNAUTHORIZED", err.Error(), nil, nil) +} + +func handleAppError(c *echo.Context, err error) error { + switch err.Error() { + case "ACCESS_DENIED": + return response.NewResponse(c, http.StatusForbidden, "FORBIDDEN", "ACCESS_DENIED", nil, nil) + case "USER_NOT_FOUND", "MENTEE_NOT_FOUND", "QUESTION_NOT_FOUND", "REQUEST_NOT_FOUND", "SHEET_NOT_FOUND": + return response.NewResponse(c, http.StatusNotFound, "NOT_FOUND", err.Error(), nil, nil) + case "EMAIL_ALREADY_EXISTS", "USERNAME_ALREADY_EXISTS": + return response.NewResponse(c, http.StatusConflict, "CONFLICT", err.Error(), nil, nil) + case "INVALID_USERNAME", "PASSWORD_MUST_CONTAIN_LETTER_AND_NUMBER", "SHEET_REQUIRED", "QUESTION_IDS_REQUIRED", "MENTEES_REQUIRED", "NO_FIELDS_TO_UPDATE", "BOOTCAMP_NOT_CONFIGURED", "INVALID_CURRENT_PASSWORD", "PASSWORD_LOGIN_NOT_AVAILABLE": + return response.NewResponse(c, http.StatusBadRequest, "BAD_REQUEST", err.Error(), nil, nil) + default: + return response.NewResponse(c, http.StatusInternalServerError, "INTERNAL_ERROR", err.Error(), nil, err) + } +} diff --git a/apps/server/internal/modules/app/handler_test.go b/apps/server/internal/modules/app/handler_test.go new file mode 100644 index 0000000..521f312 --- /dev/null +++ b/apps/server/internal/modules/app/handler_test.go @@ -0,0 +1,41 @@ +package app + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/labstack/echo/v5" +) + +func TestHandleAppErrorMapsKnownErrors(t *testing.T) { + tests := []struct { + name string + err error + wantStatus int + }{ + {name: "access denied", err: errors.New("ACCESS_DENIED"), wantStatus: http.StatusForbidden}, + {name: "not found", err: errors.New("QUESTION_NOT_FOUND"), wantStatus: http.StatusNotFound}, + {name: "conflict", err: errors.New("USERNAME_ALREADY_EXISTS"), wantStatus: http.StatusConflict}, + {name: "bad request", err: errors.New("INVALID_CURRENT_PASSWORD"), wantStatus: http.StatusBadRequest}, + {name: "internal", err: errors.New("SOMETHING_ELSE"), wantStatus: http.StatusInternalServerError}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/v1/app/context", nil) + rec := httptest.NewRecorder() + ctx := e.NewContext(req, rec) + + if err := handleAppError(ctx, tt.err); err != nil { + t.Fatalf("expected response to be written, got error %v", err) + } + + if rec.Code != tt.wantStatus { + t.Fatalf("expected status %d, got %d", tt.wantStatus, rec.Code) + } + }) + } +} diff --git a/apps/server/internal/modules/app/routes.go b/apps/server/internal/modules/app/routes.go new file mode 100644 index 0000000..87e1ea2 --- /dev/null +++ b/apps/server/internal/modules/app/routes.go @@ -0,0 +1,33 @@ +package app + +import ( + authmw "github.com/coderz-space/coderz.space/internal/common/middleware/auth" + "github.com/coderz-space/coderz.space/internal/config" + "github.com/labstack/echo/v5" +) + +func RegisterPublicRoutes(e *echo.Group, handler *Handler) { + appRouter := e.Group("/v1/app") + appRouter.POST("/auth/mentee-signup", handler.MenteeSignup) +} + +func RegisterProtectedRoutes(e *echo.Group, handler *Handler, config *config.Config) { + appRouter := e.Group("/v1/app") + appRouter.Use(authmw.AuthMiddleware(config.JWTSecret, config.JWTExpires)) + + appRouter.GET("/context", handler.GetContext) + appRouter.GET("/mentor/mentee-requests", handler.ListMenteeRequests) + appRouter.PATCH("/mentor/mentee-requests/:requestId", handler.ReviewMenteeRequest) + appRouter.GET("/sheets", handler.ListSheets) + appRouter.GET("/mentor/day-assignments/:day", handler.GetDayAssignments) + appRouter.PUT("/mentor/day-assignments/:day", handler.UpdateDayAssignments) + appRouter.POST("/mentor/assignments", handler.CreateAssignments) + appRouter.GET("/mentees/:username/questions", handler.ListMenteeQuestions) + appRouter.GET("/mentees/:username/questions/:assignmentProblemId", handler.GetMenteeQuestion) + appRouter.PATCH("/mentees/:username/questions/:assignmentProblemId", handler.UpdateMenteeQuestion) + appRouter.GET("/mentees/:username/profile", handler.GetMenteeProfile) + appRouter.GET("/me/profile", handler.GetMyProfile) + appRouter.PATCH("/me/profile", handler.UpdateMyProfile) + appRouter.PATCH("/me/password", handler.UpdateMyPassword) + appRouter.GET("/leaderboard", handler.GetLeaderboard) +} diff --git a/apps/server/internal/modules/app/service.go b/apps/server/internal/modules/app/service.go new file mode 100644 index 0000000..c395a84 --- /dev/null +++ b/apps/server/internal/modules/app/service.go @@ -0,0 +1,1859 @@ +package app + +import ( + "context" + "errors" + "fmt" + "regexp" + "strings" + "time" + + db "github.com/coderz-space/coderz.space/internal/db/sqlc" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" + "github.com/jackc/pgx/v5/pgxpool" + "golang.org/x/crypto/bcrypt" +) + +var usernamePattern = regexp.MustCompile(`^[a-z0-9_]+$`) + +type Service struct { + pool *pgxpool.Pool +} + +type resolvedContext struct { + User UserData + UserID string + Organization *OrganizationData + Bootcamp *BootcampData + MemberID string + OrgRole string + EnrollmentID string + EnrollmentRole string + AssignedSheet string + Role string + AccountStatus string +} + +type menteeRecord struct { + EnrollmentID string + MemberID string + UserID string + FirstName string + LastName string + Username string + Email string + AssignedSheet string + EnrolledAt time.Time +} + +type questionRow struct { + ID string + AssignmentID string + TargetUsername string + Title string + Description string + Difficulty string + ExternalLink string + AppProgress string + LegacyStatus string + Notes string + Resources string + AssignedAt time.Time + CompletedAt pgtype.Timestamptz +} + +func NewService(pool *pgxpool.Pool) *Service { + return &Service{pool: pool} +} + +func (s *Service) GetContext(ctx context.Context, userID string) (*ContextData, error) { + resolved, err := s.resolveContext(ctx, userID) + if err != nil { + return nil, err + } + + data := &ContextData{ + Role: resolved.Role, + AccountStatus: resolved.AccountStatus, + User: resolved.User, + Organization: resolved.Organization, + Bootcamp: resolved.Bootcamp, + } + if resolved.EnrollmentID != "" || resolved.AssignedSheet != "" { + data.Enrollment = &EnrollmentData{ + ID: resolved.EnrollmentID, + AssignedSheet: resolved.AssignedSheet, + } + } + + return data, nil +} + +func (s *Service) MenteeSignup(ctx context.Context, req MenteeSignupRequest) (*MenteeSignupData, error) { + username := normalizeUsername(req.Username) + if !usernamePattern.MatchString(username) { + return nil, errors.New("INVALID_USERNAME") + } + if !validatePasswordComplexity(req.Password) { + return nil, errors.New("PASSWORD_MUST_CONTAIN_LETTER_AND_NUMBER") + } + + defaultOrg, defaultBootcamp, err := s.getDefaultSignupContext(ctx) + if err != nil { + return nil, err + } + + fullName := strings.TrimSpace(strings.TrimSpace(req.FirstName) + " " + strings.TrimSpace(req.LastName)) + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) + if err != nil { + return nil, err + } + + tx, err := s.pool.Begin(ctx) + if err != nil { + return nil, err + } + defer func() { + _ = tx.Rollback(ctx) + }() + + var emailExists bool + if err := tx.QueryRow(ctx, ` + SELECT EXISTS( + SELECT 1 + FROM coderz.users + WHERE LOWER(COALESCE(email, '')) = LOWER($1) + ) + `, req.Email).Scan(&emailExists); err != nil { + return nil, err + } + if emailExists { + return nil, errors.New("EMAIL_ALREADY_EXISTS") + } + + var usernameExists bool + if err := tx.QueryRow(ctx, ` + SELECT EXISTS( + SELECT 1 + FROM coderz.users + WHERE LOWER(username) = LOWER($1) + ) + `, username).Scan(&usernameExists); err != nil { + return nil, err + } + if usernameExists { + return nil, errors.New("USERNAME_ALREADY_EXISTS") + } + + var userIDValue string + if err := tx.QueryRow(ctx, ` + INSERT INTO coderz.users ( + name, + email, + password_hash, + role, + username + ) VALUES ( + $1, + $2, + $3, + 'user', + $4 + ) + RETURNING id::text + `, fullName, req.Email, string(hashedPassword), username).Scan(&userIDValue); err != nil { + return nil, err + } + + var requestID string + if err := tx.QueryRow(ctx, ` + INSERT INTO coderz.mentee_requests ( + user_id, + organization_id, + bootcamp_id, + status + ) VALUES ( + $1, + $2, + $3, + 'pending' + ) + RETURNING id::text + `, userIDValue, defaultOrg.ID, defaultBootcamp.ID).Scan(&requestID); err != nil { + return nil, err + } + + if err := tx.Commit(ctx); err != nil { + return nil, err + } + + return &MenteeSignupData{ + RequestID: requestID, + Status: "pending", + Username: username, + Email: req.Email, + }, nil +} + +func (s *Service) ListMenteeRequests(ctx context.Context, userID string) ([]MenteeRequestData, error) { + resolved, err := s.resolveMentorContext(ctx, userID) + if err != nil { + return nil, err + } + + rows, err := s.pool.Query(ctx, ` + SELECT + mr.id::text, + u.name, + COALESCE(u.username, ''), + COALESCE(u.email, ''), + mr.created_at, + mr.status, + COALESCE(mr.sheet_key, '') + FROM coderz.mentee_requests mr + JOIN coderz.users u ON u.id = mr.user_id + WHERE mr.bootcamp_id = $1 + ORDER BY + CASE WHEN mr.status = 'pending' THEN 0 ELSE 1 END, + mr.created_at DESC + `, resolved.Bootcamp.ID) + if err != nil { + return nil, err + } + defer rows.Close() + + requests := make([]MenteeRequestData, 0) + for rows.Next() { + var ( + requestID string + fullName string + username string + email string + signedUpAt time.Time + status string + assignedSheet string + ) + if err := rows.Scan(&requestID, &fullName, &username, &email, &signedUpAt, &status, &assignedSheet); err != nil { + return nil, err + } + + firstName, lastName := splitName(fullName) + requests = append(requests, MenteeRequestData{ + ID: requestID, + FirstName: firstName, + LastName: lastName, + Username: username, + Email: email, + SignedUpAt: signedUpAt.Format(time.RFC3339), + Status: status, + AssignedSheet: assignedSheet, + }) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return requests, nil +} + +func (s *Service) ReviewMenteeRequest(ctx context.Context, userID, requestID string, req ReviewMenteeRequest) (*MenteeRequestData, error) { + resolved, err := s.resolveMentorContext(ctx, userID) + if err != nil { + return nil, err + } + if req.Status == "approved" && req.SheetKey == "" { + return nil, errors.New("SHEET_REQUIRED") + } + + tx, err := s.pool.Begin(ctx) + if err != nil { + return nil, err + } + defer func() { + _ = tx.Rollback(ctx) + }() + + var ( + targetUserID string + orgID string + bootcampID string + fullName string + username string + email string + createdAt time.Time + ) + if err := tx.QueryRow(ctx, ` + SELECT + mr.user_id::text, + mr.organization_id::text, + mr.bootcamp_id::text, + u.name, + COALESCE(u.username, ''), + COALESCE(u.email, ''), + mr.created_at + FROM coderz.mentee_requests mr + JOIN coderz.users u ON u.id = mr.user_id + WHERE mr.id = $1 + AND mr.bootcamp_id = $2 + `, requestID, resolved.Bootcamp.ID).Scan( + &targetUserID, + &orgID, + &bootcampID, + &fullName, + &username, + &email, + &createdAt, + ); err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, errors.New("REQUEST_NOT_FOUND") + } + return nil, err + } + + if req.Status == "approved" { + memberID, err := s.ensureOrganizationMember(ctx, tx, orgID, targetUserID) + if err != nil { + return nil, err + } + if err := s.ensureBootcampEnrollment(ctx, tx, bootcampID, memberID, req.SheetKey); err != nil { + return nil, err + } + } + + if _, err := tx.Exec(ctx, ` + UPDATE coderz.mentee_requests + SET + status = $2, + sheet_key = NULLIF($3, ''), + reviewed_by = $4, + reviewed_at = CURRENT_TIMESTAMP, + updated_at = CURRENT_TIMESTAMP + WHERE id = $1 + `, requestID, req.Status, req.SheetKey, resolved.MemberID); err != nil { + return nil, err + } + + if err := tx.Commit(ctx); err != nil { + return nil, err + } + + firstName, lastName := splitName(fullName) + return &MenteeRequestData{ + ID: requestID, + FirstName: firstName, + LastName: lastName, + Username: username, + Email: email, + SignedUpAt: createdAt.Format(time.RFC3339), + Status: req.Status, + AssignedSheet: req.SheetKey, + }, nil +} + +func (s *Service) ListSheets() []SheetData { + return listSheets() +} + +func (s *Service) GetDayAssignments(ctx context.Context, userID, day string) (*DayAssignmentsData, error) { + resolved, err := s.resolveMentorContext(ctx, userID) + if err != nil { + return nil, err + } + + normalizedDay := normalizeDay(day) + rows, err := s.pool.Query(ctx, ` + SELECT + u.name, + COALESCE(u.username, ''), + COALESCE(u.email, ''), + COALESCE(be.assigned_sheet_key, ''), + (mda.id IS NOT NULL) AS assigned + FROM coderz.bootcamp_enrollments be + JOIN coderz.organization_members om ON om.id = be.organization_member_id + JOIN coderz.users u ON u.id = om.user_id + LEFT JOIN coderz.mentee_day_assignments mda + ON mda.bootcamp_enrollment_id = be.id + AND mda.weekday = $2 + WHERE be.bootcamp_id = $1 + AND be.role = 'mentee' + AND be.status = 'active' + ORDER BY u.name ASC + `, resolved.Bootcamp.ID, normalizedDay) + if err != nil { + return nil, err + } + defer rows.Close() + + mentees := make([]DayAssignmentMenteeData, 0) + for rows.Next() { + var ( + fullName string + username string + email string + assignedSheet string + assigned bool + ) + if err := rows.Scan(&fullName, &username, &email, &assignedSheet, &assigned); err != nil { + return nil, err + } + + firstName, lastName := splitName(fullName) + mentees = append(mentees, DayAssignmentMenteeData{ + FirstName: firstName, + LastName: lastName, + Username: username, + Email: email, + Assigned: assigned, + AssignedSheet: assignedSheet, + }) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return &DayAssignmentsData{ + Day: normalizedDay, + Mentees: mentees, + }, nil +} + +func (s *Service) UpdateDayAssignments(ctx context.Context, userID, day string, req UpdateDayAssignmentsRequest) (*DayAssignmentsData, error) { + resolved, err := s.resolveMentorContext(ctx, userID) + if err != nil { + return nil, err + } + + normalizedDay := normalizeDay(day) + targets := dedupeLower(req.Usernames) + + tx, err := s.pool.Begin(ctx) + if err != nil { + return nil, err + } + defer func() { + _ = tx.Rollback(ctx) + }() + + enrollmentMap, err := s.listMenteeEnrollmentMap(ctx, tx, resolved.Bootcamp.ID) + if err != nil { + return nil, err + } + + if _, err := tx.Exec(ctx, ` + DELETE FROM coderz.mentee_day_assignments mda + USING coderz.bootcamp_enrollments be + WHERE mda.bootcamp_enrollment_id = be.id + AND be.bootcamp_id = $1 + AND mda.weekday = $2 + `, resolved.Bootcamp.ID, normalizedDay); err != nil { + return nil, err + } + + for _, username := range targets { + enrollmentID, ok := enrollmentMap[username] + if !ok { + return nil, errors.New("MENTEE_NOT_FOUND") + } + + if _, err := tx.Exec(ctx, ` + INSERT INTO coderz.mentee_day_assignments ( + bootcamp_enrollment_id, + weekday, + created_by + ) VALUES ( + $1, + $2, + $3 + ) + `, enrollmentID, normalizedDay, resolved.MemberID); err != nil { + return nil, err + } + } + + if err := tx.Commit(ctx); err != nil { + return nil, err + } + + return s.GetDayAssignments(ctx, userID, normalizedDay) +} + +func (s *Service) CreateAssignments(ctx context.Context, userID string, req CreateAssignmentsRequest) (*CreateAssignmentsData, error) { + resolved, err := s.resolveMentorContext(ctx, userID) + if err != nil { + return nil, err + } + + catalog, ok := findSheet(req.SheetKey) + if !ok { + return nil, errors.New("SHEET_NOT_FOUND") + } + + questionIDs := dedupeStrings(req.QuestionIDs) + if len(questionIDs) == 0 { + return nil, errors.New("QUESTION_IDS_REQUIRED") + } + + selectedQuestions := make([]sheetQuestion, 0, len(questionIDs)) + for _, questionID := range questionIDs { + question, found := findSheetQuestion(req.SheetKey, questionID) + if !found { + return nil, errors.New("QUESTION_NOT_FOUND") + } + selectedQuestions = append(selectedQuestions, question) + } + + targetUsernames := dedupeLower(req.MenteeUsernames) + if len(targetUsernames) == 0 { + return nil, errors.New("MENTEES_REQUIRED") + } + + mentees, err := s.listMenteeRecords(ctx, resolved.Bootcamp.ID) + if err != nil { + return nil, err + } + menteeByUsername := make(map[string]menteeRecord, len(mentees)) + for _, mentee := range mentees { + menteeByUsername[strings.ToLower(mentee.Username)] = mentee + } + + targets := make([]menteeRecord, 0, len(targetUsernames)) + for _, username := range targetUsernames { + mentee, ok := menteeByUsername[username] + if !ok { + return nil, errors.New("MENTEE_NOT_FOUND") + } + targets = append(targets, mentee) + } + + tx, err := s.pool.Begin(ctx) + if err != nil { + return nil, err + } + defer func() { + _ = tx.Rollback(ctx) + }() + + groupTitle := fmt.Sprintf("Algo Buddy %s", catalog.Name) + if req.Day != "" { + groupTitle = fmt.Sprintf("Algo Buddy %s %s", capitalizeWord(normalizeDay(req.Day)), catalog.Name) + } + + description := fmt.Sprintf("App assignment generated from %s", catalog.Name) + if req.Day != "" { + description = fmt.Sprintf("App assignment generated for %s from %s", normalizeDay(req.Day), catalog.Name) + } + + var assignmentGroupID string + if err := tx.QueryRow(ctx, ` + INSERT INTO coderz.assignment_groups ( + bootcamp_id, + created_by, + title, + description + ) VALUES ( + $1, + $2, + $3, + $4 + ) + RETURNING id::text + `, resolved.Bootcamp.ID, resolved.MemberID, groupTitle, description).Scan(&assignmentGroupID); err != nil { + return nil, err + } + + problemIDs := make([]string, 0, len(selectedQuestions)) + for index, question := range selectedQuestions { + problemID, err := s.getOrCreateProblem(ctx, tx, resolved.Organization.ID, resolved.MemberID, req.SheetKey, question) + if err != nil { + return nil, err + } + problemIDs = append(problemIDs, problemID) + + if _, err := tx.Exec(ctx, ` + INSERT INTO coderz.assignment_group_problems ( + assignment_group_id, + problem_id, + position + ) VALUES ( + $1, + $2, + $3 + ) + `, assignmentGroupID, problemID, index+1); err != nil { + return nil, err + } + } + + for _, mentee := range targets { + var assignmentID string + if err := tx.QueryRow(ctx, ` + INSERT INTO coderz.assignments ( + assignment_group_id, + bootcamp_enrollment_id, + assigned_by, + status + ) VALUES ( + $1, + $2, + $3, + 'active' + ) + RETURNING id::text + `, assignmentGroupID, mentee.EnrollmentID, resolved.MemberID).Scan(&assignmentID); err != nil { + return nil, err + } + + for _, problemID := range problemIDs { + if _, err := tx.Exec(ctx, ` + INSERT INTO coderz.assignment_problems ( + assignment_id, + problem_id, + status, + app_progress_status + ) VALUES ( + $1, + $2, + 'pending', + 'not_started' + ) + `, assignmentID, problemID); err != nil { + return nil, err + } + } + } + + if err := tx.Commit(ctx); err != nil { + return nil, err + } + + if err := s.refreshLeaderboard(ctx, s.pool, resolved.Bootcamp.ID); err != nil { + return nil, err + } + + return &CreateAssignmentsData{ + AssignmentGroupID: assignmentGroupID, + AssignmentsCount: len(targets), + }, nil +} + +func (s *Service) ListMenteeQuestions(ctx context.Context, userID, username string) ([]QuestionData, error) { + resolved, err := s.resolveContext(ctx, userID) + if err != nil { + return nil, err + } + if resolved.Bootcamp == nil || resolved.AccountStatus != "approved" { + return nil, errors.New("ACCESS_DENIED") + } + + rows, err := s.pool.Query(ctx, ` + SELECT + ap.id::text, + a.id::text, + COALESCE(u.username, ''), + p.title, + COALESCE(p.description, ''), + p.difficulty::text, + COALESCE(p.external_link, ''), + COALESCE(ap.app_progress_status, ''), + ap.status::text, + COALESCE(ap.notes, ''), + COALESCE(ap.resources, ''), + a.assigned_at, + ap.completed_at + FROM coderz.assignment_problems ap + JOIN coderz.assignments a ON a.id = ap.assignment_id AND a.archived_at IS NULL + JOIN coderz.problems p ON p.id = ap.problem_id + JOIN coderz.bootcamp_enrollments be ON be.id = a.bootcamp_enrollment_id + JOIN coderz.organization_members om ON om.id = be.organization_member_id + JOIN coderz.users u ON u.id = om.user_id + WHERE be.bootcamp_id = $1 + AND be.role = 'mentee' + AND be.status = 'active' + AND LOWER(COALESCE(u.username, '')) = LOWER($2) + ORDER BY a.assigned_at DESC, ap.created_at ASC + `, resolved.Bootcamp.ID, username) + if err != nil { + return nil, err + } + defer rows.Close() + + questions := make([]QuestionData, 0) + for rows.Next() { + row, err := scanQuestionRow(rows) + if err != nil { + return nil, err + } + questions = append(questions, row.toQuestionData()) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return questions, nil +} + +func (s *Service) GetMenteeQuestion(ctx context.Context, userID, username, assignmentProblemID string) (*QuestionData, error) { + resolved, err := s.resolveContext(ctx, userID) + if err != nil { + return nil, err + } + if resolved.Bootcamp == nil || resolved.AccountStatus != "approved" { + return nil, errors.New("ACCESS_DENIED") + } + + row, err := s.getQuestionRow(ctx, s.pool, resolved.Bootcamp.ID, username, assignmentProblemID) + if err != nil { + return nil, err + } + + data := row.toQuestionData() + return &data, nil +} + +func (s *Service) UpdateMenteeQuestion(ctx context.Context, userID, username, assignmentProblemID string, req UpdateQuestionRequest) (*QuestionData, error) { + resolved, err := s.resolveContext(ctx, userID) + if err != nil { + return nil, err + } + if resolved.Bootcamp == nil || resolved.AccountStatus != "approved" { + return nil, errors.New("ACCESS_DENIED") + } + if resolved.Role != "mentor" && !strings.EqualFold(resolved.User.Username, username) { + return nil, errors.New("ACCESS_DENIED") + } + if req.ProgressStatus == nil && req.Solution == nil && req.Resources == nil { + return nil, errors.New("NO_FIELDS_TO_UPDATE") + } + + currentRow, err := s.getQuestionRow(ctx, s.pool, resolved.Bootcamp.ID, username, assignmentProblemID) + if err != nil { + return nil, err + } + + progressStatus := currentRow.normalizedProgressStatus() + if req.ProgressStatus != nil { + progressStatus = *req.ProgressStatus + } + + legacyStatus := mapProgressToLegacyStatus(progressStatus) + setCompletedAt := req.ProgressStatus != nil && progressStatus == "completed" + clearCompletedAt := req.ProgressStatus != nil && currentRow.normalizedProgressStatus() == "completed" && progressStatus != "completed" + + tx, err := s.pool.Begin(ctx) + if err != nil { + return nil, err + } + defer func() { + _ = tx.Rollback(ctx) + }() + + if _, err := tx.Exec(ctx, ` + UPDATE coderz.assignment_problems + SET + app_progress_status = $2, + status = $3, + notes = CASE WHEN $4 THEN $5 ELSE notes END, + resources = CASE WHEN $6 THEN $7 ELSE resources END, + completed_at = CASE + WHEN $8 THEN CURRENT_TIMESTAMP + WHEN $9 THEN NULL + ELSE completed_at + END, + updated_at = CURRENT_TIMESTAMP + WHERE id = $1 + `, assignmentProblemID, progressStatus, legacyStatus, req.Solution != nil, valueOrEmpty(req.Solution), req.Resources != nil, valueOrEmpty(req.Resources), setCompletedAt, clearCompletedAt); err != nil { + return nil, err + } + + if err := s.updateAssignmentAggregate(ctx, tx, currentRow.AssignmentID); err != nil { + return nil, err + } + if err := s.refreshLeaderboard(ctx, tx, resolved.Bootcamp.ID); err != nil { + return nil, err + } + + if err := tx.Commit(ctx); err != nil { + return nil, err + } + + updatedRow, err := s.getQuestionRow(ctx, s.pool, resolved.Bootcamp.ID, username, assignmentProblemID) + if err != nil { + return nil, err + } + + data := updatedRow.toQuestionData() + return &data, nil +} + +func (s *Service) GetMenteeProfile(ctx context.Context, userID, username string) (*ProfileData, error) { + resolved, err := s.resolveContext(ctx, userID) + if err != nil { + return nil, err + } + if resolved.Bootcamp == nil || resolved.AccountStatus != "approved" { + return nil, errors.New("ACCESS_DENIED") + } + + mentee, err := s.findMenteeByUsername(ctx, s.pool, resolved.Bootcamp.ID, username) + if err != nil { + return nil, err + } + + solved, err := s.countCompletedProblems(ctx, resolved.Bootcamp.ID, username) + if err != nil { + return nil, err + } + + var ( + bio string + github string + linkedin string + ) + if err := s.pool.QueryRow(ctx, ` + SELECT + COALESCE(bio, ''), + COALESCE(github_url, ''), + COALESCE(linkedin_url, '') + FROM coderz.users + WHERE id = $1 + `, mentee.UserID).Scan(&bio, &github, &linkedin); err != nil { + return nil, err + } + + return &ProfileData{ + FirstName: mentee.FirstName, + LastName: mentee.LastName, + Username: mentee.Username, + Email: "", + Solved: solved, + JoinedAt: mentee.EnrolledAt.Format(time.RFC3339), + Bio: bio, + Github: github, + Linkedin: linkedin, + }, nil +} + +func (s *Service) GetMyProfile(ctx context.Context, userID string) (*ProfileData, error) { + resolved, err := s.resolveContext(ctx, userID) + if err != nil { + return nil, err + } + + solved := 0 + if resolved.Bootcamp != nil { + solved, err = s.countCompletedProblems(ctx, resolved.Bootcamp.ID, resolved.User.Username) + if err != nil { + return nil, err + } + } + + var createdAt time.Time + if err := s.pool.QueryRow(ctx, ` + SELECT created_at + FROM coderz.users + WHERE id = $1 + `, resolved.UserID).Scan(&createdAt); err != nil { + return nil, err + } + joinedAt := createdAt.Format(time.RFC3339) + + if resolved.EnrollmentID != "" { + var enrolledAt time.Time + if err := s.pool.QueryRow(ctx, ` + SELECT enrolled_at + FROM coderz.bootcamp_enrollments + WHERE id = $1 + `, resolved.EnrollmentID).Scan(&enrolledAt); err == nil { + joinedAt = enrolledAt.Format(time.RFC3339) + } + } + + return &ProfileData{ + FirstName: resolved.User.FirstName, + LastName: resolved.User.LastName, + Username: resolved.User.Username, + Email: resolved.User.Email, + Solved: solved, + JoinedAt: joinedAt, + Bio: resolved.User.Bio, + Github: resolved.User.Github, + Linkedin: resolved.User.Linkedin, + }, nil +} + +func (s *Service) UpdateMyProfile(ctx context.Context, userID string, req UpdateProfileRequest) (*ProfileData, error) { + resolved, err := s.resolveContext(ctx, userID) + if err != nil { + return nil, err + } + + username := normalizeUsername(req.Username) + if !usernamePattern.MatchString(username) { + return nil, errors.New("INVALID_USERNAME") + } + + fullName := strings.TrimSpace(strings.TrimSpace(req.FirstName) + " " + strings.TrimSpace(req.LastName)) + if _, err := s.pool.Exec(ctx, ` + UPDATE coderz.users + SET + name = $2, + email = $3, + username = $4, + bio = NULLIF($5, ''), + github_url = NULLIF($6, ''), + linkedin_url = NULLIF($7, ''), + updated_at = CURRENT_TIMESTAMP + WHERE id = $1 + `, resolved.UserID, fullName, req.Email, username, req.Bio, req.Github, req.Linkedin); err != nil { + lowerErr := strings.ToLower(err.Error()) + if strings.Contains(lowerErr, "uq_users_username") { + return nil, errors.New("USERNAME_ALREADY_EXISTS") + } + if strings.Contains(lowerErr, "users_email_key") { + return nil, errors.New("EMAIL_ALREADY_EXISTS") + } + return nil, err + } + + return s.GetMyProfile(ctx, userID) +} + +func (s *Service) UpdateMyPassword(ctx context.Context, userID string, req UpdatePasswordRequest) error { + if !validatePasswordComplexity(req.NewPassword) { + return errors.New("PASSWORD_MUST_CONTAIN_LETTER_AND_NUMBER") + } + + var passwordHash pgtype.Text + if err := s.pool.QueryRow(ctx, ` + SELECT password_hash + FROM coderz.users + WHERE id = $1 + `, userID).Scan(&passwordHash); err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return errors.New("USER_NOT_FOUND") + } + return err + } + if !passwordHash.Valid { + return errors.New("PASSWORD_LOGIN_NOT_AVAILABLE") + } + if err := bcrypt.CompareHashAndPassword([]byte(passwordHash.String), []byte(req.CurrentPassword)); err != nil { + return errors.New("INVALID_CURRENT_PASSWORD") + } + + newHash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost) + if err != nil { + return err + } + + tx, err := s.pool.Begin(ctx) + if err != nil { + return err + } + defer func() { + _ = tx.Rollback(ctx) + }() + + if _, err := tx.Exec(ctx, ` + UPDATE coderz.users + SET + password_hash = $2, + updated_at = CURRENT_TIMESTAMP + WHERE id = $1 + `, userID, string(newHash)); err != nil { + return err + } + if _, err := tx.Exec(ctx, ` + DELETE FROM coderz.refresh_tokens + WHERE user_id = $1 + `, userID); err != nil { + return err + } + + return tx.Commit(ctx) +} + +func (s *Service) GetLeaderboard(ctx context.Context, userID string) ([]LeaderboardEntryData, error) { + resolved, err := s.resolveContext(ctx, userID) + if err != nil { + return nil, err + } + if resolved.Bootcamp == nil || resolved.AccountStatus != "approved" { + return nil, errors.New("ACCESS_DENIED") + } + + if err := s.refreshLeaderboard(ctx, s.pool, resolved.Bootcamp.ID); err != nil { + return nil, err + } + + rows, err := s.pool.Query(ctx, ` + SELECT + COALESCE(u.username, ''), + u.name, + COALESCE(le.problems_completed, 0) AS solved + FROM coderz.bootcamp_enrollments be + JOIN coderz.organization_members om ON om.id = be.organization_member_id + JOIN coderz.users u ON u.id = om.user_id + LEFT JOIN coderz.leaderboard_entries le + ON le.bootcamp_enrollment_id = be.id + AND le.bootcamp_id = be.bootcamp_id + WHERE be.bootcamp_id = $1 + AND be.role = 'mentee' + AND be.status = 'active' + ORDER BY COALESCE(le.rank, 2147483647), solved DESC, u.name ASC + `, resolved.Bootcamp.ID) + if err != nil { + return nil, err + } + defer rows.Close() + + entries := make([]LeaderboardEntryData, 0) + for rows.Next() { + var ( + username string + fullName string + solved int + ) + if err := rows.Scan(&username, &fullName, &solved); err != nil { + return nil, err + } + + firstName, lastName := splitName(fullName) + entries = append(entries, LeaderboardEntryData{ + Username: username, + FirstName: firstName, + LastName: lastName, + Solved: solved, + }) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return entries, nil +} + +func (s *Service) resolveContext(ctx context.Context, userID string) (*resolvedContext, error) { + var ( + foundID string + name string + username string + email string + bio string + github string + linkedin string + ) + if err := s.pool.QueryRow(ctx, ` + SELECT + id::text, + name, + COALESCE(username, ''), + COALESCE(email, ''), + COALESCE(bio, ''), + COALESCE(github_url, ''), + COALESCE(linkedin_url, '') + FROM coderz.users + WHERE id = $1 + `, userID).Scan(&foundID, &name, &username, &email, &bio, &github, &linkedin); err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, errors.New("USER_NOT_FOUND") + } + return nil, err + } + + firstName, lastName := splitName(name) + resolved := &resolvedContext{ + UserID: foundID, + User: UserData{ + ID: foundID, + Name: name, + FirstName: firstName, + LastName: lastName, + Username: username, + Email: email, + Bio: bio, + Github: github, + Linkedin: linkedin, + }, + Role: "unknown", + AccountStatus: "unassigned", + } + + var ( + memberID string + orgRole string + orgID string + orgName string + orgSlug string + bootcampID string + bootcampName string + enrollmentID string + enrollmentRole string + assignedSheet string + ) + err := s.pool.QueryRow(ctx, ` + SELECT + om.id::text, + om.role::text, + o.id::text, + o.name, + o.slug, + b.id::text, + b.name, + be.id::text, + be.role::text, + COALESCE(be.assigned_sheet_key, '') + FROM coderz.bootcamp_enrollments be + JOIN coderz.organization_members om ON om.id = be.organization_member_id + JOIN coderz.organizations o ON o.id = om.organization_id + JOIN coderz.bootcamps b ON b.id = be.bootcamp_id + WHERE om.user_id = $1 + AND o.status = 'approved' + AND b.archived_at IS NULL + AND b.is_active = TRUE + AND be.status = 'active' + ORDER BY b.created_at DESC, be.enrolled_at DESC + LIMIT 1 + `, userID).Scan(&memberID, &orgRole, &orgID, &orgName, &orgSlug, &bootcampID, &bootcampName, &enrollmentID, &enrollmentRole, &assignedSheet) + if err == nil { + resolved.MemberID = memberID + resolved.OrgRole = orgRole + resolved.Organization = &OrganizationData{ID: orgID, Name: orgName, Slug: orgSlug} + resolved.Bootcamp = &BootcampData{ID: bootcampID, Name: bootcampName} + resolved.EnrollmentID = enrollmentID + resolved.EnrollmentRole = enrollmentRole + resolved.AssignedSheet = assignedSheet + resolved.AccountStatus = "approved" + if enrollmentRole == "mentor" || orgRole == "admin" || orgRole == "mentor" { + resolved.Role = "mentor" + } else { + resolved.Role = "mentee" + } + return resolved, nil + } + if !errors.Is(err, pgx.ErrNoRows) { + return nil, err + } + + err = s.pool.QueryRow(ctx, ` + SELECT + om.id::text, + om.role::text, + o.id::text, + o.name, + o.slug, + b.id::text, + b.name + FROM coderz.organization_members om + JOIN coderz.organizations o ON o.id = om.organization_id + JOIN coderz.bootcamps b ON b.organization_id = o.id + WHERE om.user_id = $1 + AND o.status = 'approved' + AND om.role IN ('admin', 'mentor') + AND b.archived_at IS NULL + AND b.is_active = TRUE + ORDER BY b.created_at DESC, om.joined_at DESC + LIMIT 1 + `, userID).Scan(&memberID, &orgRole, &orgID, &orgName, &orgSlug, &bootcampID, &bootcampName) + if err == nil { + resolved.MemberID = memberID + resolved.OrgRole = orgRole + resolved.Organization = &OrganizationData{ID: orgID, Name: orgName, Slug: orgSlug} + resolved.Bootcamp = &BootcampData{ID: bootcampID, Name: bootcampName} + resolved.Role = "mentor" + resolved.AccountStatus = "approved" + return resolved, nil + } + if !errors.Is(err, pgx.ErrNoRows) { + return nil, err + } + + var status string + err = s.pool.QueryRow(ctx, ` + SELECT + mr.status, + COALESCE(mr.sheet_key, ''), + o.id::text, + o.name, + o.slug, + b.id::text, + b.name + FROM coderz.mentee_requests mr + JOIN coderz.organizations o ON o.id = mr.organization_id + JOIN coderz.bootcamps b ON b.id = mr.bootcamp_id + WHERE mr.user_id = $1 + ORDER BY mr.created_at DESC + LIMIT 1 + `, userID).Scan(&status, &assignedSheet, &orgID, &orgName, &orgSlug, &bootcampID, &bootcampName) + if err == nil { + resolved.Organization = &OrganizationData{ID: orgID, Name: orgName, Slug: orgSlug} + resolved.Bootcamp = &BootcampData{ID: bootcampID, Name: bootcampName} + resolved.Role = "mentee" + resolved.AccountStatus = status + resolved.AssignedSheet = assignedSheet + return resolved, nil + } + if !errors.Is(err, pgx.ErrNoRows) { + return nil, err + } + + return resolved, nil +} + +func (s *Service) resolveMentorContext(ctx context.Context, userID string) (*resolvedContext, error) { + resolved, err := s.resolveContext(ctx, userID) + if err != nil { + return nil, err + } + if resolved.Role != "mentor" || resolved.AccountStatus != "approved" || resolved.Organization == nil || resolved.Bootcamp == nil || resolved.MemberID == "" { + return nil, errors.New("ACCESS_DENIED") + } + return resolved, nil +} + +func (s *Service) getDefaultSignupContext(ctx context.Context) (*OrganizationData, *BootcampData, error) { + var ( + orgID string + orgName string + orgSlug string + bootcampID string + bootcampName string + ) + if err := s.pool.QueryRow(ctx, ` + SELECT + o.id::text, + o.name, + o.slug, + b.id::text, + b.name + FROM coderz.bootcamps b + JOIN coderz.organizations o ON o.id = b.organization_id + WHERE o.status = 'approved' + AND b.archived_at IS NULL + AND b.is_active = TRUE + ORDER BY b.created_at DESC + LIMIT 1 + `).Scan(&orgID, &orgName, &orgSlug, &bootcampID, &bootcampName); err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, nil, errors.New("BOOTCAMP_NOT_CONFIGURED") + } + return nil, nil, err + } + + return &OrganizationData{ID: orgID, Name: orgName, Slug: orgSlug}, &BootcampData{ID: bootcampID, Name: bootcampName}, nil +} + +func (s *Service) ensureOrganizationMember(ctx context.Context, q db.DBTX, organizationID, userID string) (string, error) { + var memberID string + err := q.QueryRow(ctx, ` + SELECT id::text + FROM coderz.organization_members + WHERE organization_id = $1 + AND user_id = $2 + LIMIT 1 + `, organizationID, userID).Scan(&memberID) + if err == nil { + return memberID, nil + } + if !errors.Is(err, pgx.ErrNoRows) { + return "", err + } + + if err := q.QueryRow(ctx, ` + INSERT INTO coderz.organization_members ( + organization_id, + user_id, + role + ) VALUES ( + $1, + $2, + 'mentee' + ) + RETURNING id::text + `, organizationID, userID).Scan(&memberID); err != nil { + return "", err + } + + return memberID, nil +} + +func (s *Service) ensureBootcampEnrollment(ctx context.Context, q db.DBTX, bootcampID, memberID, assignedSheet string) error { + var enrollmentID string + err := q.QueryRow(ctx, ` + SELECT id::text + FROM coderz.bootcamp_enrollments + WHERE bootcamp_id = $1 + AND organization_member_id = $2 + LIMIT 1 + `, bootcampID, memberID).Scan(&enrollmentID) + if err != nil && !errors.Is(err, pgx.ErrNoRows) { + return err + } + + if errors.Is(err, pgx.ErrNoRows) { + _, err = q.Exec(ctx, ` + INSERT INTO coderz.bootcamp_enrollments ( + bootcamp_id, + organization_member_id, + role, + status, + assigned_sheet_key + ) VALUES ( + $1, + $2, + 'mentee', + 'active', + NULLIF($3, '') + ) + `, bootcampID, memberID, assignedSheet) + return err + } + + _, err = q.Exec(ctx, ` + UPDATE coderz.bootcamp_enrollments + SET + role = 'mentee', + status = 'active', + assigned_sheet_key = NULLIF($2, '') + WHERE id = $1 + `, enrollmentID, assignedSheet) + return err +} + +func (s *Service) listMenteeEnrollmentMap(ctx context.Context, q db.DBTX, bootcampID string) (map[string]string, error) { + rows, err := q.Query(ctx, ` + SELECT + LOWER(COALESCE(u.username, '')) AS username, + be.id::text + FROM coderz.bootcamp_enrollments be + JOIN coderz.organization_members om ON om.id = be.organization_member_id + JOIN coderz.users u ON u.id = om.user_id + WHERE be.bootcamp_id = $1 + AND be.role = 'mentee' + AND be.status = 'active' + `, bootcampID) + if err != nil { + return nil, err + } + defer rows.Close() + + result := make(map[string]string) + for rows.Next() { + var username string + var enrollmentID string + if err := rows.Scan(&username, &enrollmentID); err != nil { + return nil, err + } + result[username] = enrollmentID + } + + return result, rows.Err() +} + +func (s *Service) listMenteeRecords(ctx context.Context, bootcampID string) ([]menteeRecord, error) { + return s.listMenteeRecordsWithQuery(ctx, s.pool, bootcampID) +} + +func (s *Service) listMenteeRecordsWithQuery(ctx context.Context, q db.DBTX, bootcampID string) ([]menteeRecord, error) { + rows, err := q.Query(ctx, ` + SELECT + be.id::text, + om.id::text, + u.id::text, + u.name, + COALESCE(u.username, ''), + COALESCE(u.email, ''), + COALESCE(be.assigned_sheet_key, ''), + be.enrolled_at + FROM coderz.bootcamp_enrollments be + JOIN coderz.organization_members om ON om.id = be.organization_member_id + JOIN coderz.users u ON u.id = om.user_id + WHERE be.bootcamp_id = $1 + AND be.role = 'mentee' + AND be.status = 'active' + ORDER BY u.name ASC + `, bootcampID) + if err != nil { + return nil, err + } + defer rows.Close() + + mentees := make([]menteeRecord, 0) + for rows.Next() { + var ( + enrollmentID string + memberID string + userID string + fullName string + username string + email string + assignedSheet string + enrolledAt time.Time + ) + if err := rows.Scan(&enrollmentID, &memberID, &userID, &fullName, &username, &email, &assignedSheet, &enrolledAt); err != nil { + return nil, err + } + + firstName, lastName := splitName(fullName) + mentees = append(mentees, menteeRecord{ + EnrollmentID: enrollmentID, + MemberID: memberID, + UserID: userID, + FirstName: firstName, + LastName: lastName, + Username: username, + Email: email, + AssignedSheet: assignedSheet, + EnrolledAt: enrolledAt, + }) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return mentees, nil +} + +func (s *Service) findMenteeByUsername(ctx context.Context, q db.DBTX, bootcampID, username string) (*menteeRecord, error) { + var ( + enrollmentID string + memberID string + userID string + fullName string + foundUsername string + email string + assignedSheet string + enrolledAt time.Time + ) + if err := q.QueryRow(ctx, ` + SELECT + be.id::text, + om.id::text, + u.id::text, + u.name, + COALESCE(u.username, ''), + COALESCE(u.email, ''), + COALESCE(be.assigned_sheet_key, ''), + be.enrolled_at + FROM coderz.bootcamp_enrollments be + JOIN coderz.organization_members om ON om.id = be.organization_member_id + JOIN coderz.users u ON u.id = om.user_id + WHERE be.bootcamp_id = $1 + AND be.role = 'mentee' + AND be.status = 'active' + AND LOWER(COALESCE(u.username, '')) = LOWER($2) + LIMIT 1 + `, bootcampID, username).Scan(&enrollmentID, &memberID, &userID, &fullName, &foundUsername, &email, &assignedSheet, &enrolledAt); err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, errors.New("MENTEE_NOT_FOUND") + } + return nil, err + } + + firstName, lastName := splitName(fullName) + return &menteeRecord{ + EnrollmentID: enrollmentID, + MemberID: memberID, + UserID: userID, + FirstName: firstName, + LastName: lastName, + Username: foundUsername, + Email: email, + AssignedSheet: assignedSheet, + EnrolledAt: enrolledAt, + }, nil +} + +func (s *Service) getOrCreateProblem(ctx context.Context, q db.DBTX, organizationID, createdBy, sheetKey string, question sheetQuestion) (string, error) { + link := catalogLink(sheetKey, question.ID) + var problemID string + err := q.QueryRow(ctx, ` + SELECT id::text + FROM coderz.problems + WHERE organization_id = $1 + AND external_link = $2 + LIMIT 1 + `, organizationID, link).Scan(&problemID) + if err == nil { + return problemID, nil + } + if !errors.Is(err, pgx.ErrNoRows) { + return "", err + } + + if err := q.QueryRow(ctx, ` + INSERT INTO coderz.problems ( + organization_id, + created_by, + title, + description, + difficulty, + external_link + ) VALUES ( + $1, + $2, + $3, + $4, + $5, + $6 + ) + RETURNING id::text + `, organizationID, createdBy, question.Title, question.Description, question.Difficulty, link).Scan(&problemID); err != nil { + return "", err + } + + return problemID, nil +} + +func (s *Service) getQuestionRow(ctx context.Context, q db.DBTX, bootcampID, username, assignmentProblemID string) (*questionRow, error) { + row := q.QueryRow(ctx, ` + SELECT + ap.id::text, + a.id::text, + COALESCE(u.username, ''), + p.title, + COALESCE(p.description, ''), + p.difficulty::text, + COALESCE(p.external_link, ''), + COALESCE(ap.app_progress_status, ''), + ap.status::text, + COALESCE(ap.notes, ''), + COALESCE(ap.resources, ''), + a.assigned_at, + ap.completed_at + FROM coderz.assignment_problems ap + JOIN coderz.assignments a ON a.id = ap.assignment_id AND a.archived_at IS NULL + JOIN coderz.problems p ON p.id = ap.problem_id + JOIN coderz.bootcamp_enrollments be ON be.id = a.bootcamp_enrollment_id + JOIN coderz.organization_members om ON om.id = be.organization_member_id + JOIN coderz.users u ON u.id = om.user_id + WHERE ap.id = $1 + AND be.bootcamp_id = $2 + AND LOWER(COALESCE(u.username, '')) = LOWER($3) + LIMIT 1 + `, assignmentProblemID, bootcampID, username) + + question, err := scanQuestionRow(row) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, errors.New("QUESTION_NOT_FOUND") + } + return nil, err + } + + return &question, nil +} + +func (s *Service) updateAssignmentAggregate(ctx context.Context, q db.DBTX, assignmentID string) error { + var totalCount int + var completedCount int + if err := q.QueryRow(ctx, ` + SELECT + COUNT(*)::int, + COUNT(*) FILTER (WHERE status = 'completed')::int + FROM coderz.assignment_problems + WHERE assignment_id = $1 + `, assignmentID).Scan(&totalCount, &completedCount); err != nil { + return err + } + + status := "active" + if totalCount > 0 && totalCount == completedCount { + status = "completed" + } + + _, err := q.Exec(ctx, ` + UPDATE coderz.assignments + SET + status = $2, + updated_at = CURRENT_TIMESTAMP + WHERE id = $1 + `, assignmentID, status) + return err +} + +func (s *Service) refreshLeaderboard(ctx context.Context, q db.DBTX, bootcampID string) error { + rows, err := q.Query(ctx, ` + SELECT + be.id::text, + COUNT(ap.id)::int AS total_assigned, + COUNT(*) FILTER ( + WHERE ap.app_progress_status = 'completed' + OR ap.status = 'completed' + )::int AS completed_count, + COUNT(*) FILTER ( + WHERE ap.app_progress_status <> 'not_started' + OR ap.status IN ('attempted', 'completed') + )::int AS attempted_count + FROM coderz.bootcamp_enrollments be + JOIN coderz.organization_members om ON om.id = be.organization_member_id + JOIN coderz.users u ON u.id = om.user_id + LEFT JOIN coderz.assignments a + ON a.bootcamp_enrollment_id = be.id + AND a.archived_at IS NULL + LEFT JOIN coderz.assignment_problems ap ON ap.assignment_id = a.id + WHERE be.bootcamp_id = $1 + AND be.role = 'mentee' + AND be.status = 'active' + GROUP BY be.id, u.username, u.name + ORDER BY completed_count DESC, attempted_count DESC, u.name ASC + `, bootcampID) + if err != nil { + return err + } + defer rows.Close() + + type leaderboardRow struct { + enrollmentID string + total int + completed int + attempted int + } + stats := make([]leaderboardRow, 0) + for rows.Next() { + var item leaderboardRow + if err := rows.Scan(&item.enrollmentID, &item.total, &item.completed, &item.attempted); err != nil { + return err + } + stats = append(stats, item) + } + if err := rows.Err(); err != nil { + return err + } + + if _, err := q.Exec(ctx, ` + DELETE FROM coderz.leaderboard_entries + WHERE bootcamp_id = $1 + `, bootcampID); err != nil { + return err + } + + for index, item := range stats { + completionRate := float32(0) + if item.total > 0 { + completionRate = float32(item.completed) / float32(item.total) + } + score := (item.completed * 10) + (item.attempted * 3) + + if _, err := q.Exec(ctx, ` + INSERT INTO coderz.leaderboard_entries ( + bootcamp_id, + bootcamp_enrollment_id, + problems_completed, + problems_attempted, + completion_rate, + streak_days, + score, + rank, + calculated_at + ) VALUES ( + $1, + $2, + $3, + $4, + $5, + 0, + $6, + $7, + CURRENT_TIMESTAMP + ) + `, bootcampID, item.enrollmentID, item.completed, item.attempted, completionRate, score, index+1); err != nil { + return err + } + } + + return nil +} + +func (s *Service) countCompletedProblems(ctx context.Context, bootcampID, username string) (int, error) { + var solved int + if err := s.pool.QueryRow(ctx, ` + SELECT COUNT(*)::int + FROM coderz.assignment_problems ap + JOIN coderz.assignments a ON a.id = ap.assignment_id AND a.archived_at IS NULL + JOIN coderz.bootcamp_enrollments be ON be.id = a.bootcamp_enrollment_id + JOIN coderz.organization_members om ON om.id = be.organization_member_id + JOIN coderz.users u ON u.id = om.user_id + WHERE be.bootcamp_id = $1 + AND LOWER(COALESCE(u.username, '')) = LOWER($2) + AND ( + ap.app_progress_status = 'completed' + OR ap.status = 'completed' + ) + `, bootcampID, username).Scan(&solved); err != nil { + return 0, err + } + return solved, nil +} + +func scanQuestionRow(scanner interface{ Scan(dest ...any) error }) (questionRow, error) { + var row questionRow + err := scanner.Scan( + &row.ID, + &row.AssignmentID, + &row.TargetUsername, + &row.Title, + &row.Description, + &row.Difficulty, + &row.ExternalLink, + &row.AppProgress, + &row.LegacyStatus, + &row.Notes, + &row.Resources, + &row.AssignedAt, + &row.CompletedAt, + ) + return row, err +} + +func (q questionRow) normalizedProgressStatus() string { + if q.AppProgress != "" { + return q.AppProgress + } + switch q.LegacyStatus { + case "completed": + return "completed" + case "attempted": + return "revision_needed" + default: + return "not_started" + } +} + +func (q questionRow) toQuestionData() QuestionData { + description := q.Description + topic := "General" + if catalogQuestion, ok := findSheetQuestionByLink(q.ExternalLink); ok { + description = catalogQuestion.Description + topic = catalogQuestion.Topic + } + + progressStatus := q.normalizedProgressStatus() + status := "pending" + if progressStatus == "completed" { + status = "completed" + } + + completedAt := "" + if q.CompletedAt.Valid { + completedAt = q.CompletedAt.Time.Format(time.RFC3339) + } + + return QuestionData{ + ID: q.ID, + Title: q.Title, + Description: description, + Difficulty: q.Difficulty, + Topic: topic, + Status: status, + ProgressStatus: progressStatus, + AssignedAt: q.AssignedAt.Format(time.RFC3339), + CompletedAt: completedAt, + Solution: q.Notes, + Resources: q.Resources, + } +} + +func splitName(name string) (string, string) { + trimmed := strings.TrimSpace(name) + if trimmed == "" { + return "", "" + } + parts := strings.Fields(trimmed) + if len(parts) == 1 { + return parts[0], "" + } + return parts[0], strings.Join(parts[1:], " ") +} + +func normalizeUsername(username string) string { + return strings.ToLower(strings.TrimSpace(username)) +} + +func validatePasswordComplexity(password string) bool { + hasLetter := false + hasNumber := false + + for _, char := range password { + if (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') { + hasLetter = true + } + if char >= '0' && char <= '9' { + hasNumber = true + } + if hasLetter && hasNumber { + return true + } + } + + return false +} + +func dedupeLower(values []string) []string { + seen := make(map[string]struct{}, len(values)) + result := make([]string, 0, len(values)) + for _, value := range values { + normalized := strings.ToLower(strings.TrimSpace(value)) + if normalized == "" { + continue + } + if _, ok := seen[normalized]; ok { + continue + } + seen[normalized] = struct{}{} + result = append(result, normalized) + } + return result +} + +func dedupeStrings(values []string) []string { + seen := make(map[string]struct{}, len(values)) + result := make([]string, 0, len(values)) + for _, value := range values { + normalized := strings.TrimSpace(value) + if normalized == "" { + continue + } + if _, ok := seen[normalized]; ok { + continue + } + seen[normalized] = struct{}{} + result = append(result, normalized) + } + return result +} + +func mapProgressToLegacyStatus(progress string) string { + switch progress { + case "completed": + return "completed" + case "discussion_needed", "revision_needed": + return "attempted" + default: + return "pending" + } +} + +func normalizeDay(day string) string { + return strings.ToLower(strings.TrimSpace(day)) +} + +func capitalizeWord(value string) string { + if value == "" { + return "" + } + return strings.ToUpper(value[:1]) + value[1:] +} + +func valueOrEmpty(value *string) string { + if value == nil { + return "" + } + return *value +} diff --git a/apps/server/internal/modules/app/service_test.go b/apps/server/internal/modules/app/service_test.go new file mode 100644 index 0000000..f459610 --- /dev/null +++ b/apps/server/internal/modules/app/service_test.go @@ -0,0 +1,119 @@ +package app + +import ( + "testing" + "time" + + "github.com/jackc/pgx/v5/pgtype" +) + +func TestNormalizeUsername(t *testing.T) { + if got := normalizeUsername(" Alice_User "); got != "alice_user" { + t.Fatalf("expected normalized username %q, got %q", "alice_user", got) + } +} + +func TestValidatePasswordComplexity(t *testing.T) { + tests := []struct { + password string + valid bool + }{ + {password: "Password123", valid: true}, + {password: "lettersonly", valid: false}, + {password: "123456789", valid: false}, + {password: "Alpha9", valid: true}, + } + + for _, tt := range tests { + t.Run(tt.password, func(t *testing.T) { + if got := validatePasswordComplexity(tt.password); got != tt.valid { + t.Fatalf("expected complexity check for %q to be %v, got %v", tt.password, tt.valid, got) + } + }) + } +} + +func TestQuestionRowToQuestionDataUsesCatalogMetadata(t *testing.T) { + assignedAt := time.Date(2026, time.April, 1, 9, 0, 0, 0, time.UTC) + completedAt := time.Date(2026, time.April, 2, 9, 0, 0, 0, time.UTC) + + row := questionRow{ + ID: "assignment-problem-1", + Title: "Array Rotation", + Description: "database description", + Difficulty: "easy", + ExternalLink: catalogLink("gfg-dsa-360", "gfg-1"), + AppProgress: "completed", + Notes: "notes", + Resources: "resources", + AssignedAt: assignedAt, + CompletedAt: pgtype.Timestamptz{ + Time: completedAt, + Valid: true, + }, + } + + data := row.toQuestionData() + + if data.Description != "Practice array rotation techniques and in-place updates." { + t.Fatalf("expected catalog description, got %q", data.Description) + } + if data.Topic != "Arrays" { + t.Fatalf("expected catalog topic %q, got %q", "Arrays", data.Topic) + } + if data.Status != "completed" { + t.Fatalf("expected completed status, got %q", data.Status) + } + if data.CompletedAt != completedAt.Format(time.RFC3339) { + t.Fatalf("expected completedAt %q, got %q", completedAt.Format(time.RFC3339), data.CompletedAt) + } +} + +func TestQuestionRowToQuestionDataFallsBackToDatabaseFields(t *testing.T) { + assignedAt := time.Date(2026, time.April, 1, 9, 0, 0, 0, time.UTC) + row := questionRow{ + ID: "assignment-problem-2", + Title: "Custom Problem", + Description: "database description", + Difficulty: "medium", + AppProgress: "", + LegacyStatus: "attempted", + AssignedAt: assignedAt, + } + + data := row.toQuestionData() + + if data.Description != "database description" { + t.Fatalf("expected database description fallback, got %q", data.Description) + } + if data.Topic != "General" { + t.Fatalf("expected default topic %q, got %q", "General", data.Topic) + } + if data.ProgressStatus != "revision_needed" { + t.Fatalf("expected attempted legacy status to map to revision_needed, got %q", data.ProgressStatus) + } + if data.Status != "pending" { + t.Fatalf("expected non-completed question to stay pending, got %q", data.Status) + } +} + +func TestMapProgressToLegacyStatus(t *testing.T) { + tests := []struct { + progress string + expected string + }{ + {progress: "completed", expected: "completed"}, + {progress: "discussion_needed", expected: "attempted"}, + {progress: "revision_needed", expected: "attempted"}, + {progress: "not_started", expected: "pending"}, + {progress: "unexpected", expected: "pending"}, + } + + for _, tt := range tests { + t.Run(tt.progress, func(t *testing.T) { + if got := mapProgressToLegacyStatus(tt.progress); got != tt.expected { + t.Fatalf("expected legacy status %q, got %q", tt.expected, got) + } + }) + } +} diff --git a/apps/server/internal/modules/auth/handler.go b/apps/server/internal/modules/auth/handler.go index 90a7836..45f3071 100644 --- a/apps/server/internal/modules/auth/handler.go +++ b/apps/server/internal/modules/auth/handler.go @@ -1,7 +1,6 @@ package auth import ( - "fmt" "net/http" "github.com/coderz-space/coderz.space/internal/common/middleware/auth" @@ -36,20 +35,15 @@ func (h *Handler) Signup(c *echo.Context) error { if err := (&echo.DefaultBinder{}).Bind(c, &body); err != nil { return response.NewResponse(c, http.StatusBadRequest, "BAD_REQUEST", "INVALID_REQUEST_BODY", nil, err) } - fmt.Println("hello world😅 1") if err := validator.NewValidator().ValidateStruct(body); err != nil { - fmt.Println("hello world😅 2") - return response.NewResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", "VALIDATION_FAILED", nil, err) } - fmt.Println("hello world😅 x") data, err := h.service.Signup(c.Request().Context(), body) if err != nil { return response.NewResponse(c, http.StatusBadRequest, "BAD_REQUEST", err.Error(), nil, nil) } - fmt.Println("hello world😅 3") h.setAuthCookies(c, data.AccessToken, data.RefreshToken) @@ -240,12 +234,14 @@ func (h *Handler) ResetPassword(c *echo.Context) error { } func (h *Handler) setAuthCookies(c *echo.Context, accessToken, refreshToken string) { + secure := c.Scheme() == "https" + accessCookie := &http.Cookie{ Name: "access_token", Value: accessToken, Path: "/", HttpOnly: true, - Secure: true, + Secure: secure, SameSite: http.SameSiteStrictMode, MaxAge: 900, // 15 minutes } @@ -256,7 +252,7 @@ func (h *Handler) setAuthCookies(c *echo.Context, accessToken, refreshToken stri Value: refreshToken, Path: "/", HttpOnly: true, - Secure: true, + Secure: secure, SameSite: http.SameSiteStrictMode, MaxAge: int(h.service.config.RefreshTokenExpires.Seconds()), } diff --git a/apps/server/internal/routes/router.go b/apps/server/internal/routes/router.go index 4e9c9ad..0187db2 100644 --- a/apps/server/internal/routes/router.go +++ b/apps/server/internal/routes/router.go @@ -6,6 +6,7 @@ import ( "github.com/coderz-space/coderz.space/internal/container" "github.com/coderz-space/coderz.space/internal/modules/analytics" + "github.com/coderz-space/coderz.space/internal/modules/app" "github.com/coderz-space/coderz.space/internal/modules/assignment" "github.com/coderz-space/coderz.space/internal/modules/auth" "github.com/coderz-space/coderz.space/internal/modules/bootcamp" @@ -23,6 +24,10 @@ func RegisterRoutes(e *echo.Group, di *container.Container) { auth.RegisterPublicRoutes(e, di.AuthHandler) auth.RegisterProtectedRoutes(e, di.AuthHandler, di.Config) + // App facade routes + app.RegisterPublicRoutes(e, di.AppHandler) + app.RegisterProtectedRoutes(e, di.AppHandler, di.Config) + // Organization module routes organization.RegisterProtectedRoutes(e, di.OrganizationHandler, di.Config) diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index e568c26..f3de157 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -11,8 +11,8 @@ @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); + --font-sans: "Segoe UI", "Helvetica Neue", Arial, sans-serif; + --font-mono: "IBM Plex Mono", "SFMono-Regular", Consolas, monospace; } .dark { @@ -23,5 +23,5 @@ body { background: var(--background); color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; + font-family: var(--font-sans); } diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index aa909e7..5904608 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -1,18 +1,6 @@ import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; import ThemeToggle from "@/components/ThemeToggle"; -import { StubToastProvider } from "@/components/StubToast"; - -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); export const metadata: Metadata = { title: "Algo Buddy", @@ -26,24 +14,18 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + - {/* Blocking script: sets dark class before first paint to avoid flash */}